Merge "[Battery usage U] Remove 'Phone Idle' to avoid confusion"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5352562..36e4a98 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -126,6 +126,7 @@
<uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
<uses-permission android:name="android.permission.START_VIEW_APP_FEATURES" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES" />
+ <uses-permission android:name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS" />
<application
android:name=".SettingsApplication"
@@ -760,7 +761,7 @@
<category android:name="android.intent.category.VOICE_LAUNCH" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
- android:value="com.android.settings.TetherSettings" />
+ android:value="com.android.settings.network.tether.TetherSettings" />
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
android:value="@string/menu_key_network"/>
</activity>
@@ -792,7 +793,7 @@
android:exported="true"
android:targetActivity="Settings$TetherSettingsActivity">
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
- android:value="com.android.settings.TetherSettings" />
+ android:value="com.android.settings.network.tether.TetherSettings" />
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
android:value="@string/menu_key_network"/>
</activity-alias>
diff --git a/res/drawable/ia_settings_communal.xml b/res/drawable/ia_settings_communal.xml
new file mode 100644
index 0000000..14642c9
--- /dev/null
+++ b/res/drawable/ia_settings_communal.xml
@@ -0,0 +1,24 @@
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?android:attr/colorControlNormal">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M24,38Q19,38 15.5,37.325Q12,36.65 12,35.7V34H7.25Q5.95,34 5.05,33.025Q4.15,32.05 4.25,30.75L5.8,12.75Q5.9,11.6 6.75,10.8Q7.6,10 8.75,10H39.25Q40.4,10 41.25,10.8Q42.1,11.6 42.2,12.75L43.75,30.75Q43.85,32.05 42.95,33.025Q42.05,34 40.75,34H36V35.7Q36,36.65 32.5,37.325Q29,38 24,38ZM7.25,31H40.75Q40.75,31 40.75,31Q40.75,31 40.75,31L39.25,13Q39.25,13 39.25,13Q39.25,13 39.25,13H8.75Q8.75,13 8.75,13Q8.75,13 8.75,13L7.25,31Q7.25,31 7.25,31Q7.25,31 7.25,31Z"/>
+</vector>
diff --git a/res/drawable/ic_admin_panel_settings.xml b/res/drawable/ic_admin_panel_settings.xml
new file mode 100644
index 0000000..2d3a7f1
--- /dev/null
+++ b/res/drawable/ic_admin_panel_settings.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="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,17Q17.625,17 18.062,16.562Q18.5,16.125 18.5,15.5Q18.5,14.875 18.062,14.438Q17.625,14 17,14Q16.375,14 15.938,14.438Q15.5,14.875 15.5,15.5Q15.5,16.125 15.938,16.562Q16.375,17 17,17ZM17,20Q17.775,20 18.425,19.637Q19.075,19.275 19.475,18.675Q18.925,18.35 18.3,18.175Q17.675,18 17,18Q16.325,18 15.7,18.175Q15.075,18.35 14.525,18.675Q14.925,19.275 15.575,19.637Q16.225,20 17,20ZM12,22Q8.525,21.125 6.263,18.012Q4,14.9 4,11.1V5L12,2L20,5V10.675Q19.525,10.475 19.025,10.312Q18.525,10.15 18,10.075V6.4L12,4.15L6,6.4V11.1Q6,12.275 6.312,13.45Q6.625,14.625 7.188,15.688Q7.75,16.75 8.55,17.65Q9.35,18.55 10.325,19.15Q10.6,19.95 11.05,20.675Q11.5,21.4 12.075,21.975Q12.05,21.975 12.038,21.988Q12.025,22 12,22ZM17,22Q14.925,22 13.463,20.538Q12,19.075 12,17Q12,14.925 13.463,13.462Q14.925,12 17,12Q19.075,12 20.538,13.462Q22,14.925 22,17Q22,19.075 20.538,20.538Q19.075,22 17,22ZM12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Z"/>
+</vector>
diff --git a/res/drawable/ic_modifier_keys_reset.xml b/res/drawable/ic_modifier_keys_reset.xml
new file mode 100644
index 0000000..a8cc198
--- /dev/null
+++ b/res/drawable/ic_modifier_keys_reset.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?androidprv:attr/colorAccentPrimaryVariant">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,4C13.58,0.69 7.31,1.58 4,6V4H2v6h6V8H5.09c1.44,-2.47 4.09,-3.98 6.94,-3.97c4.42,0.02 7.99,3.61 7.97,8.03s-3.61,7.99 -8.03,7.97C7.55,20.01 3.98,16.42 4,12H2c0,3.15 1.48,6.11 4,8c4.42,3.31 10.69,2.42 14,-2C23.31,13.58 22.42,7.31 18,4z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_settings_trackpad.xml b/res/drawable/ic_settings_trackpad.xml
new file mode 100644
index 0000000..9580e02
--- /dev/null
+++ b/res/drawable/ic_settings_trackpad.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M5,40Q3.75,40 2.875,39.125Q2,38.25 2,37V11Q2,9.75 2.875,8.875Q3.75,8 5,8H43Q44.25,8 45.125,8.875Q46,9.75 46,11V37Q46,38.25 45.125,39.125Q44.25,40 43,40ZM6.5,11H5Q5,11 5,11Q5,11 5,11V37Q5,37 5,37Q5,37 5,37H6.5ZM9.5,37H38.5V11H9.5ZM41.5,11V37H43Q43,37 43,37Q43,37 43,37V11Q43,11 43,11Q43,11 43,11ZM41.5,11H43Q43,11 43,11Q43,11 43,11Q43,11 43,11Q43,11 43,11H41.5ZM6.5,11H5Q5,11 5,11Q5,11 5,11Q5,11 5,11Q5,11 5,11Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_trackpad_gesture_back.xml b/res/drawable/ic_trackpad_gesture_back.xml
new file mode 100644
index 0000000..b6a80cf
--- /dev/null
+++ b/res/drawable/ic_trackpad_gesture_back.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2019 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:autoMirrored="true"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,11H7.83l5.59-5.59L12,4l-8,8l8,8l1.41-1.41L7.83,13H20V11z" />
+</vector>
diff --git a/res/drawable/ic_trackpad_gesture_home.xml b/res/drawable/ic_trackpad_gesture_home.xml
new file mode 100644
index 0000000..f0e7232
--- /dev/null
+++ b/res/drawable/ic_trackpad_gesture_home.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M11,39H18.5V26.5H29.5V39H37V19.5L24,9.75L11,19.5ZM8,42V18L24,6L40,18V42H26.5V29.5H21.5V42ZM24,24.35Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_trackpad_gesture_notifications.xml b/res/drawable/ic_trackpad_gesture_notifications.xml
new file mode 100644
index 0000000..37d77d8
--- /dev/null
+++ b/res/drawable/ic_trackpad_gesture_notifications.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:pathData="M18,17V11C18,7.93 16.37,5.36 13.5,4.68V4C13.5,3.17 12.83,2.5 12,2.5C11.17,2.5 10.5,3.17 10.5,4V4.68C7.64,5.36 6,7.92 6,11V17H4V19H14H14.38H20V17H18ZM16,17H8V11C8,8.52 9.51,6.5 12,6.5C14.49,6.5 16,8.52 16,11V17ZM14,20C14,21.1 13.1,22 12,22C10.9,22 10,21.1 10,20H14Z"
+ android:fillType="evenOdd"
+ android:fillColor="?android:attr/colorPrimary"/>
+</vector>
diff --git a/res/drawable/ic_trackpad_gesture_recent_apps.xml b/res/drawable/ic_trackpad_gesture_recent_apps.xml
new file mode 100644
index 0000000..76ba829
--- /dev/null
+++ b/res/drawable/ic_trackpad_gesture_recent_apps.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:pathData="M16,6C16,7.1 16.9,8 18,8C19.1,8 20,7.1 20,6C20,4.9 19.1,4 18,4C16.9,4 16,4.9 16,6ZM6,8C7.1,8 8,7.1 8,6C8,4.9 7.1,4 6,4C4.9,4 4,4.9 4,6C4,7.1 4.9,8 6,8ZM12.001,20C13.101,20 14.001,19.1 14.001,18C14.001,16.9 13.101,16 12.001,16C10.901,16 10.001,16.9 10.001,18C10.001,19.1 10.901,20 12.001,20ZM8.001,18C8.001,19.1 7.101,20 6.001,20C4.901,20 4.001,19.1 4.001,18C4.001,16.9 4.901,16 6.001,16C7.101,16 8.001,16.9 8.001,18ZM6.001,14C7.101,14 8.001,13.1 8.001,12C8.001,10.9 7.101,10 6.001,10C4.901,10 4.001,10.9 4.001,12C4.001,13.1 4.901,14 6.001,14ZM14.001,12C14.001,13.1 13.101,14 12.001,14C10.901,14 10.001,13.1 10.001,12C10.001,10.9 10.901,10 12.001,10C13.101,10 14.001,10.9 14.001,12ZM14.001,6C14.001,7.1 13.101,8 12.001,8C10.901,8 10.001,7.1 10.001,6C10.001,4.9 10.901,4 12.001,4C13.101,4 14.001,4.9 14.001,6ZM18,14C19.1,14 20,13.1 20,12C20,10.9 19.1,10 18,10C16.9,10 16,10.9 16,12C16,13.1 16.9,14 18,14ZM20,18C20,19.1 19.1,20 18,20C16.9,20 16,19.1 16,18C16,16.9 16.9,16 18,16C19.1,16 20,16.9 20,18Z"
+ android:fillType="evenOdd"
+ android:fillColor="?android:attr/colorPrimary"/>
+</vector>
diff --git a/res/drawable/ic_trackpad_gesture_switch_apps.xml b/res/drawable/ic_trackpad_gesture_switch_apps.xml
new file mode 100644
index 0000000..3d18d1c
--- /dev/null
+++ b/res/drawable/ic_trackpad_gesture_switch_apps.xml
@@ -0,0 +1,26 @@
+<!--
+Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?android:attr/colorControlNormal"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M15.85,40 L13.75,37.9 19.8,31.85H4V28.85H19.8L13.75,22.8L15.85,20.7L25.5,30.35ZM32.15,27.3 L22.5,17.65 32.15,8 34.25,10.1 28.2,16.15H44V19.15H28.2L34.25,25.2Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_trackpad_pointer_speed.xml b/res/drawable/ic_trackpad_pointer_speed.xml
new file mode 100644
index 0000000..4e9abeb
--- /dev/null
+++ b/res/drawable/ic_trackpad_pointer_speed.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6.05,24.35V21.35H12.35V24.35ZM11.75,35.9 L9.75,33.9 14.15,29.5 16.15,31.5ZM14.15,16.05 L9.75,11.65 11.75,9.65 16.15,14.05ZM37,40.05 L27.6,30.65 25.4,37.45 20.2,19.45 37.8,25 30.9,27.45 40.25,36.8ZM21.85,12.3V6H24.85V12.3ZM32.55,16.05 L30.55,14.05 34.95,9.65 36.95,11.65Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_trackpad_reverse_scrolling.xml b/res/drawable/ic_trackpad_reverse_scrolling.xml
new file mode 100644
index 0000000..a62f904
--- /dev/null
+++ b/res/drawable/ic_trackpad_reverse_scrolling.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16.1,25.5V9.7L10.1,15.7L8,13.6L17.65,3.95L27.3,13.6L25.2,15.7L19.1,9.65V25.5ZM30.35,43.95 L20.7,34.25 22.8,32.2 28.8,38.2V22.4H31.8V38.25L37.9,32.2L40,34.3Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_trackpad_tap_to_click.xml b/res/drawable/ic_trackpad_tap_to_click.xml
new file mode 100644
index 0000000..7db0454
--- /dev/null
+++ b/res/drawable/ic_trackpad_tap_to_click.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M23.25,2Q27.8,2 31.025,5.175Q34.25,8.35 34.25,12.9Q34.25,15.5 33.125,17.8Q32,20.1 29.95,21.7H28.25V19.2Q29.7,18.05 30.475,16.4Q31.25,14.75 31.25,12.9Q31.25,9.6 28.9,7.3Q26.55,5 23.25,5Q19.95,5 17.6,7.3Q15.25,9.6 15.25,12.9Q15.25,14.75 16.025,16.4Q16.8,18.05 18.25,19.2V22.8Q15.45,21.35 13.85,18.7Q12.25,16.05 12.25,12.9Q12.25,8.35 15.475,5.175Q18.7,2 23.25,2ZM21.35,44Q20.5,44 19.75,43.675Q19,43.35 18.45,42.8L8.15,32.5L10.95,29.6Q11.65,28.9 12.525,28.525Q13.4,28.15 14.35,28.4L18.25,29.3V13Q18.25,10.9 19.7,9.45Q21.15,8 23.25,8Q25.35,8 26.8,9.45Q28.25,10.9 28.25,13V21.6H29.55Q29.8,21.6 30,21.7Q30.2,21.8 30.45,21.9L37.85,25.5Q39.05,26.05 39.625,27.275Q40.2,28.5 39.95,29.8L38.15,40.7Q37.9,42.15 36.75,43.075Q35.6,44 34.15,44ZM20.95,41H35L37.15,28.55Q37.15,28.55 37.15,28.55Q37.15,28.55 37.15,28.55L28,24H25.25V13Q25.25,12.1 24.7,11.55Q24.15,11 23.25,11Q22.35,11 21.8,11.55Q21.25,12.1 21.25,13V32.95L13.55,31.3L12.4,32.45ZM35,41H20.95H21.25Q21.25,41 21.8,41Q22.35,41 23.25,41Q24.15,41 24.7,41Q25.25,41 25.25,41H28H35Q35,41 35,41Q35,41 35,41Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_trackpad_touch_gestures_inverse.xml b/res/drawable/ic_trackpad_touch_gestures_inverse.xml
new file mode 100644
index 0000000..d663572
--- /dev/null
+++ b/res/drawable/ic_trackpad_touch_gestures_inverse.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?android:attr/textColorPrimaryInverse">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M40.15,47 L38.2,43.1 34.3,41.15 38.2,39.3 40.15,35.3 42,39.3 46,41.15 42,43.1ZM28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4ZM23.05,48H30.4L29.75,45H23.05Q22,45 20.975,44.8Q19.95,44.6 19.25,43.9L6.5,30.65L7.85,29.4L19.75,35.85V9.15Q19.75,8.6 20.1,8.25Q20.45,7.9 21,7.9Q21.55,7.9 21.9,8.25Q22.25,8.6 22.25,9.15V24H26V4.25Q26,3.7 26.35,3.35Q26.7,3 27.25,3Q27.8,3 28.15,3.35Q28.5,3.7 28.5,4.25V24H32.25V6.1Q32.25,5.55 32.6,5.2Q32.95,4.85 33.5,4.85Q34.05,4.85 34.4,5.2Q34.75,5.55 34.75,6.1V24H38.5V12.45Q38.5,11.9 38.85,11.55Q39.2,11.2 39.75,11.2Q40.3,11.2 40.65,11.55Q41,11.9 41,12.45V31.4H44V12.45Q44,10.7 42.75,9.45Q41.5,8.2 39.75,8.2Q39.2,8.2 38.675,8.3Q38.15,8.4 37.75,8.75V6.45Q37.75,4.55 36.525,3.2Q35.3,1.85 33.5,1.85Q32.85,1.85 32.25,2.025Q31.65,2.2 31.1,2.6Q30.65,1.4 29.625,0.7Q28.6,0 27.3,0Q25.55,0 24.275,1.25Q23,2.5 23,4.25V5.55Q22.6,5.2 22.075,5.05Q21.55,4.9 21,4.9Q19.25,4.9 18,6.15Q16.75,7.4 16.75,9.15V30.95L8.85,26.5Q8,26.05 7.1,26.325Q6.2,26.6 5.5,27.25L2,30.3L16.85,45.7Q18.05,46.95 19.675,47.475Q21.3,48 23.05,48ZM6.85,14.8 L5.15,11.15 1.5,9.45 5.15,7.75 6.85,4.1 8.55,7.7 12.2,9.6 8.6,11.15Z"/>
+</vector>
diff --git a/res/drawable/ic_trackpad_touch_gestures_normal.xml b/res/drawable/ic_trackpad_touch_gestures_normal.xml
new file mode 100644
index 0000000..34619c8
--- /dev/null
+++ b/res/drawable/ic_trackpad_touch_gestures_normal.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M40.15,47 L38.2,43.1 34.3,41.15 38.2,39.3 40.15,35.3 42,39.3 46,41.15 42,43.1ZM28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4Q28.5,31.4 28.5,31.4ZM23.05,48H30.4L29.75,45H23.05Q22,45 20.975,44.8Q19.95,44.6 19.25,43.9L6.5,30.65L7.85,29.4L19.75,35.85V9.15Q19.75,8.6 20.1,8.25Q20.45,7.9 21,7.9Q21.55,7.9 21.9,8.25Q22.25,8.6 22.25,9.15V24H26V4.25Q26,3.7 26.35,3.35Q26.7,3 27.25,3Q27.8,3 28.15,3.35Q28.5,3.7 28.5,4.25V24H32.25V6.1Q32.25,5.55 32.6,5.2Q32.95,4.85 33.5,4.85Q34.05,4.85 34.4,5.2Q34.75,5.55 34.75,6.1V24H38.5V12.45Q38.5,11.9 38.85,11.55Q39.2,11.2 39.75,11.2Q40.3,11.2 40.65,11.55Q41,11.9 41,12.45V31.4H44V12.45Q44,10.7 42.75,9.45Q41.5,8.2 39.75,8.2Q39.2,8.2 38.675,8.3Q38.15,8.4 37.75,8.75V6.45Q37.75,4.55 36.525,3.2Q35.3,1.85 33.5,1.85Q32.85,1.85 32.25,2.025Q31.65,2.2 31.1,2.6Q30.65,1.4 29.625,0.7Q28.6,0 27.3,0Q25.55,0 24.275,1.25Q23,2.5 23,4.25V5.55Q22.6,5.2 22.075,5.05Q21.55,4.9 21,4.9Q19.25,4.9 18,6.15Q16.75,7.4 16.75,9.15V30.95L8.85,26.5Q8,26.05 7.1,26.325Q6.2,26.6 5.5,27.25L2,30.3L16.85,45.7Q18.05,46.95 19.675,47.475Q21.3,48 23.05,48ZM6.85,14.8 L5.15,11.15 1.5,9.45 5.15,7.75 6.85,4.1 8.55,7.7 12.2,9.6 8.6,11.15Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/modifier_key_bordered.xml b/res/drawable/modifier_key_bordered.xml
new file mode 100644
index 0000000..17f0090
--- /dev/null
+++ b/res/drawable/modifier_key_bordered.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="24dp" />
+ <solid android:color="@android:color/transparent"/>
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/modifier_key_colored.xml b/res/drawable/modifier_key_colored.xml
new file mode 100644
index 0000000..2bb033b
--- /dev/null
+++ b/res/drawable/modifier_key_colored.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="24dp"/>
+ <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/modifier_key_lisetview_background.xml b/res/drawable/modifier_key_lisetview_background.xml
new file mode 100644
index 0000000..b65c61e
--- /dev/null
+++ b/res/drawable/modifier_key_lisetview_background.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="24dp"/>
+ <solid android:color="?androidprv:attr/colorSurfaceVariant"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/layout/dialog_eid_status.xml b/res/layout/dialog_eid_status.xml
new file mode 100644
index 0000000..95d4872
--- /dev/null
+++ b/res/layout/dialog_eid_status.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/sim_content_padding">
+
+ <TextView
+ style="@style/device_info_dialog_value"
+ android:id="@+id/esim_id_value"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textIsSelectable="true"
+ android:text="@string/device_info_not_available"/>
+
+ <ImageView
+ android:id="@+id/esim_id_qrcode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ tools:ignore="ContentDescription" />
+
+</LinearLayout>
diff --git a/res/layout/modifier_key_item.xml b/res/layout/modifier_key_item.xml
new file mode 100644
index 0000000..7bd344c
--- /dev/null
+++ b/res/layout/modifier_key_item.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingEnd="?android:attr/scrollbarSize"
+ android:layout_weight="1">
+
+ <ImageView
+ android:id="@+id/modifier_key_check_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:layout_marginStart="24dip"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:src="@drawable/ic_check_24dp"
+ android:tint="?androidprv:attr/colorAccentPrimaryVariant"/>
+
+ <TextView
+ android:id="@+id/modifier_key_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:textDirection="locale"
+ android:padding="16dp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_toEndOf="@+id/modifier_key_check_icon"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+</RelativeLayout>
diff --git a/res/layout/modifier_key_picker_dialog.xml b/res/layout/modifier_key_picker_dialog.xml
new file mode 100644
index 0000000..8600348
--- /dev/null
+++ b/res/layout/modifier_key_picker_dialog.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/modifier_key_fragment_container"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:divider="?android:dividerHorizontal"
+ android:showDividers="end" >
+ <TextView
+ android:id="@+id/modifier_key_picker_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="24dip"
+ android:layout_marginBottom="8dip"
+ android:layout_gravity="center_horizontal"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/modifier_keys_picker_title"/>
+
+ <TextView
+ android:id="@+id/modifier_key_picker_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="16dip"
+ android:layout_gravity="center_horizontal"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"/>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:divider="?android:dividerHorizontal"
+ android:showDividers="end">
+ <ListView
+ android:id="@+id/modifier_key_picker"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:divider="@null"
+ android:dividerHeight="8dp"
+ android:padding="16dip"
+ android:listSelector="@drawable/modifier_key_lisetview_background"
+ />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dip"
+ android:layout_marginEnd="8dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1">
+
+ <Button
+ android:id="@+id/modifier_key_cancel_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_marginStart="8dip"
+ android:layout_alignParentStart="true"
+ android:paddingVertical="14dp"
+ android:drawablePadding="9dp"
+ style="@style/ModifierKeyButtonCancel"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/modifier_keys_cancel"/>
+
+ <Button
+ android:id="@+id/modifier_key_done_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_marginEnd="8dip"
+ android:layout_alignParentEnd="true"
+ android:paddingVertical="14dp"
+ android:drawablePadding="9dp"
+ style="@style/ModifierKeyButtonDone"
+ android:textColor="?androidprv:attr/textColorOnAccent"
+ android:text="@string/modifier_keys_done"/>
+
+ </RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/modifier_key_reset_dialog.xml b/res/layout/modifier_key_reset_dialog.xml
new file mode 100644
index 0000000..fd38b11
--- /dev/null
+++ b/res/layout/modifier_key_reset_dialog.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <TextView
+ android:id="@+id/modifier_key_reset_dialog_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="24dip"
+ android:layout_marginBottom="8dip"
+ android:layout_gravity="center_horizontal"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/modifier_keys_reset_title" />
+
+ <TextView
+ android:id="@+id/modifier_key_reset_dialog_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="16dip"
+ android:layout_marginStart="32dip"
+ android:layout_marginEnd="32dip"
+ android:gravity="center"
+ android:layout_gravity="center_horizontal"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/modifier_keys_reset_message" />
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dip"
+ android:layout_marginEnd="8dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1">
+
+ <Button
+ android:id="@+id/modifier_key_reset_cancel_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_marginStart="8dip"
+ android:layout_alignParentStart="true"
+ android:paddingVertical="14dp"
+ android:drawablePadding="9dp"
+ style="@style/ModifierKeyButtonCancel"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/modifier_keys_cancel"/>
+
+ <Button
+ android:id="@+id/modifier_key_reset_restore_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_marginEnd="8dip"
+ android:layout_alignParentEnd="true"
+ android:paddingVertical="14dp"
+ android:drawablePadding="9dp"
+ style="@style/ModifierKeyButtonCancel"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/modifier_keys_restore"/>
+
+ </RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 970261f..bbacc5c 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -600,6 +600,9 @@
<!-- Whether the dream setup activity should be enabled as part of setupwizard -->
<bool name="dream_setup_supported">false</bool>
+ <!-- Whether to show communal settings at the top level. -->
+ <bool name="config_show_communal_settings">false</bool>
+
<!-- Whether to put the apps with system UID into system component bucket or not -->
<bool name="config_battery_combine_system_components">false</bool>
diff --git a/res/values/menu_keys.xml b/res/values/menu_keys.xml
index 2841b69..27e9639 100755
--- a/res/values/menu_keys.xml
+++ b/res/values/menu_keys.xml
@@ -17,6 +17,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="menu_key_network" translatable="false">top_level_network</string>
+ <string name="menu_key_communal" translatable="false">top_level_communal</string>
<string name="menu_key_connected_devices" translatable="false">top_level_connected_devices</string>
<string name="menu_key_apps" translatable="false">top_level_apps</string>
<string name="menu_key_notifications" translatable="false">top_level_notifications</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 36b8ed6..0c8ca31 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -36,10 +36,10 @@
<!-- String for removal of sensitive info on about, depending on tap -->
<string name="device_info_protected_single_press">Tap to show info</string>
<!-- [CHAR LIMIT=NONE] Device Info screen. Countdown for user taps to enable development settings -->
- <plurals name="show_dev_countdown">
- <item quantity="one">You are now <xliff:g id="step_count">%1$d</xliff:g> step away from being a developer.</item>
- <item quantity="other">You are now <xliff:g id="step_count">%1$d</xliff:g> steps away from being a developer.</item>
- </plurals>
+ <string name="show_dev_countdown">{count, plural,
+ =1 {You are now # step away from being a developer.}
+ other {You are now # steps away from being a developer.}
+ }</string>
<!-- [CHAR LIMIT=NONE] Device Info screen. Confirmation that developer settings are enabled -->
<string name="show_dev_on">You are now a developer!</string>
<!-- [CHAR LIMIT=NONE] Device Info screen. Okay we get it, stop pressing, you already have it on -->
@@ -256,8 +256,8 @@
<string name="stylus_textfield_handwriting">Stylus writing in textfields</string>
<!-- Preference title for toggling whether stylus button presses are ignored [CHAR LIMIT=none] -->
<string name="stylus_ignore_button">Ignore all stylus button presses</string>
- <!-- Name shown in a USI stylus header in device details page [CHAR LIMIT=60] -->
- <string name="stylus_usi_header_title">USI stylus</string>
+ <!-- Name shown in the title of individual stylus preference in the connected devices page [CHAR LIMIT=60] -->
+ <string name="stylus_connected_devices_title">Stylus</string>
<!-- Date & time settings screen title -->
<string name="date_and_time">Date & time</string>
@@ -355,10 +355,10 @@
<string name="desc_app_locale_selection_supported">Only apps that support language selection are shown here.</string>
<!-- The title of the confirmation dialog shown when the user selects one / several languages and tries to remove them [CHAR LIMIT=60] -->
- <plurals name="dlg_remove_locales_title">
- <item quantity="one">Remove selected language?</item>
- <item quantity="other">Remove selected languages?</item>
- </plurals>
+ <string name="dlg_remove_locales_title">{count, plural,
+ =1 {Remove selected language?}
+ other {Remove selected languages?}
+ }</string>
<!-- The text of the confirmation dialog shown when the user selects several languages and tries to remove them [CHAR LIMIT=NONE] -->
<string name="dlg_remove_locales_message">Text will be displayed in another language.</string>
@@ -401,6 +401,8 @@
<string name="share">Share</string>
<!-- Button label for generic add action [CHAR LIMIT=20] -->
<string name="add">Add</string>
+ <!-- Button label for remove action [CHAR LIMIT=20] -->
+ <string name="remove">Remove</string>
<!-- Title of the Settings activity shown within the application itself. -->
<string name="settings_label">Settings</string>
@@ -426,6 +428,8 @@
<string name="date_time_auto">Set time automatically</string>
<!-- Date & time setting screen setting switch title: whether the time zone should be determined automatically [CHAR LIMIT=100] -->
<string name="zone_auto_title">Set automatically</string>
+ <!-- Date & time setting screen setting switch summary for non-telephony devices [CHAR LIMIT=100] -->
+ <string name="auto_zone_requires_location_summary">Location will be used for setting the time zone when this toggle is on</string>
<!-- Date & time setting screen setting option summary text for the automatic 24 hour setting checkbox [CHAR LIMIT=100] -->
<string name="date_time_24hour_auto">Use locale default</string>
<!-- Date & time setting screen setting check box title -->
@@ -1124,10 +1128,10 @@
<string name="lock_last_password_attempt_before_wipe_profile">If you enter an incorrect password on the next attempt, your work profile and its data will be deleted</string>
<!-- Hint shown in dialog screen when password is too short -->
- <plurals name="lockpassword_password_too_short">
- <item quantity="one">Must contain at least <xliff:g id="count" example="1">%d</xliff:g> character</item>
- <item quantity="other">Must be at least <xliff:g id="count" example="3">%d</xliff:g> characters</item>
- </plurals>
+ <string name="lockpassword_password_too_short">{count, plural,
+ =1 {Must contain at least # character}
+ other {Must be at least # characters}
+ }</string>
<!-- Hint shown in dialog screen when password is too short and only using numbers. [CHAR LIMIT=NONE] -->
<string name="lockpassword_password_too_short_all_numeric">
{count, plural,
@@ -1136,21 +1140,21 @@
}
</string>
<!-- Hint shown in dialog screen when PIN is too short -->
- <plurals name="lockpassword_pin_too_short">
- <item quantity="one">PIN must contain at least <xliff:g id="count" example="1">%d</xliff:g> digit</item>
- <item quantity="other">PIN must be at least <xliff:g id="count" example="3">%d</xliff:g> digits</item>
- </plurals>
+ <string name="lockpassword_pin_too_short">{count, plural,
+ =1 {PIN must contain at least # digit}
+ other {PIN must be at least # digits}
+ }</string>
<!-- Error shown in popup when password is too long -->
- <plurals name="lockpassword_password_too_long">
- <item quantity="one">Must be fewer than <xliff:g id="number" example="1">%d</xliff:g> character</item>
- <item quantity="other">Must be fewer than <xliff:g id="number" example="17">%d</xliff:g> characters</item>
- </plurals>
+ <string name="lockpassword_password_too_long">{count, plural,
+ =1 {Must be fewer than # character}
+ other {Must be fewer than # characters}
+ }</string>
<!-- Error shown in popup when PIN is too long -->
- <plurals name="lockpassword_pin_too_long">
- <item quantity="one">Must be fewer than <xliff:g id="number" example="1">%d</xliff:g> digit</item>
- <item quantity="other">Must be fewer than <xliff:g id="number" example="17">%d</xliff:g> digits</item>
- </plurals>
+ <string name="lockpassword_pin_too_long">{count, plural,
+ =1 {Must be fewer than # digit}
+ other {Must be fewer than # digits}
+ }</string>
<!-- Error shown when in PIN mode and PIN has been used recently. Please keep this string short! -->
<string name="lockpassword_pin_recently_used">Device admin doesn\'t allow using a recent PIN</string>
@@ -1159,46 +1163,46 @@
<string name="lockpassword_illegal_character">This can\'t include an invalid character</string>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of letters -->
- <plurals name="lockpassword_password_requires_letters">
- <item quantity="one">Must contain at least 1 letter</item>
- <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> letters</item>
- </plurals>
+ <string name="lockpassword_password_requires_letters">{count, plural,
+ =1 {Must contain at least 1 letter}
+ other {Must contain at least # letters}
+ }</string>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of lowercase letters -->
- <plurals name="lockpassword_password_requires_lowercase">
- <item quantity="one">Must contain at least 1 lowercase letter</item>
- <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> lowercase letters</item>
- </plurals>
+ <string name="lockpassword_password_requires_lowercase">{count, plural,
+ =1 {Must contain at least 1 lowercase letter}
+ other {Must contain at least # lowercase letters}
+ }</string>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of uppercase letters -->
- <plurals name="lockpassword_password_requires_uppercase">
- <item quantity="one">Must contain at least 1 uppercase letter</item>
- <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> uppercase letters</item>
- </plurals>
+ <string name="lockpassword_password_requires_uppercase">{count, plural,
+ =1 {Must contain at least 1 uppercase letter}
+ other {Must contain at least # uppercase letters}
+ }</string>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of numerical digits -->
- <plurals name="lockpassword_password_requires_numeric">
- <item quantity="one">Must contain at least 1 numerical digit</item>
- <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> numerical digits</item>
- </plurals>
+ <string name="lockpassword_password_requires_numeric">{count, plural,
+ =1 {Must contain at least 1 numerical digit}
+ other {Must contain at least # numerical digits}
+ }</string>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of special symbols -->
- <plurals name="lockpassword_password_requires_symbols">
- <item quantity="one">Must contain at least 1 special symbol</item>
- <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> special symbols</item>
- </plurals>
+ <string name="lockpassword_password_requires_symbols">{count, plural,
+ =1 {Must contain at least 1 special symbol}
+ other {Must contain at least # special symbols}
+ }</string>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of non-letter characters -->
- <plurals name="lockpassword_password_requires_nonletter">
- <item quantity="one">Must contain at least 1 non-letter character</item>
- <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> non-letter characters</item>
- </plurals>
+ <string name="lockpassword_password_requires_nonletter">{count, plural,
+ =1 {Must contain at least 1 non-letter character}
+ other {Must contain at least # non-letter characters}
+ }</string>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of non-numerical characters -->
- <plurals name="lockpassword_password_requires_nonnumerical">
- <item quantity="one">Must contain at least 1 non-numerical character</item>
- <item quantity="other">Must contain at least <xliff:g id="count" example="3">%d</xliff:g> non-numerical characters</item>
- </plurals>
+ <string name="lockpassword_password_requires_nonnumerical">{count, plural,
+ =1 {Must contain at least 1 non-numerical character}
+ other {Must contain at least # non-numerical characters}
+ }</string>
<!-- Error shown when in PASSWORD mode and password has been used recently. Please keep this string short! -->
<string name="lockpassword_password_recently_used">Device admin doesn\'t allow using a recent
@@ -1230,10 +1234,10 @@
<string name="number_of_device_admins_none">No active apps</string>
<!-- Summary of preference to manage device admin apps, informing the user how many device admin apps are installed and active -->
- <plurals name="number_of_device_admins">
- <item quantity="one"><xliff:g id="count">%d</xliff:g> active app</item>
- <item quantity="other"><xliff:g id="count">%d</xliff:g> active apps</item>
- </plurals>
+ <string name="number_of_device_admins">{count, plural,
+ =1 {# active app}
+ other {# active apps}
+ }</string>
<!-- Title of preference to manage trust agents -->
<string name="manage_trust_agents">Trust agents</string>
@@ -1245,10 +1249,10 @@
<string name="manage_trust_agents_summary">None</string>
<!-- Summary of preference to manage device policies when there is trust agent-->
- <plurals name="manage_trust_agents_summary_on">
- <item quantity="one">1 active trust agent</item>
- <item quantity="other"><xliff:g id="count" example="3">%d</xliff:g> active trust agents</item>
- </plurals>
+ <string name="manage_trust_agents_summary_on">{count, plural,
+ =1 {1 active trust agent}
+ other {# active trust agents}
+ }</string>
<!-- Bluetooth settings -->
<!--Used as title on second screen after selecting Bluetooth settings -->
@@ -1747,20 +1751,20 @@
<!-- Wi-Fi Advanced Settings --> <skip />
<!-- Wi-Fi settings screen, Saved networks summary. This shows below the "Saved networks" item and indicates the number of networks, not including passpoint network, a user has saved. [CHAR LIMIT=30] -->
- <plurals name="wifi_saved_access_points_summary">
- <item quantity="one">1 network</item>
- <item quantity="other">%d networks</item>
- </plurals>
+ <string name="wifi_saved_access_points_summary">{count, plural,
+ =1 {1 network}
+ other {# networks}
+ }</string>
<!-- Wi-Fi settings screen, Saved networks summary. This shows below the "Saved networks" item and indicates the number of passpoint networks a user has saved. [CHAR LIMIT=30] -->
- <plurals name="wifi_saved_passpoint_access_points_summary">
- <item quantity="one">1 subscription</item>
- <item quantity="other">%d subscriptions</item>
- </plurals>
+ <string name="wifi_saved_passpoint_access_points_summary">{count, plural,
+ =1 {1 subscription}
+ other {# subscriptions}
+ }</string>
<!-- Wi-Fi settings screen, Saved networks summary. This shows below the "Saved networks" item and indicates number of whole kinds networks, if there are both normal saved networks and saved passpoint networks. The number will be at least 2, so the one case is only to prevent lint error. [CHAR LIMIT=60] -->
- <plurals name="wifi_saved_all_access_points_summary">
- <item quantity="one">1 network & subscription</item>
- <item quantity="other">%d networks & subscriptions</item>
- </plurals>
+ <string name="wifi_saved_all_access_points_summary">{count, plural,
+ =1 {1 network & subscription}
+ other {# networks & subscriptions}
+ }</string>
<!-- Wi-Fi settings screen, advanced, title of the item to show the Wi-Fi device's SSID. [CHAR LIMIT=20] -->
<string name="wifi_advanced_ssid_title">SSID</string>
<!-- Wi-Fi settings screen, advanced, title of the item to show the device's Wi-Fi MAC address. [CHAR LIMIT=50] -->
@@ -2368,10 +2372,10 @@
Displayed in a dialog box. [CHAR LIMIT=100] -->
<string name="wrong_pin_code_pukked">Incorrect SIM PIN code you must now contact your carrier to unlock your device.</string>
<!-- Instructions telling the user that they entered the wrong SIM PIN while trying to unlock the keyguard. Displayed in a dialog box. [CHAR LIMIT=100] -->
- <plurals name="wrong_pin_code">
- <item quantity="one">Incorrect SIM PIN code, you have <xliff:g id="number">%d</xliff:g> remaining attempt before you must contact your carrier to unlock your device.</item>
- <item quantity="other">Incorrect SIM PIN code, you have <xliff:g id="number">%d</xliff:g> remaining attempts.</item>
- </plurals>
+ <string name="wrong_pin_code">{count, plural,
+ =1 {Incorrect SIM PIN code, you have # remaining attempt before you must contact your carrier to unlock your device.}
+ other {Incorrect SIM PIN code, you have # remaining attempts.}
+ }</string>
<!-- This is instruction text to inform the user that they have entered the wrong SIM PIN while trying to unlock the keyguard.
The variable will be replaced with a number. [CHAR LIMIT=NONE] -->
@@ -2411,6 +2415,10 @@
<string name="storage_settings_for_app" >Storage & cache</string>
<!-- Storage settings screen title -->
<string name="storage_settings_title">Storage settings</string>
+ <!-- About phone, title of EID -->
+ <string name="status_eid">EID</string>
+ <!-- About phone, title of EID for multi-sim devices -->
+ <string name="eid_multi_sim">EID (sim slot %1$d)</string>
<!-- About phone screen, title for IMEI for multi-sim devices -->
<string name="imei_multi_sim">IMEI (sim slot %1$d)</string>
<!-- About phone screen, summary of the MAC address [CHAR LIMIT=80] -->
@@ -2637,6 +2645,12 @@
<!-- Phone info screen, section titles: -->
<string name="battery_level_title">Battery level</string>
+ <!-- Communal Settings -->
+ <!-- Title of the communal settings under Settings > Communal [CHAR LIMIT=30] -->
+ <string name="communal_settings_title">Communal</string>
+ <!-- Summary of the communal settings under Settings > Communal [CHAR LIMIT=50] -->
+ <string name="communal_settings_summary">Communal settings</string>
+
<!-- APN Settings -->
<!-- APN settings screen title -->
<string name="apn_settings">APNs</string>
@@ -2724,7 +2738,7 @@
<string name="reset_mobile_network_settings_title">Reset Mobile Network Settings</string>
<!-- User selects Reset mobile network settings [CHAR LIMIT=NONE] -->
<string name="reset_mobile_network_settings_desc">This will reset all mobile network settings</string>
- <!-- Reset Mobile network setting confirmation screen title [CHAR LIMIT=30] -->
+ <!-- Reset Mobile network setting confirmation screen title [CHAR LIMIT=62] -->
<string name="reset_mobile_network_settings_confirm_title">Reset Mobile Network Settings?</string>
<!-- Reset Bluetooth and Wi-Fi Network -->
@@ -2941,18 +2955,10 @@
"total_location_app_count" is almost always greater than 1, so "apps" is always in plural form.
[CHAR LIMIT=NONE]-->
- <plurals name="location_app_permission_summary_location_on">
- <item quantity="one">
- <xliff:g id="permitted_location_app_count">%1$d</xliff:g>
- of
- <xliff:g id="total_location_app_count">%2$d</xliff:g>
- apps has access to location</item>
- <item quantity="other">
- <xliff:g id="permitted_location_app_count">%1$d</xliff:g>
- of
- <xliff:g id="total_location_app_count">%2$d</xliff:g>
- apps have access to location</item>
- </plurals>
+ <string name="location_app_permission_summary_location_on">{count, plural,
+ =1 {# of {total} apps has access to location}
+ other {# of {total} apps have access to location}
+ }</string>
<!-- [CHAR LIMIT=50] Location settings screen, sub category for recent location access -->
<string name="location_category_recent_location_access">Recent access</string>
<!-- Location settings screen, displayed when there're more than three recent location access apps [CHAR LIMIT=30] -->
@@ -2984,6 +2990,16 @@
<!-- [CHAR LIMIT=60] Date&Time settings screen, toggle button title -->
<string name="location_time_zone_detection_toggle_title">Use location</string>
+ <!-- [CHAR LIMIT=50] Date&Time settings screen, title of the dialog when AutoTimeZone is degraded -->
+ <string name="location_time_zone_detection_status_title">Cannot set the time zone automatically</string>
+ <!-- Date&Time settings screen, summary of the dialog when AutoTimeZone is degraded by settings-->
+ <string name="location_time_zone_detection_status_summary_degraded_by_settings" />
+ <!-- [CHAR LIMIT=60] Date&Time settings screen, summary of the dialog when AutoTimeZone is blocked by settings -->
+ <string name="location_time_zone_detection_status_summary_blocked_by_settings">Location or Location Services are off</string>
+ <!-- Date&Time settings screen, summary of the dialog when AutoTimeZone is blocked by the environment-->
+ <string name="location_time_zone_detection_status_summary_blocked_by_environment" />
+ <!-- Date&Time settings screen, summary of the dialog when AutoTimeZone is temporarily unavailable-->
+ <string name="location_time_zone_detection_status_summary_temporarily_unavailable" />
<!-- [CHAR LIMIT=60] Date&Time settings screen, title of the dialog shown when user tries to
enable GeoTZ when Location toggle is off. -->
<string name="location_time_zone_detection_location_is_off_dialog_title">Device location needed</string>
@@ -2993,6 +3009,8 @@
<!-- [CHAR LIMIT=30] Date&Time settings screen, button on a dialog shown when user tries to
enable GeoTZ, but Location toggle is off, which leads to Location settings page. -->
<string name="location_time_zone_detection_location_is_off_dialog_ok_button">Location settings</string>
+ <!-- [CHAR LIMIT=30] Date&Time settings screen, button on a dialog shown when the LTZP needs to be fixed -->
+ <string name="location_time_zone_provider_fix_dialog_ok_button">Fix this</string>
<!-- [CHAR LIMIT=30] Date&Time settings screen, button on a dialog shown when user tries to
enable GeoTZ, but Location toggle is off, which closes the dialog. -->
<string name="location_time_zone_detection_location_is_off_dialog_cancel_button">Cancel</string>
@@ -3297,10 +3315,10 @@
<!-- Category title listing recently used apps [CHAR_LIMIT=50]-->
<string name="recent_app_category_title">Recently opened apps</string>
<!-- Preference title for showing all apps on device [CHAR_LIMIT=50]-->
- <plurals name="see_all_apps_title">
- <item quantity="one">See all apps</item>
- <item quantity="other">See all %1$d apps</item>
- </plurals>
+ <string name="see_all_apps_title">{count, plural,
+ =1 {See all apps}
+ other {See all # apps}
+ }</string>
<!-- Title of the dialog that asks the user to contact the IT admin to reset password [CHAR LIMIT=40] -->
<string name="forgot_password_title">Contact your IT admin</string>
@@ -3330,10 +3348,10 @@
<!-- Manage applications, label that appears next to the cache size -->
<string name="cache_size_label">Cache</string>
<!-- Manage applications, individual application info storage screen. Describes the number of URIs (directories or files) an app has been granted access (by another apps)-->
- <plurals name="uri_permissions_text">
- <item quantity="one">1 item</item>
- <item quantity="other">%d items</item>
- </plurals>
+ <string name="uri_permissions_text">{count, plural,
+ =1 {1 item}
+ other {# items}
+ }</string>
<!-- Manage applications, individual application info storage screen. Button below list of URIs. -->
<string name="clear_uri_btn_text">Clear access</string>
<!-- Manage applications, Header name used for other controls -->
@@ -3662,6 +3680,84 @@
<!-- Title for the 'Virtual keyboards for work' preference. [CHAR LIMIT=45] -->
<string name="virtual_keyboards_for_work_title">On-screen keyboard for work</string>
+ <!-- Title for the button to trigger the 'trackpad settings' page if only connect with a touchpad. [CHAR LIMIT=35] -->
+ <string name="trackpad_settings">Touchpad</string>
+ <!-- Title for the button to trigger the 'trackpad settings' page if connect with a touchpad and a mouse. [CHAR LIMIT=35] -->
+ <string name="trackpad_mouse_settings">Touchpad & mouse</string>
+ <!-- Summary text for the 'trackpad settings' page. [CHAR LIMIT=100] -->
+ <string name="trackpad_settings_summary">Pointer speed, gestures</string>
+
+ <!-- Title text for 'Tap to click'. [CHAR LIMIT=35] -->
+ <string name="trackpad_tap_to_click">Tap to click</string>
+ <!-- Title text for 'Touchpad gestures' [CHAR LIMIT=35] -->
+ <string name="trackpad_touchpad_gesture_title">Touchpad gestures</string>
+ <!-- Summary text for 'Touchpad gestures' [CHAR LIMIT=60] -->
+ <string name="trackpad_touchpad_gesture_summary">Customize individual touchpad navigation gestures</string>
+ <!-- Title text for 'Reverse scrolling' [CHAR LIMIT=35] -->
+ <string name="trackpad_reverse_scrolling_title">Reverse scrolling</string>
+ <!-- Summary text for 'Reverse scrolling' [CHAR LIMIT=60] -->
+ <string name="trackpad_reverse_scrolling_summary">Content moves up when you scroll down</string>
+ <!-- Title text for 'Bottom-right tap' [CHAR LIMIT=35] -->
+ <string name="trackpad_bottom_right_tap_title">Bottom-right tap</string>
+ <!-- Summary text for 'Bottom-right tap' [CHAR LIMIT=60] -->
+ <string name="trackpad_bottom_right_tap_summary">Tap the bottom right corner of the touchpad for more options</string>
+ <!-- Title text for 'Pointer speed'. [CHAR LIMIT=35] -->
+ <string name="trackpad_pointer_speed">Pointer speed</string>
+ <!-- Title for the button to trigger the 'touch gesture' education. [CHAR LIMIT=35] -->
+ <string name="trackpad_touch_gesture">Learn touchpad gestures</string>
+
+ <!-- Title text for 'Go back' [CHAR LIMIT=35] -->
+ <string name="trackpad_go_back_title">Go back</string>
+ <!-- Summary text for 'Go back' [CHAR LIMIT=60] -->
+ <string name="trackpad_go_back_summary">Swipe left or right with three fingers</string>
+ <!-- Title text for 'Go home' [CHAR LIMIT=35] -->
+ <string name="trackpad_go_home_title">Go home</string>
+ <!-- Summary text for 'Go home' [CHAR LIMIT=60] -->
+ <string name="trackpad_go_home_summary">Swipe up with three fingers</string>
+ <!-- Title text for 'Recent apps' [CHAR LIMIT=35] -->
+ <string name="trackpad_recent_apps_title">Recent apps</string>
+ <!-- Summary text for 'Recent apps' [CHAR LIMIT=60] -->
+ <string name="trackpad_recent_apps_summary">Swipe up with three fingers, then hold</string>
+ <!-- Title text for 'Notifications' [CHAR LIMIT=35] -->
+ <string name="trackpad_notifications_title">Notifications</string>
+ <!-- Summary text for 'Notifications' [CHAR LIMIT=60] -->
+ <string name="trackpad_notifications_summary">Swipe down with three fingers</string>
+ <!-- Title text for 'Switch apps' [CHAR LIMIT=35] -->
+ <string name="trackpad_switch_apps_title">Switch apps</string>
+ <!-- Summary text for 'Switch apps' [CHAR LIMIT=60] -->
+ <string name="trackpad_switch_apps_summary">Swipe left or right with four fingers</string>
+
+ <!-- Title for the button to trigger the 'modifier keys settings' page. [CHAR LIMIT=35] -->
+ <string name="modifier_keys_settings">Modifier keys</string>
+ <!-- Summary text for the 'modifier_keys_settings' page. [CHAR LIMIT=100] -->
+ <string name="modifier_keys_settings_summary">Change the behavior of keys</string>
+
+ <!-- Title for the button to trigger the 'modifier keys caps lock'. [CHAR LIMIT=15] -->
+ <string name="modifier_keys_caps_lock" translatable="false">Caps lock</string>
+ <!-- Title for the button to trigger the 'modifier keys ctrl'. [CHAR LIMIT=15] -->
+ <string name="modifier_keys_ctrl" translatable="false">Ctrl</string>
+ <!-- Title for the button to trigger the 'modifier keys meta'. [CHAR LIMIT=15] -->
+ <string name="modifier_keys_meta" translatable="false">Meta</string>
+ <!-- Title for the button to trigger the 'modifier keys alt'. [CHAR LIMIT=15] -->
+ <string name="modifier_keys_alt" translatable="false">Alt</string>
+
+ <!-- Title for the button to trigger the 'modifier keys reset all'. [CHAR LIMIT=35] -->
+ <string name="modifier_keys_reset_title">Reset all</string>
+ <!-- Summary text for the buttons. [CHAR LIMIT=35] -->
+ <string name="modifier_keys_default_summary">Default</string>
+ <!-- Message for the 'Reset all' page. [CHAR LIMIT=100] -->
+ <string name="modifier_keys_reset_message">Are you sure you would like to reset all the modifier keys to their default?</string>
+ <!-- Text of the done button. [CHAR LIMIT=15] -->
+ <string name="modifier_keys_done">Done</string>
+ <!-- Text of the cancel button. [CHAR LIMIT=15] -->
+ <string name="modifier_keys_cancel">Cancel</string>
+ <!-- Text of the restore button. [CHAR LIMIT=15] -->
+ <string name="modifier_keys_restore">Restore</string>
+ <!-- Title for the modifier key picker dialog page [CHAR LIMIT=35] -->
+ <string name="modifier_keys_picker_title">Choose modifier key</string>
+ <!-- Summary for the modifier key picker dialog page [CHAR LIMIT=35] -->
+ <string name="modifier_keys_picker_summary">Choose a new key for <xliff:g id="modifier_key_default_name">%1$s</xliff:g>:</string>
+
<!-- Summary text for keyboards when no layout has been selected. [CHAR LIMIT=35] -->
<string name="default_keyboard_layout">Default</string>
@@ -5967,12 +6063,18 @@
<!-- Title of preference to enable calling and SMS [CHAR LIMIT=45] -->
<string name="user_enable_calling_sms">Turn on phone calls & SMS</string>
+ <!-- Title of preference to grant user admin privileges [CHAR LIMIT=45] -->
+ <string name="user_grant_admin">Give this user admin privileges</string>
<!-- Title of preference to remove the user [CHAR LIMIT=35] -->
<string name="user_remove_user">Delete user</string>
<!-- Title for confirmation of turning on calls and SMS [CHAR LIMIT=45] -->
<string name="user_enable_calling_and_sms_confirm_title">Turn on phone calls & SMS?</string>
<!-- Message for confirmation of turning on calls and SMS [CHAR LIMIT=none] -->
<string name="user_enable_calling_and_sms_confirm_message">Call and SMS history will be shared with this user.</string>
+ <!-- Title for confirmation of revoking admin privileges [CHAR LIMIT=45] -->
+ <string name="user_revoke_admin_confirm_title">Remove admin privileges?</string>
+ <!-- Message for confirmation of revoking admin privileges [CHAR LIMIT=none] -->
+ <string name="user_revoke_admin_confirm_message">Are you sure you want to remove this user\'s admin privileges?</string>
<!-- Title for the emergency info preference [CHAR LIMIT=40] -->
<string name="emergency_info_title">Emergency information</string>
<!-- Summary for the emergency info preference [CHAR LIMIT=40] -->
@@ -8119,7 +8221,7 @@
<!-- [CHAR LIMIT=60] turn eSim deletion confirmation on/off -->
<string name="confirm_sim_deletion_title">Confirm SIM deletion</string>
<!-- [CHAR LIMIT=NONE] eSim deletion confirmation description -->
- <string name="confirm_sim_deletion_description">Verify it\u0027s you before erasing a eSIM</string>
+ <string name="confirm_sim_deletion_description">Verify it\u0027s you before erasing an eSIM</string>
<!-- [CHAR LIMIT=32] Name of Advanced memory protection page in "More Security Settings" and heading of page. -->
<string name="memtag_title">Advanced memory protection beta</string>
@@ -9523,10 +9625,10 @@
<!-- Label explaining that the the number of apps is an estimation. [CHAR LIMIT=NONE] -->
<string name="enterprise_privacy_apps_count_estimation_info">Number of apps is estimated. It may not include apps installed outside of the Play Store.</string>
<!-- Summary indicating the number of apps that a label (e.g. installed apps or apps granted a particular permission) refers to. The number shown is a minimum as there may be additional apps we do not know about. [CHAR LIMIT=NONE] -->
- <plurals name="enterprise_privacy_number_packages_lower_bound">
- <item quantity="one">Minimum <xliff:g id="count">%d</xliff:g> app</item>
- <item quantity="other">Minimum <xliff:g id="count">%d</xliff:g> apps</item>
- </plurals>
+ <string name="enterprise_privacy_number_packages_lower_bound">{count, plural,
+ =1 {Minimum # app}
+ other {Minimum # apps}
+ }</string>
<!-- Label indicating that the admin granted one or more apps access to the device's location. [CHAR LIMIT=NONE] -->
<string name="enterprise_privacy_location_access">Location permissions</string>
<!-- Label indicating that the admin granted one or more apps access to the microphone. [CHAR LIMIT=NONE] -->
@@ -9536,10 +9638,10 @@
<!-- Label indicating that the admin set one or more apps as defaults for common actions (e.g. open browser, send e-mail). [CHAR LIMIT=NONE] -->
<string name="enterprise_privacy_enterprise_set_default_apps">Default apps</string>
<!-- Summary indicating the number of apps that a label (e.g. installed apps or apps granted a particular permission) refers to. [CHAR LIMIT=NONE] -->
- <plurals name="enterprise_privacy_number_packages">
- <item quantity="one"><xliff:g id="count">%d</xliff:g> app</item>
- <item quantity="other"><xliff:g id="count">%d</xliff:g> apps</item>
- </plurals>
+ <string name="enterprise_privacy_number_packages">{count, plural,
+ =1 {# app}
+ other {# apps}
+ }</string>
<!-- Label explaining that the current input method was set by the admin. [CHAR LIMIT=NONE] -->
<string name="enterprise_privacy_input_method">Default keyboard</string>
<!-- Summary indicating the input method set by the admin. [CHAR LIMIT=NONE] -->
@@ -9559,10 +9661,10 @@
<!-- Label explaining that the admin installed trusted CA certificates in work profile. [CHAR LIMIT=NONE] -->
<string name="enterprise_privacy_ca_certs_work">Trusted credentials in your work profile</string>
<!-- Summary indicating the number of trusted CA certificates installed by the admin. The number shown is a minimum as there may be additional CA certificates we do not know about. [CHAR LIMIT=NONE] -->
- <plurals name="enterprise_privacy_number_ca_certs">
- <item quantity="one">Minimum <xliff:g id="count">%d</xliff:g> CA certificate</item>
- <item quantity="other">Minimum <xliff:g id="count">%d</xliff:g> CA certificates</item>
- </plurals>
+ <string name="enterprise_privacy_number_ca_certs">{count, plural,
+ =1 {Minimum # CA certificate}
+ other {Minimum # CA certificates}
+ }</string>
<!-- Label explaining that the admin can lock the device and change the user's password. [CHAR LIMIT=NONE] -->
<string name="enterprise_privacy_lock_device">Admin can lock the device and reset password</string>
<!-- Label explaining that the admin can wipe the device remotely. [CHAR LIMIT=NONE] -->
@@ -9572,10 +9674,10 @@
<!-- Label explaining that the admin configured the work profile to wipe itself when the password is mistyped too many times. [CHAR LIMIT=NONE] -->
<string name="enterprise_privacy_failed_password_wipe_work">Failed password attempts before deleting work profile data</string>
<!-- Summary indicating the number of mistyped passwords after which the device or work profile wipes itself. [CHAR LIMIT=NONE] -->
- <plurals name="enterprise_privacy_number_failed_password_wipe">
- <item quantity="one"><xliff:g id="count">%d</xliff:g> attempt</item>
- <item quantity="other"><xliff:g id="count">%d</xliff:g> attempts</item>
- </plurals>
+ <string name="enterprise_privacy_number_failed_password_wipe">{count, plural,
+ =1 {# attempt}
+ other {# attempts}
+ }</string>
<!-- Message indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=NONE] -->
<string name="do_disclosure_generic">This device is managed by your organization.</string>
<!-- Message indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=NONE] -->
@@ -9636,26 +9738,26 @@
<!-- Strings for displaying which applications were set as default for specific actions. -->
<!-- Title for the apps that have been set as default handlers of camera-related intents. [CHAR LIMIT=30] -->
- <plurals name="default_camera_app_title">
- <item quantity="one">Camera app</item>
- <item quantity="other">Camera apps</item>
- </plurals>
+ <string name="default_camera_app_title">{count, plural,
+ =1 {Camera app}
+ other {Camera apps}
+ }</string>
<!-- Title for the app that has been set as default handler of calendar-related intents. [CHAR LIMIT=30] -->
<string name="default_calendar_app_title">Calendar app</string>
<!-- Title for the app that has been set as default handler of contacts-related intents. [CHAR LIMIT=30] -->
<string name="default_contacts_app_title">Contacts app</string>
<!-- Title for the apps that have been set as default handlers of new email intents. [CHAR LIMIT=30] -->
- <plurals name="default_email_app_title">
- <item quantity="one">Email client app</item>
- <item quantity="other">Email client apps</item>
- </plurals>
+ <string name="default_email_app_title">{count, plural,
+ =1 {Email client app}
+ other {Email client apps}
+ }</string>
<!-- Title for the app that has been set as default handler of geo-related intents. [CHAR LIMIT=30] -->
<string name="default_map_app_title">Map app</string>
<!-- Title for the apps that have been set as default handlers of call-related intents. [CHAR LIMIT=30] -->
- <plurals name="default_phone_app_title">
- <item quantity="one">Phone app</item>
- <item quantity="other">Phone apps</item>
- </plurals>
+ <string name="default_phone_app_title">{count, plural,
+ =1 {Phone app}
+ other {Phone apps}
+ }</string>
<!-- Template for concatenating two app names -->
<string name="app_names_concatenation_template_2"><xliff:g id="first_app_name">%1$s</xliff:g>, <xliff:g id="second_app_name">%2$s</xliff:g></string>
<!-- Template for concatenating three app names -->
@@ -9730,19 +9832,25 @@
<!-- AutoFill strings -->
<!-- Preference label for choosing auto-fill service. [CHAR LIMIT=60] -->
<string name="autofill_app">Autofill service</string>
+ <!-- Preference label for choosing auto-fill service. [CHAR LIMIT=60] -->
+ <string name="default_autofill_app">Default autofill service</string>
<!-- Preference category for showing auto-fill services with saved passwords. [CHAR LIMIT=60] -->
<string name="autofill_passwords">Passwords</string>
+ <!-- Preference category for showing credman services with saved credentials. [CHAR LIMIT=60] -->
+ <string name="credman_credentials">Password and identity services</string>
<!-- Summary for passwords settings that shows how many passwords are saved for each autofill
service. [CHAR LIMIT=NONE] -->
- <plurals name="autofill_passwords_count">
- <item quantity="one"><xliff:g id="count">%1$d</xliff:g> password</item>
- <item quantity="other"><xliff:g id="count">%1$d</xliff:g> passwords</item>
- </plurals>
+ <string name="autofill_passwords_count">{count, plural,
+ =1 {# password}
+ other {# passwords}
+ }</string>
<!-- DO NOT TRANSLATE Summary placeholder for when the passwords count is still loading or is
unavailable. -->
<string name="autofill_passwords_count_placeholder" translatable="false">\u2014</string>
<!-- Keywords for the auto-fill feature. [CHAR LIMIT=NONE] -->
<string name="autofill_keywords">auto, fill, autofill, password</string>
+ <!-- Keywords for the credman feature. [CHAR LIMIT=NONE] -->
+ <string name="credman_keywords">credentials, passkey, password</string>
<!-- Message of the warning dialog for setting the auto-fill app. [CHAR_LIMIT=NONE] -->
<string name="autofill_confirmation_message">
@@ -9755,6 +9863,21 @@
]]>
</string>
+ <!-- Title of the warning dialog for disabling the credential provider. [CHAR_LIMIT=NONE] -->
+ <string name="credman_confirmation_message_title">Turn off %1$s\?</string>
+
+ <!-- Message of the warning dialog for disabling the credential provider. [CHAR_LIMIT=NONE] -->
+ <string name="credman_confirmation_message">Saved info like addresses or payment methods won\'t be filled in when you sign in. To keep your saved info filled in, set a default autofill service.</string>
+
+ <!-- Title of the error dialog when too many credential providers are selected. [CHAR_LIMIT=NONE] -->
+ <string name="credman_error_message_title">Password and identity services limit</string>
+
+ <!-- Message of the error dialog when too many credential providers are selected. [CHAR_LIMIT=NONE] -->
+ <string name="credman_error_message">You can have up to 5 autofill and password services active at the same time. Turn off a service to add more.</string>
+
+ <!-- Positive button to turn off credential manager provider (confirmation). [CHAR LIMIT=60] -->
+ <string name="credman_confirmation_message_positive_button">Turn off</string>
+
<!-- Preference category for autofill debugging development settings. [CHAR LIMIT=25] -->
<string name="debug_autofill_category">Autofill</string>
@@ -10498,10 +10621,10 @@
<string name="accessibility_usage_title">Accessibility usage</string>
<!-- Summary for the accessibility usage preference in the Privacy page. [CHAR LIMIT=NONE] -->
- <plurals name="accessibility_usage_summary">
- <item quantity="one">1 app has full access to your device</item>
- <item quantity="other"><xliff:g id="service_count">%1$d</xliff:g> apps have full access to your device</item>
- </plurals>
+ <string name="accessibility_usage_summary">{count, plural,
+ =1 {1 app has full access to your device}
+ other {# apps have full access to your device}
+ }</string>
<!-- Label for the title on wfc disclaimer fragment. [CHAR LIMIT=40] -->
<string name="wfc_disclaimer_title_text">Important information</string>
@@ -11369,4 +11492,29 @@
<!-- [CHAR LIMIT=NONE] Title for preference: Transfer eSIM to another device -->
<string name="transfer_esim_to_another_device_title">Transfer eSIM to another device</string>
+ <!-- Background Install Control UI -->
+ <!-- [CHAR LIMIT=NONE] Preference Feature Summary -->
+ <string name="background_install_preference_summary">{count, plural,
+ =1 {# app}
+ other {# apps}
+ }</string>
+
+ <!-- [CHAR LIMIT=NONE] Feature Title -->
+ <string name="background_install_title">Apps installed in the background</string>
+ <!-- [CHAR LIMIT=NONE] Feature summary -->
+ <string name="background_install_summary">Your device manufacturer may install apps on your device in the background, or allow your carrier and other partners to do so.\u000a\u000aAny apps listed here aren\u0027t required for your device to function normally. You can uninstall apps you don\u0027t want. </string>
+ <!-- [CHAR LIMIT=NONE] Group list no entry -->
+ <string name="background_install_feature_list_no_entry">No apps installed in the background</string>
+ <!-- [CHAR LIMIT=NONE] Uninstall app button content description -->
+ <string name="background_install_uninstall_button_description">Uninstall app</string>
+ <!-- [CHAR LIMIT=NONE] Before time period group list title -->
+ <string name="background_install_before">{count, plural,
+ =1 {Apps installed in the last # month}
+ other {Apps installed in the last # months}
+ }</string>
+ <!-- [CHAR LIMIT=NONE] After time period group list title -->
+ <string name="background_install_after">{count, plural,
+ =1 {Apps installed more than # month ago}
+ other {Apps installed more than # months ago}
+ }</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 20ebe255..c262264 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -378,6 +378,20 @@
<item name="android:colorControlHighlight">@color/ripple_material_inverse</item>
</style>
+ <style name="ModifierKeyButtonCancel" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/modifier_key_bordered</item>
+ <item name="android:stateListAnimator">@null</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:padding">4dp</item>
+ </style>
+
+ <style name="ModifierKeyButtonDone" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/modifier_key_colored</item>
+ <item name="android:stateListAnimator">@null</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:padding">4dp</item>
+ </style>
+
<style name="LockPatternContainerStyle">
<item name="android:gravity">center</item>
<item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
diff --git a/res/xml/accounts_dashboard_settings_credman.xml b/res/xml/accounts_dashboard_settings_credman.xml
new file mode 100644
index 0000000..605d315
--- /dev/null
+++ b/res/xml/accounts_dashboard_settings_credman.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:key="user_and_account_settings_screen"
+ android:title="@string/account_dashboard_title"
+ settings:keywords="@string/keywords_accounts">
+
+ <PreferenceCategory
+ android:key="default_service_category"
+ android:order="10"
+ android:title="@string/default_autofill_app">
+
+ <com.android.settings.widget.GearPreference
+ android:fragment="com.android.settings.applications.defaultapps.DefaultAutofillPicker"
+ android:key="default_autofill_main"
+ android:title="@string/default_autofill_app"
+ settings:keywords="@string/autofill_keywords">
+ <extra
+ android:name="for_work"
+ android:value="false" />
+ </com.android.settings.widget.GearPreference>
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="credman_category"
+ android:order="20"
+ android:persistent="false"
+ android:title="@string/credman_credentials"
+ settings:controller="com.android.settings.applications.credentials.CredentialManagerPreferenceController"
+ settings:keywords="@string/credman_keywords" />
+
+ <PreferenceCategory
+ android:key="passwords_category"
+ android:order="30"
+ android:persistent="false"
+ settings:controller="com.android.settings.applications.autofill.PasswordsPreferenceController"
+ settings:keywords="@string/autofill_keywords" />
+
+ <PreferenceCategory
+ android:key="dashboard_tile_placeholder"
+ android:order="130"/>
+
+ <SwitchPreference
+ android:key="auto_sync_account_data"
+ android:title="@string/auto_sync_account_title"
+ android:summary="@string/auto_sync_account_summary"
+ android:order="202"
+ settings:allowDividerAbove="true"/>
+
+ <SwitchPreference
+ android:key="auto_sync_work_account_data"
+ android:title="@string/account_settings_menu_auto_sync_work"
+ android:summary="@string/auto_sync_account_summary"
+ settings:forWork="true"
+ android:order="203"/>
+
+ <SwitchPreference
+ android:key="auto_sync_personal_account_data"
+ android:title="@string/account_settings_menu_auto_sync_personal"
+ android:summary="@string/auto_sync_account_summary"
+ android:order="204"/>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/accounts_personal_dashboard_settings_credman.xml b/res/xml/accounts_personal_dashboard_settings_credman.xml
new file mode 100644
index 0000000..a5188dd
--- /dev/null
+++ b/res/xml/accounts_personal_dashboard_settings_credman.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:key="user_and_account_settings_screen"
+ android:title="@string/account_dashboard_title"
+ settings:keywords="@string/keywords_accounts">
+
+ <PreferenceCategory
+ android:key="default_service_category"
+ android:order="10"
+ android:title="@string/default_autofill_app">
+
+ <com.android.settings.widget.GearPreference
+ android:fragment="com.android.settings.applications.defaultapps.DefaultAutofillPicker"
+ android:key="default_autofill_main"
+ android:title="@string/default_autofill_app"
+ settings:keywords="@string/autofill_keywords">
+ <extra
+ android:name="for_work"
+ android:value="false" />
+ </com.android.settings.widget.GearPreference>
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="credman_category"
+ android:order="20"
+ android:persistent="false"
+ android:title="@string/credman_credentials"
+ settings:controller="com.android.settings.applications.credentials.CredentialManagerPreferenceController"
+ settings:keywords="@string/credman_keywords" />
+
+ <PreferenceCategory
+ android:key="passwords_category"
+ android:order="30"
+ android:persistent="false"
+ settings:controller="com.android.settings.applications.autofill.PasswordsPreferenceController"
+ settings:keywords="@string/autofill_keywords" />
+
+ <PreferenceCategory
+ android:key="dashboard_tile_placeholder"
+ android:order="130"/>
+
+ <SwitchPreference
+ android:key="auto_sync_account_data"
+ android:title="@string/auto_sync_account_title"
+ android:summary="@string/auto_sync_account_summary"
+ android:order="200"
+ settings:allowDividerAbove="true"/>
+
+ <SwitchPreference
+ android:key="auto_sync_personal_account_data"
+ android:title="@string/account_settings_menu_auto_sync_personal"
+ android:summary="@string/auto_sync_account_summary"
+ android:order="210"/>
+
+</PreferenceScreen>
diff --git a/res/xml/accounts_work_dashboard_settings_credman.xml b/res/xml/accounts_work_dashboard_settings_credman.xml
new file mode 100644
index 0000000..f4e8af2
--- /dev/null
+++ b/res/xml/accounts_work_dashboard_settings_credman.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:key="user_and_account_settings_screen"
+ android:title="@string/account_dashboard_title"
+ settings:keywords="@string/keywords_accounts">
+
+ <com.android.settings.widget.WorkOnlyCategory
+ android:key="autofill_work_app_defaults"
+ android:order="30"
+ android:title="@string/default_autofill_app">
+
+ <com.android.settings.widget.GearPreference
+ android:fragment="com.android.settings.applications.defaultapps.DefaultAutofillPicker"
+ android:key="default_autofill_work"
+ android:title="@string/default_autofill_app"
+ settings:searchable="false">
+ <extra
+ android:name="for_work"
+ android:value="true" />
+ </com.android.settings.widget.GearPreference>
+ </com.android.settings.widget.WorkOnlyCategory>
+
+ <PreferenceCategory
+ android:key="credman_category"
+ android:order="20"
+ android:persistent="false"
+ android:title="@string/credman_credentials"
+ settings:controller="com.android.settings.applications.credentials.CredentialManagerPreferenceController"
+ settings:keywords="@string/credman_keywords" />
+
+ <PreferenceCategory
+ android:key="passwords_category"
+ android:order="30"
+ android:persistent="false"
+ settings:controller="com.android.settings.applications.autofill.PasswordsPreferenceController"
+ settings:keywords="@string/autofill_keywords" />
+
+ <PreferenceCategory
+ android:key="dashboard_tile_placeholder"
+ android:order="130"/>
+
+ <SwitchPreference
+ android:key="auto_sync_account_data"
+ android:title="@string/auto_sync_account_title"
+ android:summary="@string/auto_sync_account_summary"
+ android:order="200"
+ settings:allowDividerAbove="true"/>
+
+ <SwitchPreference
+ android:key="auto_sync_work_account_data"
+ android:title="@string/account_settings_menu_auto_sync_work"
+ android:summary="@string/auto_sync_account_summary"
+ android:order="210"/>
+
+</PreferenceScreen>
diff --git a/res/xml/communal_settings.xml b/res/xml/communal_settings.xml
new file mode 100644
index 0000000..1a7938f
--- /dev/null
+++ b/res/xml/communal_settings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="communal_preference_screen"
+ android:title="@string/communal_settings_title" />
diff --git a/res/xml/date_time_prefs.xml b/res/xml/date_time_prefs.xml
index e3d0a7e..593c428 100644
--- a/res/xml/date_time_prefs.xml
+++ b/res/xml/date_time_prefs.xml
@@ -47,6 +47,11 @@
android:title="@string/zone_auto_title"
settings:userRestriction="no_config_date_time"/>
+ <com.android.settingslib.widget.BannerMessagePreference
+ android:key="location_time_zone_detection_status"
+ android:title="@string/location_time_zone_detection_status_title"
+ settings:controller="com.android.settings.datetime.LocationProviderStatusPreferenceController"/>
+
<!-- This preference gets removed if location-based time zone detection is not supported -->
<SwitchPreference
android:key="location_time_zone_detection"
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index 1b5e6c0..ad5236e 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -146,7 +146,8 @@
android:key="screensaver"
android:title="@string/screensaver_settings_title"
android:fragment="com.android.settings.dream.DreamSettings"
- settings:keywords="@string/keywords_screensaver"/>
+ settings:keywords="@string/keywords_screensaver"
+ settings:controller="com.android.settings.display.ScreenSaverPreferenceController"/>
<SwitchPreference
android:key="camera_gesture"
diff --git a/res/xml/modifier_keys_settings.xml b/res/xml/modifier_keys_settings.xml
new file mode 100644
index 0000000..63e7ee1
--- /dev/null
+++ b/res/xml/modifier_keys_settings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/modifier_keys_settings"
+ android:key="modifier_keys_all"
+ settings:controller="com.android.settings.inputmethod.ModifierKeysPreferenceController">
+ <Preference
+ android:key="modifier_keys_caps_lock"
+ android:title="@string/modifier_keys_caps_lock"
+ android:summary="@string/modifier_keys_default_summary"/>
+
+ <Preference
+ android:key="modifier_keys_ctrl"
+ android:title="@string/modifier_keys_ctrl"
+ android:summary="@string/modifier_keys_default_summary"/>
+
+ <Preference
+ android:key="modifier_keys_meta"
+ android:title="@string/modifier_keys_meta"
+ android:summary="@string/modifier_keys_default_summary"/>
+
+ <Preference
+ android:key="modifier_keys_alt"
+ android:title="@string/modifier_keys_alt"
+ android:summary="@string/modifier_keys_default_summary"/>
+
+ <Preference
+ android:key="modifier_keys_restore"
+ android:icon="@drawable/ic_modifier_keys_reset"
+ settings:controller="com.android.settings.inputmethod.ModifierKeysRestorePreferenceController"/>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/my_device_info.xml b/res/xml/my_device_info.xml
index 9f14026..4cbe13f 100644
--- a/res/xml/my_device_info.xml
+++ b/res/xml/my_device_info.xml
@@ -105,7 +105,7 @@
<!-- Model & hardware -->
<Preference
android:key="device_model"
- android:order="31"
+ android:order="30"
android:title="@string/model_info"
android:summary="@string/summary_placeholder"
android:fragment="com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFragment"
@@ -113,6 +113,18 @@
settings:keywords="@string/keywords_model_and_hardware"
settings:controller="com.android.settings.deviceinfo.HardwareInfoPreferenceController"/>
+ <!-- EID -->
+ <com.android.settings.network.telephony.TelephonyPreferenceDialog
+ android:key="eid_info"
+ android:order="31"
+ android:title="@string/status_eid"
+ android:summary="@string/device_info_protected_single_press"
+ android:positiveButtonText="@string/dlg_ok"
+ android:dialogLayout="@layout/dialog_eid_status"
+ settings:isPreferenceVisible="@bool/config_show_sim_info"
+ settings:enableCopying="true"
+ settings:controller="com.android.settings.deviceinfo.simstatus.SimEidPreferenceController"/>
+
<!-- IMEI -->
<com.android.settings.deviceinfo.PhoneNumberSummaryPreference
android:key="imei_info"
diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml
index a4b5faa..bbe5ace 100644
--- a/res/xml/network_provider_internet.xml
+++ b/res/xml/network_provider_internet.xml
@@ -72,7 +72,7 @@
settings:useAdminDisabledSummary="true" />
<com.android.settingslib.RestrictedPreference
- android:fragment="com.android.settings.TetherSettings"
+ android:fragment="com.android.settings.network.tether.TetherSettings"
android:key="tether_settings"
android:title="@string/tether_settings_title_all"
android:icon="@drawable/ic_wifi_tethering"
diff --git a/res/xml/physical_keyboard_settings.xml b/res/xml/physical_keyboard_settings.xml
index 54a5c65..40851a9 100644
--- a/res/xml/physical_keyboard_settings.xml
+++ b/res/xml/physical_keyboard_settings.xml
@@ -31,5 +31,11 @@
android:key="keyboard_shortcuts_helper"
android:title="@string/keyboard_shortcuts_helper"
android:summary="@string/keyboard_shortcuts_helper_summary" />
+
+ <Preference
+ android:key="modifier_keys_settings"
+ android:title="@string/modifier_keys_settings"
+ android:summary="@string/modifier_keys_settings_summary"
+ android:fragment="com.android.settings.inputmethod.ModifierKeysSettings" />
</PreferenceCategory>
</PreferenceScreen>
diff --git a/res/xml/system_dashboard_fragment.xml b/res/xml/system_dashboard_fragment.xml
index 3b24203..1f5559f 100644
--- a/res/xml/system_dashboard_fragment.xml
+++ b/res/xml/system_dashboard_fragment.xml
@@ -46,6 +46,15 @@
settings:controller="com.android.settings.inputmethod.KeyboardPreferenceController"/>
<Preference
+ android:key="trackpad_settings"
+ android:title="@string/trackpad_settings"
+ android:summary="@string/trackpad_settings_summary"
+ android:icon="@drawable/ic_settings_trackpad"
+ android:order="-254"
+ android:fragment="com.android.settings.inputmethod.TrackpadSettings"
+ settings:controller="com.android.settings.inputmethod.TrackpadSettingsController"/>
+
+ <Preference
android:key="gesture_settings"
android:title="@string/gesture_preference_title"
android:icon="@drawable/ic_settings_gestures"
diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml
index 5fbc735..8c82b67 100644
--- a/res/xml/top_level_settings.xml
+++ b/res/xml/top_level_settings.xml
@@ -31,6 +31,16 @@
settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/>
<com.android.settings.widget.HomepagePreference
+ android:fragment="com.android.settings.communal.CommunalDashboardFragment"
+ android:icon="@drawable/ia_settings_communal"
+ android:key="top_level_communal"
+ android:order="-145"
+ android:title="@string/communal_settings_title"
+ android:summary="@string/communal_settings_summary"
+ settings:highlightableMenuKey="@string/menu_key_communal"
+ settings:controller="com.android.settings.communal.CommunalPreferenceController"/>
+
+ <com.android.settings.widget.HomepagePreference
android:fragment="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment"
android:icon="@drawable/ic_devices_other"
android:key="top_level_connected_devices"
diff --git a/res/xml/trackpad_gesture_settings.xml b/res/xml/trackpad_gesture_settings.xml
new file mode 100644
index 0000000..dfc4199
--- /dev/null
+++ b/res/xml/trackpad_gesture_settings.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/trackpad_touchpad_gesture_title">
+ <SwitchPreference
+ android:key="gesture_go_back"
+ android:title="@string/trackpad_go_back_title"
+ android:summary="@string/trackpad_go_back_summary"
+ android:icon="@drawable/ic_trackpad_gesture_back"
+ android:order="10"/>
+
+ <SwitchPreference
+ android:key="gesture_go_home"
+ android:title="@string/trackpad_go_home_title"
+ android:summary="@string/trackpad_go_home_summary"
+ android:icon="@drawable/ic_trackpad_gesture_home"
+ android:order="20"/>
+
+ <SwitchPreference
+ android:key="gesture_recent_apps"
+ android:title="@string/trackpad_recent_apps_title"
+ android:summary="@string/trackpad_recent_apps_summary"
+ android:icon="@drawable/ic_trackpad_gesture_recent_apps"
+ android:order="30"/>
+
+ <SwitchPreference
+ android:key="gesture_notifications"
+ android:title="@string/trackpad_notifications_title"
+ android:summary="@string/trackpad_notifications_summary"
+ android:icon="@drawable/ic_trackpad_gesture_notifications"
+ android:order="40"/>
+
+ <SwitchPreference
+ android:key="gesture_switch_apps"
+ android:title="@string/trackpad_switch_apps_title"
+ android:summary="@string/trackpad_switch_apps_summary"
+ android:icon="@drawable/ic_trackpad_gesture_switch_apps"
+ android:order="50"/>
+
+ <com.android.settingslib.widget.ButtonPreference
+ android:key="trackpad_touch_gesture_developer_mode"
+ android:title="@string/trackpad_touch_gesture"
+ android:icon="@drawable/ic_trackpad_touch_gestures_inverse"
+ settings:controller="com.android.settings.inputmethod.TouchGesturesButtonPreferenceController"/>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/trackpad_settings.xml b/res/xml/trackpad_settings.xml
new file mode 100644
index 0000000..6401fb8
--- /dev/null
+++ b/res/xml/trackpad_settings.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/trackpad_settings">
+ <Preference
+ android:key="trackpad_gesture_settings"
+ android:title="@string/trackpad_touchpad_gesture_title"
+ android:summary="@string/trackpad_touchpad_gesture_summary"
+ android:icon="@drawable/ic_trackpad_touch_gestures_normal"
+ android:order="-10"
+ android:fragment="com.android.settings.inputmethod.TrackpadTouchGestureSettings"
+ settings:controller="com.android.settings.inputmethod.TrackpadTouchGestureSettingsController"/>
+
+ <SwitchPreference
+ android:key="trackpad_tap_to_click"
+ android:title="@string/trackpad_tap_to_click"
+ android:icon="@drawable/ic_trackpad_tap_to_click"
+ android:order="10"/>
+
+ <SwitchPreference
+ android:key="trackpad_reverse_scrolling"
+ android:title="@string/trackpad_reverse_scrolling_title"
+ android:summary="@string/trackpad_reverse_scrolling_summary"
+ android:icon="@drawable/ic_trackpad_reverse_scrolling"
+ android:order="20"/>
+
+ <SwitchPreference
+ android:key="trackpad_bottom_right_tap"
+ android:title="@string/trackpad_bottom_right_tap_title"
+ android:summary="@string/trackpad_bottom_right_tap_summary"
+ android:icon="@drawable/ic_trackpad_reverse_scrolling"
+ android:order="30"/>
+
+ <com.android.settings.widget.SeekBarPreference
+ android:key="trackpad_pointer_speed"
+ android:title="@string/trackpad_pointer_speed"
+ android:icon="@drawable/ic_trackpad_pointer_speed"
+ android:order="40"
+ android:selectable="false"
+ android:max="100"
+ android:min="0"
+ android:defaultValue="50"/>
+
+ <com.android.settingslib.widget.ButtonPreference
+ android:key="trackpad_touch_gesture"
+ android:title="@string/trackpad_touch_gesture"
+ android:icon="@drawable/ic_trackpad_touch_gestures_inverse"
+ settings:controller="com.android.settings.inputmethod.TouchGesturesButtonPreferenceController"/>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/user_details_settings.xml b/res/xml/user_details_settings.xml
index 2301bac..068039c 100644
--- a/res/xml/user_details_settings.xml
+++ b/res/xml/user_details_settings.xml
@@ -22,6 +22,10 @@
android:key="switch_user"
android:icon="@drawable/ic_swap" />
<SwitchPreference
+ android:key="user_grant_admin"
+ android:icon="@drawable/ic_admin_panel_settings"
+ android:title="@string/user_grant_admin" />
+ <SwitchPreference
android:key="enable_calling"
android:icon="@drawable/ic_phone"
android:title="@string/user_enable_calling_sms" />
diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java
index 9a62412..97b9aae 100644
--- a/src/com/android/settings/DisplaySettings.java
+++ b/src/com/android/settings/DisplaySettings.java
@@ -24,7 +24,6 @@
import com.android.settings.display.BrightnessLevelPreferenceController;
import com.android.settings.display.CameraGesturePreferenceController;
import com.android.settings.display.LiftToWakePreferenceController;
-import com.android.settings.display.ScreenSaverPreferenceController;
import com.android.settings.display.ShowOperatorNamePreferenceController;
import com.android.settings.display.TapToWakePreferenceController;
import com.android.settings.display.ThemePreferenceController;
@@ -76,7 +75,6 @@
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new CameraGesturePreferenceController(context));
controllers.add(new LiftToWakePreferenceController(context));
- controllers.add(new ScreenSaverPreferenceController(context));
controllers.add(new TapToWakePreferenceController(context));
controllers.add(new VrDisplayPreferenceController(context));
controllers.add(new ShowOperatorNamePreferenceController(context));
diff --git a/src/com/android/settings/IccLockSettings.java b/src/com/android/settings/IccLockSettings.java
index c206cc6..0ebe8c7 100644
--- a/src/com/android/settings/IccLockSettings.java
+++ b/src/com/android/settings/IccLockSettings.java
@@ -57,6 +57,7 @@
import com.android.settings.network.ProxySubscriptionManager;
import com.android.settings.network.SubscriptionUtil;
+import com.android.settingslib.utils.StringUtil;
import java.util.ArrayList;
import java.util.List;
@@ -674,9 +675,8 @@
} else if (attemptsRemaining == 1) {
displayMessage = mRes.getString(R.string.wrong_pin_code_one, attemptsRemaining);
} else if (attemptsRemaining > 1) {
- displayMessage = mRes
- .getQuantityString(R.plurals.wrong_pin_code, attemptsRemaining,
- attemptsRemaining);
+ displayMessage = StringUtil.getIcuPluralsString(getPrefContext(), attemptsRemaining,
+ R.string.wrong_pin_code);
} else {
displayMessage = mRes.getString(R.string.pin_failed);
}
diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java
index 0d2cfe9..e6e6df3 100644
--- a/src/com/android/settings/SettingsApplication.java
+++ b/src/com/android/settings/SettingsApplication.java
@@ -43,7 +43,11 @@
@Override
public void onCreate() {
super.onCreate();
- ElapsedTimeUtils.assignSuwFinishedTimeStamp(this.getApplicationContext());
+
+ // Add null checking to avoid test case failed.
+ if (getApplicationContext() != null) {
+ ElapsedTimeUtils.assignSuwFinishedTimeStamp(getApplicationContext());
+ }
// Set Spa environment.
setSpaEnvironment();
diff --git a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
index 22bef28..7316e12 100644
--- a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
@@ -210,6 +210,8 @@
@Override
public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
if (mNeedResetSettings) {
outState.putBoolean(NEED_RESET_SETTINGS, true);
}
diff --git a/src/com/android/settings/accounts/AccountDashboardFragment.java b/src/com/android/settings/accounts/AccountDashboardFragment.java
index 3e83d6f..e252688 100644
--- a/src/com/android/settings/accounts/AccountDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountDashboardFragment.java
@@ -22,11 +22,14 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.credentials.CredentialManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.SearchIndexableResource;
import com.android.settings.R;
import com.android.settings.applications.autofill.PasswordsPreferenceController;
+import com.android.settings.applications.credentials.CredentialManagerPreferenceController;
import com.android.settings.applications.defaultapps.DefaultAutofillPreferenceController;
import com.android.settings.applications.defaultapps.DefaultWorkAutofillPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
@@ -44,10 +47,8 @@
@SearchIndexable
public class AccountDashboardFragment extends DashboardFragment {
-
private static final String TAG = "AccountDashboardFrag";
-
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCOUNT;
@@ -60,7 +61,7 @@
@Override
protected int getPreferenceScreenResId() {
- return R.xml.accounts_dashboard_settings;
+ return getPreferenceLayoutResId(this.getContext());
}
@Override
@@ -71,6 +72,12 @@
@Override
public void onAttach(Context context) {
super.onAttach(context);
+ if (CredentialManager.isServiceEnabled(context)) {
+ CredentialManagerPreferenceController cmpp =
+ use(CredentialManagerPreferenceController.class);
+ cmpp.setParentFragment(this);
+ }
+
getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
}
@@ -95,11 +102,13 @@
}
private static void buildAccountPreferenceControllers(
- Context context, DashboardFragment parent, String[] authorities,
+ Context context,
+ DashboardFragment parent,
+ String[] authorities,
List<AbstractPreferenceController> controllers) {
final AccountPreferenceController accountPrefController =
- new AccountPreferenceController(context, parent, authorities,
- ProfileSelectFragment.ProfileType.ALL);
+ new AccountPreferenceController(
+ context, parent, authorities, ProfileSelectFragment.ProfileType.ALL);
if (parent != null) {
parent.getSettingsLifecycle().addObserver(accountPrefController);
}
@@ -109,8 +118,21 @@
controllers.add(new AutoSyncWorkDataPreferenceController(context, parent));
}
+ private static int getPreferenceLayoutResId(Context context) {
+ return (context != null && CredentialManager.isServiceEnabled(context))
+ ? R.xml.accounts_dashboard_settings_credman
+ : R.xml.accounts_dashboard_settings;
+ }
+
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.xml.accounts_dashboard_settings) {
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
+ boolean enabled) {
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = getPreferenceLayoutResId(context);
+ return List.of(sir);
+ }
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
@@ -124,11 +146,11 @@
@SuppressWarnings("MissingSuperCall") // TODO: Fix me
@Override
- public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context,
- boolean enabled) {
+ public List<SearchIndexableRaw> getDynamicRawDataToIndex(
+ Context context, boolean enabled) {
final List<SearchIndexableRaw> indexRaws = new ArrayList<>();
- final UserManager userManager = (UserManager) context.getSystemService(
- Context.USER_SERVICE);
+ final UserManager userManager =
+ (UserManager) context.getSystemService(Context.USER_SERVICE);
final List<UserInfo> profiles = userManager.getProfiles(UserHandle.myUserId());
for (final UserInfo userInfo : profiles) {
if (userInfo.isManagedProfile()) {
diff --git a/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java b/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java
index 4661c64..5f9a288 100644
--- a/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java
@@ -22,9 +22,11 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
+import android.credentials.CredentialManager;
import com.android.settings.R;
import com.android.settings.applications.autofill.PasswordsPreferenceController;
+import com.android.settings.applications.credentials.CredentialManagerPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.users.AutoSyncDataPreferenceController;
@@ -34,11 +36,8 @@
import java.util.ArrayList;
import java.util.List;
-/**
- * Account Setting page for personal profile.
- */
+/** Account Setting page for personal profile. */
public class AccountPersonalDashboardFragment extends DashboardFragment {
-
private static final String TAG = "AccountPersonalFrag";
@Override
@@ -53,6 +52,9 @@
@Override
protected int getPreferenceScreenResId() {
+ if (this.getContext() != null && CredentialManager.isServiceEnabled(this.getContext())) {
+ return R.xml.accounts_personal_dashboard_settings_credman;
+ }
return R.xml.accounts_personal_dashboard_settings;
}
@@ -64,6 +66,12 @@
@Override
public void onAttach(Context context) {
super.onAttach(context);
+ if (CredentialManager.isServiceEnabled(context)) {
+ CredentialManagerPreferenceController cmpp =
+ use(CredentialManagerPreferenceController.class);
+ cmpp.setParentFragment(this);
+ }
+
getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
}
@@ -77,11 +85,13 @@
}
private static void buildAccountPreferenceControllers(
- Context context, DashboardFragment parent, String[] authorities,
+ Context context,
+ DashboardFragment parent,
+ String[] authorities,
List<AbstractPreferenceController> controllers) {
final AccountPreferenceController accountPrefController =
- new AccountPreferenceController(context, parent, authorities,
- ProfileSelectFragment.ProfileType.PERSONAL);
+ new AccountPreferenceController(
+ context, parent, authorities, ProfileSelectFragment.ProfileType.PERSONAL);
if (parent != null) {
parent.getSettingsLifecycle().addObserver(accountPrefController);
}
@@ -91,15 +101,15 @@
}
// TODO: b/141601408. After featureFlag settings_work_profile is launched, unmark this
-// public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-// new BaseSearchIndexProvider(R.xml.accounts_personal_dashboard_settings) {
-//
-// @Override
-// public List<AbstractPreferenceController> createPreferenceControllers(
-// Context context) {
-// ..Add autofill here too..
-// return buildPreferenceControllers(
-// context, null /* parent */, null /* authorities*/);
-// }
-// };
+ // public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ // new BaseSearchIndexProvider(R.xml.accounts_personal_dashboard_settings) {
+ //
+ // @Override
+ // public List<AbstractPreferenceController> createPreferenceControllers(
+ // Context context) {
+ // ..Add autofill here too..
+ // return buildPreferenceControllers(
+ // context, null /* parent */, null /* authorities*/);
+ // }
+ // };
}
diff --git a/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java b/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java
index f64e041..4835b17 100644
--- a/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java
@@ -22,9 +22,11 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
+import android.credentials.CredentialManager;
import com.android.settings.R;
import com.android.settings.applications.autofill.PasswordsPreferenceController;
+import com.android.settings.applications.credentials.CredentialManagerPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.users.AutoSyncDataPreferenceController;
@@ -34,11 +36,8 @@
import java.util.ArrayList;
import java.util.List;
-/**
- * Account Setting page for work profile.
- */
+/** Account Setting page for work profile. */
public class AccountWorkProfileDashboardFragment extends DashboardFragment {
-
private static final String TAG = "AccountWorkProfileFrag";
@Override
@@ -53,6 +52,9 @@
@Override
protected int getPreferenceScreenResId() {
+ if (this.getContext() != null && CredentialManager.isServiceEnabled(this.getContext())) {
+ return R.xml.accounts_work_dashboard_settings_credman;
+ }
return R.xml.accounts_work_dashboard_settings;
}
@@ -64,6 +66,12 @@
@Override
public void onAttach(Context context) {
super.onAttach(context);
+ if (CredentialManager.isServiceEnabled(context)) {
+ CredentialManagerPreferenceController cmpp =
+ use(CredentialManagerPreferenceController.class);
+ cmpp.setParentFragment(this);
+ }
+
getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
}
@@ -77,11 +85,13 @@
}
private static void buildAccountPreferenceControllers(
- Context context, DashboardFragment parent, String[] authorities,
+ Context context,
+ DashboardFragment parent,
+ String[] authorities,
List<AbstractPreferenceController> controllers) {
final AccountPreferenceController accountPrefController =
- new AccountPreferenceController(context, parent, authorities,
- ProfileSelectFragment.ProfileType.WORK);
+ new AccountPreferenceController(
+ context, parent, authorities, ProfileSelectFragment.ProfileType.WORK);
if (parent != null) {
parent.getSettingsLifecycle().addObserver(accountPrefController);
}
@@ -91,15 +101,15 @@
}
// TODO: b/141601408. After featureFlag settings_work_profile is launched, unmark this
-// public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-// new BaseSearchIndexProvider(R.xml.accounts_work_dashboard_settings) {
-//
-// @Override
-// public List<AbstractPreferenceController> createPreferenceControllers(
-// Context context) {
-// ..Add autofill here too..
-// return buildPreferenceControllers(
-// context, null /* parent */, null /* authorities*/);
-// }
-// };
+ // public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ // new BaseSearchIndexProvider(R.xml.accounts_work_dashboard_settings) {
+ //
+ // @Override
+ // public List<AbstractPreferenceController> createPreferenceControllers(
+ // Context context) {
+ // ..Add autofill here too..
+ // return buildPreferenceControllers(
+ // context, null /* parent */, null /* authorities*/);
+ // }
+ // };
}
diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java
index 5a1d11b..807f043 100644
--- a/src/com/android/settings/applications/AppStorageSettings.java
+++ b/src/com/android/settings/applications/AppStorageSettings.java
@@ -58,6 +58,7 @@
import com.android.settingslib.applications.ApplicationsState.Callbacks;
import com.android.settingslib.applications.StorageStatsSource;
import com.android.settingslib.applications.StorageStatsSource.AppStorageStats;
+import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.ActionButtonsPreference;
import com.android.settingslib.widget.LayoutPreference;
@@ -438,9 +439,8 @@
int numberResources = entry.getValue().value;
Preference pref = new Preference(getPrefContext());
pref.setTitle(entry.getKey());
- pref.setSummary(getPrefContext().getResources()
- .getQuantityString(R.plurals.uri_permissions_text, numberResources,
- numberResources));
+ pref.setSummary(StringUtil.getIcuPluralsString(mUri.getContext(), numberResources,
+ R.string.uri_permissions_text));
pref.setSelectable(false);
pref.setLayoutResource(R.layout.horizontal_preference);
pref.setOrder(order);
diff --git a/src/com/android/settings/applications/AppsPreferenceController.java b/src/com/android/settings/applications/AppsPreferenceController.java
index 922ba3c..37ec0bb 100644
--- a/src/com/android/settings/applications/AppsPreferenceController.java
+++ b/src/com/android/settings/applications/AppsPreferenceController.java
@@ -142,9 +142,8 @@
@Override
protected void onCountComplete(int num) {
if (!mRecentApps.isEmpty()) {
- mSeeAllPref.setTitle(
- mContext.getResources().getQuantityString(R.plurals.see_all_apps_title,
- num, num));
+ mSeeAllPref.setTitle(StringUtil.getIcuPluralsString(mContext, num,
+ R.string.see_all_apps_title));
} else {
mAllAppsInfoPref.setSummary(mContext.getString(R.string.apps_summary, num));
}
diff --git a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
index a130bb2..03a551f 100644
--- a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
+++ b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
@@ -55,6 +55,7 @@
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.AppPreference;
import java.lang.ref.WeakReference;
@@ -153,9 +154,8 @@
passwordCount.observe(
mLifecycleOwner, count -> {
// TODO(b/169455298): Validate the result.
- final CharSequence summary =
- mContext.getResources().getQuantityString(
- R.plurals.autofill_passwords_count, count, count);
+ final CharSequence summary = StringUtil.getIcuPluralsString(mContext, count,
+ R.string.autofill_passwords_count);
pref.setSummary(summary);
});
// TODO(b/169455298): Limit the number of concurrent queries.
diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
new file mode 100644
index 0000000..8150604
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.credentials;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.credentials.CredentialManager;
+import android.credentials.ListEnabledProvidersException;
+import android.credentials.ListEnabledProvidersResponse;
+import android.credentials.SetEnabledProvidersException;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.UserHandle;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.dashboard.DashboardFragment;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/** Queries available credential manager providers and adds preferences for them. */
+public class CredentialManagerPreferenceController extends BasePreferenceController
+ implements LifecycleObserver {
+ private static final String TAG = "CredentialManagerPreferenceController";
+ private static final int MAX_SELECTABLE_PROVIDERS = 5;
+
+ private final PackageManager mPm;
+ private final IconDrawableFactory mIconFactory;
+ private final List<ServiceInfo> mServices;
+ private final Set<String> mEnabledPackageNames;
+ private final @Nullable CredentialManager mCredentialManager;
+ private final CancellationSignal mCancellationSignal = new CancellationSignal();
+ private final Executor mExecutor;
+
+ private @Nullable DashboardFragment mParentFragment = null;
+
+ public CredentialManagerPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mPm = context.getPackageManager();
+ mIconFactory = IconDrawableFactory.newInstance(mContext);
+ mServices = new ArrayList<>();
+ mEnabledPackageNames = new HashSet<>();
+ mExecutor = ContextCompat.getMainExecutor(mContext);
+ mCredentialManager =
+ getCredentialManager(context, preferenceKey.equals("credentials_test"));
+ }
+
+ private @Nullable CredentialManager getCredentialManager(Context context, boolean isTest) {
+ if (isTest) {
+ return null;
+ }
+
+ Object service = context.getSystemService(Context.CREDENTIAL_SERVICE);
+
+ if (service != null && CredentialManager.isServiceEnabled(context)) {
+ return (CredentialManager) service;
+ }
+
+ return null;
+ }
+
+ @VisibleForTesting
+ public boolean isConnected() {
+ return mCredentialManager != null;
+ }
+
+ /**
+ * Sets the parent fragment and attaches this controller to the settings lifecycle.
+ *
+ * @param fragment the fragment to use as the parent
+ */
+ public void setParentFragment(DashboardFragment fragment) {
+ mParentFragment = fragment;
+ fragment.getSettingsLifecycle().addObserver(this);
+ }
+
+ @OnLifecycleEvent(ON_CREATE)
+ void onCreate(LifecycleOwner lifecycleOwner) {
+ if (mCredentialManager == null) {
+ return;
+ }
+
+ mCredentialManager.listEnabledProviders(
+ mCancellationSignal,
+ mExecutor,
+ new OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>() {
+ @Override
+ public void onResult(ListEnabledProvidersResponse result) {
+ Set<String> enabledPackages = new HashSet<>();
+ for (String flattenedComponentName : result.getProviderComponentNames()) {
+ ComponentName cn =
+ ComponentName.unflattenFromString(flattenedComponentName);
+ if (cn != null) {
+ enabledPackages.add(cn.getPackageName());
+ }
+ }
+
+ List<ServiceInfo> services = new ArrayList<>();
+ for (CredentialProviderInfo cpi :
+ CredentialProviderInfo.getAvailableServices(mContext, getUser())) {
+ services.add(cpi.getServiceInfo());
+ }
+
+ init(lifecycleOwner, services, enabledPackages);
+ }
+
+ @Override
+ public void onError(ListEnabledProvidersException e) {
+ Log.e(TAG, "listEnabledProviders error: " + e.toString());
+ }
+ });
+ }
+
+ @VisibleForTesting
+ void init(
+ LifecycleOwner lifecycleOwner,
+ List<ServiceInfo> availableServices,
+ Set<String> enabledPackages) {
+ mServices.clear();
+ mServices.addAll(availableServices);
+
+ mEnabledPackageNames.clear();
+ mEnabledPackageNames.addAll(enabledPackages);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mServices.isEmpty() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ PreferenceGroup group = screen.findPreference(getPreferenceKey());
+ Context context = screen.getContext();
+
+ for (ServiceInfo serviceInfo : mServices) {
+ CharSequence title = "";
+ if (serviceInfo.nonLocalizedLabel != null) {
+ title = serviceInfo.loadLabel(mPm);
+ }
+
+ group.addPreference(
+ addProviderPreference(
+ context,
+ title,
+ mIconFactory.getBadgedIcon(
+ serviceInfo, serviceInfo.applicationInfo, getUser()),
+ serviceInfo.packageName));
+ }
+ }
+
+ /**
+ * Enables the package name as an enabled credential manager provider.
+ *
+ * @param packageName the package name to enable
+ */
+ @VisibleForTesting
+ public boolean togglePackageNameEnabled(String packageName) {
+ if (mEnabledPackageNames.size() >= MAX_SELECTABLE_PROVIDERS) {
+ return false;
+ } else {
+ mEnabledPackageNames.add(packageName);
+ commitEnabledPackages();
+ return true;
+ }
+ }
+
+ /**
+ * Disables the package name as a credential manager provider.
+ *
+ * @param packageName the package name to disable
+ */
+ @VisibleForTesting
+ public void togglePackageNameDisabled(String packageName) {
+ mEnabledPackageNames.remove(packageName);
+ commitEnabledPackages();
+ }
+
+ /** Returns the enabled credential manager provider package names. */
+ @VisibleForTesting
+ public Set<String> getEnabledProviders() {
+ return mEnabledPackageNames;
+ }
+
+ /**
+ * Returns the enabled credential manager provider flattened component names that can be stored
+ * in the setting.
+ */
+ @VisibleForTesting
+ public List<String> getEnabledSettings() {
+ // Get all the component names that match the enabled package names.
+ List<String> enabledServices = new ArrayList<>();
+ for (ServiceInfo service : mServices) {
+ if (mEnabledPackageNames.contains(service.packageName)) {
+ enabledServices.add(service.getComponentName().flattenToString());
+ }
+ }
+
+ return enabledServices;
+ }
+
+ private SwitchPreference addProviderPreference(
+ @NonNull Context prefContext,
+ @NonNull CharSequence title,
+ @Nullable Drawable icon,
+ @NonNull String packageName) {
+ final SwitchPreference pref = new SwitchPreference(prefContext);
+ pref.setTitle(title);
+ pref.setChecked(mEnabledPackageNames.contains(packageName));
+
+ if (icon != null) {
+ pref.setIcon(Utils.getSafeIcon(icon));
+ }
+
+ pref.setOnPreferenceClickListener(
+ p -> {
+ boolean isChecked = pref.isChecked();
+
+ if (isChecked) {
+ // Show the error if too many enabled.
+ if (!togglePackageNameEnabled(packageName)) {
+ final DialogFragment fragment = newErrorDialogFragment();
+
+ if (fragment == null || mParentFragment == null) {
+ return true;
+ }
+
+ fragment.show(
+ mParentFragment.getActivity().getSupportFragmentManager(),
+ ErrorDialogFragment.TAG);
+
+ // The user set the check to true so we need to set it back.
+ pref.setChecked(false);
+ }
+
+ return true;
+ } else {
+ // Show the confirm disable dialog.
+ final DialogFragment fragment =
+ newConfirmationDialogFragment(packageName, title, pref);
+
+ if (fragment == null || mParentFragment == null) {
+ return true;
+ }
+
+ fragment.show(
+ mParentFragment.getActivity().getSupportFragmentManager(),
+ ConfirmationDialogFragment.TAG);
+ }
+
+ return true;
+ });
+
+ return pref;
+ }
+
+ private void commitEnabledPackages() {
+ // Commit using the CredMan API.
+ if (mCredentialManager == null) {
+ return;
+ }
+
+ List<String> enabledServices = getEnabledSettings();
+ mCredentialManager.setEnabledProviders(
+ enabledServices,
+ getUser(),
+ mExecutor,
+ new OutcomeReceiver<Void, SetEnabledProvidersException>() {
+ @Override
+ public void onResult(Void result) {
+ Log.i(TAG, "setEnabledProviders success");
+ }
+
+ @Override
+ public void onError(SetEnabledProvidersException e) {
+ Log.e(TAG, "setEnabledProviders error: " + e.toString());
+ }
+ });
+ }
+
+ private @Nullable ConfirmationDialogFragment newConfirmationDialogFragment(
+ @NonNull String packageName,
+ @NonNull CharSequence appName,
+ @NonNull SwitchPreference pref) {
+ DialogHost host =
+ new DialogHost() {
+ @Override
+ public DashboardFragment getParentFragment() {
+ return mParentFragment;
+ }
+
+ @Override
+ public void onDialogClick(int whichButton) {
+ if (whichButton == DialogInterface.BUTTON_POSITIVE) {
+ // Since the package is now enabled then we
+ // should remove it from the enabled list.
+ togglePackageNameDisabled(packageName);
+ } else if (whichButton == DialogInterface.BUTTON_NEGATIVE) {
+ // Set the checked back to true because we
+ // backed out of turning this off.
+ pref.setChecked(true);
+ }
+ }
+ };
+
+ if (host.getParentFragment() == null) {
+ return null;
+ }
+
+ return new ConfirmationDialogFragment(host, packageName, appName);
+ }
+
+ private @Nullable ErrorDialogFragment newErrorDialogFragment() {
+ DialogHost host =
+ new DialogHost() {
+ @Override
+ public DashboardFragment getParentFragment() {
+ return mParentFragment;
+ }
+
+ @Override
+ public void onDialogClick(int whichButton) {}
+ };
+
+ if (host.getParentFragment() == null) {
+ return null;
+ }
+
+ return new ErrorDialogFragment(host);
+ }
+
+ private int getUser() {
+ UserHandle workUser = getWorkProfileUser();
+ return workUser != null ? workUser.getIdentifier() : UserHandle.myUserId();
+ }
+
+ /** Called when the dialog button is clicked. */
+ private interface DialogHost {
+ void onDialogClick(int whichButton);
+
+ DashboardFragment getParentFragment();
+ }
+
+ /** Dialog fragment parent class. */
+ private abstract static class CredentialManagerDialogFragment extends InstrumentedDialogFragment
+ implements DialogInterface.OnClickListener {
+
+ public static final String TAG = "CredentialManagerDialogFragment";
+ public static final String PACKAGE_NAME_KEY = "package_name";
+ public static final String APP_NAME_KEY = "app_name";
+
+ private DialogHost mDialogHost;
+
+ CredentialManagerDialogFragment(DialogHost dialogHost) {
+ super();
+ setTargetFragment(dialogHost.getParentFragment(), 0);
+ mDialogHost = dialogHost;
+ }
+
+ public DialogHost getDialogHost() {
+ return mDialogHost;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.ACCOUNT;
+ }
+ }
+
+ /** Dialog showing error when too many providers are selected. */
+ private static class ErrorDialogFragment extends CredentialManagerDialogFragment {
+
+ ErrorDialogFragment(DialogHost dialogHost) {
+ super(dialogHost);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(getContext().getString(R.string.credman_error_message_title))
+ .setMessage(getContext().getString(R.string.credman_error_message))
+ .setPositiveButton(android.R.string.ok, this)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {}
+ }
+
+ /**
+ * Confirmation dialog fragment shows a dialog to the user to confirm that they are disabling a
+ * provider.
+ */
+ private static class ConfirmationDialogFragment extends CredentialManagerDialogFragment {
+
+ ConfirmationDialogFragment(
+ DialogHost dialogHost, @NonNull String packageName, @NonNull CharSequence appName) {
+ super(dialogHost);
+
+ final Bundle argument = new Bundle();
+ argument.putString(PACKAGE_NAME_KEY, packageName);
+ argument.putCharSequence(APP_NAME_KEY, appName);
+ setArguments(argument);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Bundle bundle = getArguments();
+ final String title =
+ getContext()
+ .getString(
+ R.string.credman_confirmation_message_title,
+ bundle.getCharSequence(
+ CredentialManagerDialogFragment.APP_NAME_KEY));
+
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(title)
+ .setMessage(getContext().getString(R.string.credman_confirmation_message))
+ .setPositiveButton(R.string.credman_confirmation_message_positive_button, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getDialogHost().onDialogClick(which);
+ }
+ }
+}
diff --git a/src/com/android/settings/communal/CommunalDashboardFragment.java b/src/com/android/settings/communal/CommunalDashboardFragment.java
new file mode 100644
index 0000000..64f87b5
--- /dev/null
+++ b/src/com/android/settings/communal/CommunalDashboardFragment.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.communal;
+
+import android.app.settings.SettingsEnums;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+
+/**
+ * Dashboard fragment for the top-level Communal settings.
+ */
+public class CommunalDashboardFragment extends DashboardFragment {
+ private static final String TAG = "CommunalFragment";
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.COMMUNAL_MODE_SETTINGS;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.communal_settings;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+}
diff --git a/src/com/android/settings/communal/CommunalPreferenceController.java b/src/com/android/settings/communal/CommunalPreferenceController.java
new file mode 100644
index 0000000..e16dcc3
--- /dev/null
+++ b/src/com/android/settings/communal/CommunalPreferenceController.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.communal;
+
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * Controls the top-level Communal settings preference.
+ */
+public class CommunalPreferenceController extends BasePreferenceController {
+ public CommunalPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mContext.getResources().getBoolean(R.bool.config_show_communal_settings)
+ ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
index 61195c9..0d51ebe 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
@@ -17,6 +17,9 @@
import android.content.Context;
import android.content.pm.PackageManager;
+import android.hardware.input.InputManager;
+import android.util.FeatureFlagUtils;
+import android.view.InputDevice;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -26,6 +29,7 @@
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
import com.android.settings.connecteddevice.dock.DockUpdater;
+import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
@@ -51,11 +55,14 @@
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
private DockUpdater mConnectedDockUpdater;
+ private StylusDeviceUpdater mStylusDeviceUpdater;
private final PackageManager mPackageManager;
+ private final InputManager mInputManager;
public ConnectedDeviceGroupController(Context context) {
super(context, KEY);
mPackageManager = context.getPackageManager();
+ mInputManager = context.getSystemService(InputManager.class);
}
@Override
@@ -69,7 +76,13 @@
mConnectedUsbDeviceUpdater.registerCallback();
}
- mConnectedDockUpdater.registerCallback();
+ if (mConnectedDockUpdater != null) {
+ mConnectedDockUpdater.registerCallback();
+ }
+
+ if (mStylusDeviceUpdater != null) {
+ mStylusDeviceUpdater.registerCallback();
+ }
}
@Override
@@ -82,7 +95,13 @@
mConnectedUsbDeviceUpdater.unregisterCallback();
}
- mConnectedDockUpdater.unregisterCallback();
+ if (mConnectedDockUpdater != null) {
+ mConnectedDockUpdater.unregisterCallback();
+ }
+
+ if (mStylusDeviceUpdater != null) {
+ mStylusDeviceUpdater.unregisterCallback();
+ }
}
@Override
@@ -103,8 +122,15 @@
mConnectedUsbDeviceUpdater.initUsbPreference(context);
}
- mConnectedDockUpdater.setPreferenceContext(context);
- mConnectedDockUpdater.forceUpdate();
+ if (mConnectedDockUpdater != null) {
+ mConnectedDockUpdater.setPreferenceContext(context);
+ mConnectedDockUpdater.forceUpdate();
+ }
+
+ if (mStylusDeviceUpdater != null) {
+ mStylusDeviceUpdater.setPreferenceContext(context);
+ mStylusDeviceUpdater.forceUpdate();
+ }
}
}
@@ -112,6 +138,7 @@
public int getAvailabilityStatus() {
return (hasBluetoothFeature()
|| hasUsbFeature()
+ || hasUsiStylusFeature()
|| mConnectedDockUpdater != null)
? AVAILABLE_UNSEARCHABLE
: UNSUPPORTED_ON_DEVICE;
@@ -141,11 +168,13 @@
@VisibleForTesting
void init(BluetoothDeviceUpdater bluetoothDeviceUpdater,
ConnectedUsbDeviceUpdater connectedUsbDeviceUpdater,
- DockUpdater connectedDockUpdater) {
+ DockUpdater connectedDockUpdater,
+ StylusDeviceUpdater connectedStylusDeviceUpdater) {
mBluetoothDeviceUpdater = bluetoothDeviceUpdater;
mConnectedUsbDeviceUpdater = connectedUsbDeviceUpdater;
mConnectedDockUpdater = connectedDockUpdater;
+ mStylusDeviceUpdater = connectedStylusDeviceUpdater;
}
public void init(DashboardFragment fragment) {
@@ -160,7 +189,10 @@
hasUsbFeature()
? new ConnectedUsbDeviceUpdater(context, fragment, this)
: null,
- connectedDockUpdater);
+ connectedDockUpdater,
+ hasUsiStylusFeature()
+ ? new StylusDeviceUpdater(context, fragment, this)
+ : null);
}
private boolean hasBluetoothFeature() {
@@ -171,4 +203,21 @@
return mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY)
|| mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST);
}
+
+ private boolean hasUsiStylusFeature() {
+ if (!FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES)) {
+ return false;
+ }
+
+ for (int deviceId : mInputManager.getInputDeviceIds()) {
+ InputDevice device = mInputManager.getInputDevice(deviceId);
+ if (device != null
+ && device.supportsSource(InputDevice.SOURCE_STYLUS)
+ && !device.isExternal()) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java b/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java
new file mode 100644
index 0000000..575c526
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.stylus;
+
+import android.content.Context;
+import android.hardware.BatteryState;
+import android.hardware.input.InputManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputDevice;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.connecteddevice.DevicePreferenceCallback;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controller to maintain available USI stylus devices. Listens to bluetooth
+ * stylus connection to determine whether to show the USI preference.
+ */
+public class StylusDeviceUpdater implements InputManager.InputDeviceListener,
+ InputManager.InputDeviceBatteryListener {
+
+ private static final String TAG = "StylusDeviceUpdater";
+ private static final String PREF_KEY = "stylus_usi_device";
+ private static final String INPUT_ID_ARG = "device_input_id";
+
+ private final DevicePreferenceCallback mDevicePreferenceCallback;
+ private final List<Integer> mRegisteredBatteryCallbackIds;
+ private final DashboardFragment mFragment;
+ private final InputManager mInputManager;
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
+
+ private long mLastUsiSeenTime = 0;
+ private Context mContext;
+
+ @VisibleForTesting
+ Integer mLastDetectedUsiId;
+
+ @VisibleForTesting
+ Preference mUsiPreference;
+
+
+ public StylusDeviceUpdater(Context context, DashboardFragment fragment,
+ DevicePreferenceCallback devicePreferenceCallback) {
+ mFragment = fragment;
+ mRegisteredBatteryCallbackIds = new ArrayList<>();
+ mDevicePreferenceCallback = devicePreferenceCallback;
+ mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+ mContext = context;
+ mInputManager = context.getSystemService(InputManager.class);
+
+ }
+
+ /**
+ * Register the stylus event callback and update the list
+ */
+ public void registerCallback() {
+ for (int deviceId : mInputManager.getInputDeviceIds()) {
+ onInputDeviceAdded(deviceId);
+ }
+ mInputManager.registerInputDeviceListener(this, new Handler(Looper.myLooper()));
+ forceUpdate();
+ }
+
+ /**
+ * Unregister the stylus event callback
+ */
+ public void unregisterCallback() {
+ for (int deviceId : mRegisteredBatteryCallbackIds) {
+ mInputManager.removeInputDeviceBatteryListener(deviceId, this);
+ }
+ mInputManager.unregisterInputDeviceListener(this);
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+ if (inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)
+ && !inputDevice.isExternal()) {
+ try {
+ mInputManager.addInputDeviceBatteryListener(deviceId,
+ mContext.getMainExecutor(), this);
+ mRegisteredBatteryCallbackIds.add(deviceId);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ }
+ forceUpdate();
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ Log.d(TAG, String.format("Input device removed %d", deviceId));
+ forceUpdate();
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ if (mInputManager.getInputDevice(deviceId).supportsSource(InputDevice.SOURCE_STYLUS)) {
+ forceUpdate();
+ }
+ }
+
+
+ @Override
+ public void onBatteryStateChanged(int deviceId, long eventTimeMillis,
+ @NonNull BatteryState batteryState) {
+ if (batteryState.isPresent()) {
+ mLastUsiSeenTime = eventTimeMillis;
+ mLastDetectedUsiId = deviceId;
+ } else {
+ mLastUsiSeenTime = -1;
+ mLastDetectedUsiId = null;
+ }
+ forceUpdate();
+ }
+
+ /**
+ * Set the context to generate the {@link Preference}, so it could get the correct theme.
+ */
+ public void setPreferenceContext(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Force update to add or remove stylus preference
+ */
+ public void forceUpdate() {
+ if (shouldShowUsiPreference()) {
+ addOrUpdateUsiPreference();
+ } else {
+ removeUsiPreference();
+ }
+ }
+
+ private synchronized void addOrUpdateUsiPreference() {
+ if (mUsiPreference == null) {
+ mUsiPreference = new Preference(mContext);
+ mDevicePreferenceCallback.onDeviceAdded(mUsiPreference);
+ }
+ mUsiPreference.setKey(PREF_KEY);
+ mUsiPreference.setTitle(R.string.stylus_connected_devices_title);
+ // TODO(b/250909304): pending actual icon visD
+ mUsiPreference.setIcon(R.drawable.circle);
+ mUsiPreference.setOnPreferenceClickListener((Preference p) -> {
+ mMetricsFeatureProvider.logClickedPreference(p, mFragment.getMetricsCategory());
+ launchDeviceDetails();
+ return true;
+ });
+ }
+
+ private synchronized void removeUsiPreference() {
+ if (mUsiPreference != null) {
+ mDevicePreferenceCallback.onDeviceRemoved(mUsiPreference);
+ mUsiPreference = null;
+ }
+ }
+
+ private boolean shouldShowUsiPreference() {
+ return isUsiConnectionValid() && !hasConnectedBluetoothStylusDevice();
+ }
+
+ @VisibleForTesting
+ public Preference getPreference() {
+ return mUsiPreference;
+ }
+
+ @VisibleForTesting
+ boolean hasConnectedBluetoothStylusDevice() {
+ for (int deviceId : mInputManager.getInputDeviceIds()) {
+ InputDevice device = mInputManager.getInputDevice(deviceId);
+ if (device.supportsSource(InputDevice.SOURCE_STYLUS)
+ && mInputManager.getInputDeviceBluetoothAddress(deviceId) != null) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @VisibleForTesting
+ boolean isUsiConnectionValid() {
+ // battery listener uses uptimeMillis as its eventTime
+ long currentTime = SystemClock.uptimeMillis();
+ long usiValidityDuration = 60 * 60 * 1000; // 1 hour
+ return mLastUsiSeenTime > 0 && currentTime - usiValidityDuration <= mLastUsiSeenTime;
+ }
+
+ private void launchDeviceDetails() {
+ final Bundle args = new Bundle();
+ args.putInt(INPUT_ID_ARG, mLastDetectedUsiId);
+
+ new SubSettingLauncher(mFragment.getContext())
+ .setDestination(StylusUsiDetailsFragment.class.getName())
+ .setArguments(args)
+ .setSourceMetricsCategory(mFragment.getMetricsCategory()).launch();
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java b/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java
index 826cc1f..13d7b57 100644
--- a/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java
+++ b/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java
@@ -66,7 +66,7 @@
mHeaderPreference = screen.findPreference(getPreferenceKey());
View view = mHeaderPreference.findViewById(R.id.entity_header);
TextView titleView = view.findViewById(R.id.entity_header_title);
- titleView.setText(R.string.stylus_usi_header_title);
+ titleView.setText(R.string.stylus_connected_devices_title);
ImageView iconView = mHeaderPreference.findViewById(R.id.entity_header_icon);
if (iconView != null) {
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index da81f6e..2243f57 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -23,7 +23,6 @@
import com.android.settings.MainClearConfirm;
import com.android.settings.Settings;
import com.android.settings.TestingSettings;
-import com.android.settings.TetherSettings;
import com.android.settings.TrustedCredentialsSettings;
import com.android.settings.accessibility.AccessibilityDetailsSettingsFragment;
import com.android.settings.accessibility.AccessibilitySettings;
@@ -83,6 +82,7 @@
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.connecteddevice.NfcAndPaymentFragment;
import com.android.settings.connecteddevice.PreviouslyConnectedDeviceDashboardFragment;
+import com.android.settings.connecteddevice.stylus.StylusUsiDetailsFragment;
import com.android.settings.connecteddevice.usb.UsbDetailsFragment;
import com.android.settings.datausage.DataSaverSummary;
import com.android.settings.datausage.DataUsageList;
@@ -120,9 +120,11 @@
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
import com.android.settings.inputmethod.KeyboardSettings;
+import com.android.settings.inputmethod.ModifierKeysSettings;
import com.android.settings.inputmethod.NewKeyboardLayoutEnabledLocalesFragment;
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
import com.android.settings.inputmethod.SpellCheckersSettings;
+import com.android.settings.inputmethod.TrackpadSettings;
import com.android.settings.inputmethod.UserDictionaryList;
import com.android.settings.inputmethod.UserDictionarySettings;
import com.android.settings.language.LanguageAndInputSettings;
@@ -138,6 +140,7 @@
import com.android.settings.network.apn.ApnSettings;
import com.android.settings.network.telephony.MobileNetworkSettings;
import com.android.settings.network.telephony.NetworkSelectSettings;
+import com.android.settings.network.tether.TetherSettings;
import com.android.settings.nfc.AndroidBeam;
import com.android.settings.nfc.PaymentSettings;
import com.android.settings.notification.ConfigureNotificationSettings;
@@ -215,7 +218,9 @@
LanguageAndInputSettings.class.getName(),
LanguageSettings.class.getName(),
KeyboardSettings.class.getName(),
+ ModifierKeysSettings.class.getName(),
NewKeyboardLayoutEnabledLocalesFragment.class.getName(),
+ TrackpadSettings.class.getName(),
SpellCheckersSettings.class.getName(),
UserDictionaryList.class.getName(),
UserDictionarySettings.class.getName(),
@@ -336,6 +341,7 @@
BluetoothDeviceDetailsFragment.class.getName(),
BluetoothBroadcastDialog.class.getName(),
BluetoothFindBroadcastsFragment.class.getName(),
+ StylusUsiDetailsFragment.class.getName(),
DataUsageList.class.getName(),
ToggleBackupSettingFragment.class.getName(),
PreviouslyConnectedDeviceDashboardFragment.class.getName(),
diff --git a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
index a2b1454..565a3e0 100644
--- a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
+++ b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
@@ -23,6 +23,7 @@
import com.android.settings.accounts.AccountDashboardFragment;
import com.android.settings.accounts.AccountDetailDashboardFragment;
import com.android.settings.applications.AppDashboardFragment;
+import com.android.settings.communal.CommunalDashboardFragment;
import com.android.settings.connecteddevice.AdvancedConnectedDeviceDashboardFragment;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.development.DevelopmentSettingsDashboardFragment;
@@ -126,6 +127,8 @@
CategoryKey.CATEGORY_BATTERY_SAVER_SETTINGS);
PARENT_TO_CATEGORY_KEY_MAP.put(SmartBatterySettings.class.getName(),
CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS);
+ PARENT_TO_CATEGORY_KEY_MAP.put(CommunalDashboardFragment.class.getName(),
+ CategoryKey.CATEGORY_COMMUNAL_SETTINGS);
CATEGORY_KEY_TO_PARENT_MAP = new ArrayMap<>(PARENT_TO_CATEGORY_KEY_MAP.size());
diff --git a/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java b/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java
index 98629b4..a4d745b 100644
--- a/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java
+++ b/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java
@@ -29,8 +29,10 @@
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
+import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -99,6 +101,25 @@
return result;
}
+ @Override
+ public CharSequence getSummary() {
+ // If auto time zone cannot enable telephony fallback and is capable of location, then auto
+ // time zone must use location.
+ if (LocationProviderStatusPreferenceController.hasLocationTimeZoneNoTelephonyFallback(
+ mTimeManager.getTimeZoneCapabilitiesAndConfig().getDetectorStatus())) {
+ return mContext.getResources().getString(R.string.auto_zone_requires_location_summary);
+ }
+ // If the user has a dedicated toggle to control location use, the summary can
+ // be empty because the use of location is explicit.
+ return "";
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ refreshSummary(screen.findPreference(getPreferenceKey()));
+ }
+
@VisibleForTesting
boolean isEnabled() {
TimeZoneConfiguration config = getTimeZoneCapabilitiesAndConfig().getConfiguration();
diff --git a/src/com/android/settings/datetime/LocationProviderStatusPreferenceController.java b/src/com/android/settings/datetime/LocationProviderStatusPreferenceController.java
new file mode 100644
index 0000000..9380f13
--- /dev/null
+++ b/src/com/android/settings/datetime/LocationProviderStatusPreferenceController.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.datetime;
+
+import android.app.time.DetectorStatusTypes;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
+import android.app.time.TimeManager;
+import android.app.time.TimeZoneDetectorStatus;
+import android.content.Context;
+import android.location.LocationManager;
+import android.service.timezone.TimeZoneProviderStatus;
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.location.LocationSettings;
+import com.android.settingslib.widget.BannerMessagePreference;
+
+import java.util.concurrent.Executor;
+
+/**
+ * The controller for the "location time zone detection" entry in the Location settings
+ * screen.
+ */
+public class LocationProviderStatusPreferenceController
+ extends BasePreferenceController implements TimeManager.TimeZoneDetectorListener {
+ private final TimeManager mTimeManager;
+ private final LocationManager mLocationManager;
+
+ private BannerMessagePreference mPreference = null;
+
+ public LocationProviderStatusPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mTimeManager = context.getSystemService(TimeManager.class);
+ mLocationManager = context.getSystemService(LocationManager.class);
+
+ Executor mainExecutor = context.getMainExecutor();
+ mTimeManager.addTimeZoneDetectorListener(mainExecutor, this);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ assert mPreference != null;
+ mPreference
+ .setPositiveButtonText(
+ R.string.location_time_zone_provider_fix_dialog_ok_button)
+ .setPositiveButtonOnClickListener(v -> launchLocationSettings());
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ // Checks that the summary is non-empty as most status strings are optional. If a status
+ // string is empty, we ignore the status.
+ if (!TextUtils.isEmpty(getSummary())) {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ private void launchLocationSettings() {
+ new SubSettingLauncher(mContext)
+ .setDestination(LocationSettings.class.getName())
+ .setSourceMetricsCategory(getMetricsCategory())
+ .launch();
+ }
+
+ // Android has up to two location time zone providers (LTZPs) which can
+ // (optionally) report their status along several dimensions. Typically there is
+ // only one LTZP on a device, the primary. The UI here only reports status for one
+ // LTZP. This UI logic prioritizes the primary if there is a "bad" status for both.
+ @Nullable
+ private TimeZoneProviderStatus getLtzpStatus() {
+ LocationTimeZoneAlgorithmStatus status =
+ mTimeManager.getTimeZoneCapabilitiesAndConfig().getDetectorStatus()
+ .getLocationTimeZoneAlgorithmStatus();
+ TimeZoneProviderStatus primary = status.getPrimaryProviderReportedStatus();
+ TimeZoneProviderStatus secondary = status.getSecondaryProviderReportedStatus();
+ if (primary == null && secondary == null) {
+ return null;
+ }
+
+ if (primary == null) {
+ return secondary;
+ } else if (secondary == null) {
+ return primary;
+ }
+
+ if (status.getPrimaryProviderStatus()
+ != LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN) {
+ return secondary;
+ }
+
+ return primary;
+ }
+
+ @Override
+ public void onChange() {
+ if (mPreference != null) {
+ mPreference.setVisible(getAvailabilityStatus() == AVAILABLE_UNSEARCHABLE);
+ refreshSummary(mPreference);
+ }
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ boolean locationEnabled = mLocationManager.isLocationEnabled();
+ final TimeZoneDetectorStatus detectorStatus =
+ mTimeManager.getTimeZoneCapabilitiesAndConfig().getDetectorStatus();
+
+ if (!locationEnabled && hasLocationTimeZoneNoTelephonyFallback(detectorStatus)) {
+ return mContext.getResources().getString(
+ R.string.location_time_zone_detection_status_summary_blocked_by_settings);
+ }
+
+ TimeZoneProviderStatus ltzpStatus = getLtzpStatus();
+ if (ltzpStatus == null) {
+ return "";
+ }
+
+ int status = ltzpStatus.getLocationDetectionDependencyStatus();
+
+ if (status == TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT) {
+ return mContext.getResources().getString(
+ R.string.location_time_zone_detection_status_summary_blocked_by_environment);
+ }
+ if (status == TimeZoneProviderStatus.DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS) {
+ return mContext.getResources().getString(
+ R.string.location_time_zone_detection_status_summary_degraded_by_settings);
+ }
+ if (status == TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE) {
+ return mContext.getResources().getString(
+ R.string.location_time_zone_detection_status_summary_temporarily_unavailable);
+ }
+ if (status == TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS) {
+ return mContext.getResources().getString(
+ R.string.location_time_zone_detection_status_summary_blocked_by_settings);
+ }
+
+ return "";
+ }
+
+ /** package */
+ static boolean hasLocationTimeZoneNoTelephonyFallback(TimeZoneDetectorStatus detectorStatus) {
+ final LocationTimeZoneAlgorithmStatus locationStatus =
+ detectorStatus.getLocationTimeZoneAlgorithmStatus();
+ final TelephonyTimeZoneAlgorithmStatus telephonyStatus =
+ detectorStatus.getTelephonyTimeZoneAlgorithmStatus();
+ return telephonyStatus.getAlgorithmStatus()
+ == DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED
+ && locationStatus.getStatus()
+ != DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
index 6af58b5..55b38af 100644
--- a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
@@ -47,6 +47,7 @@
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
+import com.android.settingslib.utils.StringUtil;
import com.google.android.setupcompat.util.WizardManagerHelper;
public class BuildNumberPreferenceController extends BasePreferenceController implements
@@ -181,9 +182,8 @@
mDevHitToast.cancel();
}
mDevHitToast = Toast.makeText(mContext,
- mContext.getResources().getQuantityString(
- R.plurals.show_dev_countdown, mDevHitCountdown,
- mDevHitCountdown),
+ StringUtil.getIcuPluralsString(mContext, mDevHitCountdown,
+ R.string.show_dev_countdown),
Toast.LENGTH_SHORT);
mDevHitToast.show();
}
diff --git a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
index 691cbd6..991ad25 100644
--- a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
+++ b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
@@ -40,6 +40,8 @@
import com.android.settings.deviceinfo.UptimePreferenceController;
import com.android.settings.deviceinfo.WifiMacAddressPreferenceController;
import com.android.settings.deviceinfo.imei.ImeiInfoPreferenceController;
+import com.android.settings.deviceinfo.simstatus.EidStatus;
+import com.android.settings.deviceinfo.simstatus.SimEidPreferenceController;
import com.android.settings.deviceinfo.simstatus.SimStatusPreferenceController;
import com.android.settings.deviceinfo.simstatus.SlotSimStatus;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -59,6 +61,7 @@
implements DeviceNamePreferenceController.DeviceNamePreferenceHost {
private static final String LOG_TAG = "MyDeviceInfoFragment";
+ private static final String KEY_EID_INFO = "eid_info";
private static final String KEY_MY_DEVICE_INFO_HEADER = "my_device_info_header";
private BuildNumberPreferenceController mBuildNumberPreferenceController;
@@ -109,7 +112,9 @@
final ExecutorService executor = (fragment == null) ? null :
Executors.newSingleThreadExecutor();
- final SlotSimStatus slotSimStatus = new SlotSimStatus(context, executor);
+ androidx.lifecycle.Lifecycle lifecycleObject = (fragment == null) ? null :
+ fragment.getLifecycle();
+ final SlotSimStatus slotSimStatus = new SlotSimStatus(context, executor, lifecycleObject);
controllers.add(new IpAddressPreferenceController(context, lifecycle));
controllers.add(new WifiMacAddressPreferenceController(context, lifecycle));
@@ -128,6 +133,12 @@
slotRecord.init(fragment, slotSimStatus);
controllers.add(slotRecord);
}
+
+ EidStatus eidStatus = new EidStatus(slotSimStatus, context, executor);
+ SimEidPreferenceController simEid = new SimEidPreferenceController(context, KEY_EID_INFO);
+ simEid.init(slotSimStatus, eidStatus);
+ controllers.add(simEid);
+
if (executor != null) {
executor.shutdown();
}
diff --git a/src/com/android/settings/deviceinfo/simstatus/EidStatus.java b/src/com/android/settings/deviceinfo/simstatus/EidStatus.java
new file mode 100644
index 0000000..2f986a3
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/simstatus/EidStatus.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.simstatus;
+
+import android.content.Context;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
+import android.telephony.euicc.EuiccManager;
+import android.text.TextUtils;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Phaser;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.IntStream;
+
+/**
+ * A class for query EID.
+ */
+public class EidStatus {
+
+ private static final String TAG = "EidStatus";
+ private final SlotSimStatus mSlotSimStatus;
+ private final Phaser mBlocker = new Phaser(1);
+ private final AtomicReference<String> mEid = new AtomicReference<String>();
+
+ /**
+ * Construct of class.
+ * @param slotSimStatus SlotSimStatus
+ * @param context Context
+ */
+ public EidStatus(SlotSimStatus slotSimStatus, Context context) {
+ this(slotSimStatus, context, null);
+ }
+
+ /**
+ * Construct of class.
+ * @param slotSimStatus SlotSimStatus
+ * @param context Context
+ * @param executor executor for offload to thread
+ */
+ public EidStatus(SlotSimStatus slotSimStatus, Context context, Executor executor) {
+ mSlotSimStatus = slotSimStatus;
+
+ if (executor == null) {
+ getEidOperation(context);
+ } else {
+ executor.execute(() -> getEidOperation(context));
+ }
+ }
+
+ /**
+ * Get the EID
+ * @return EID string
+ */
+ public String getEid() {
+ mBlocker.awaitAdvance(0);
+ return mEid.get();
+ }
+
+ protected TelephonyManager getTelephonyManager(Context context) {
+ return context.getSystemService(TelephonyManager.class);
+ }
+
+ protected EuiccManager getEuiccManager(Context context) {
+ return context.getSystemService(EuiccManager.class);
+ }
+
+ protected String getDefaultEid(EuiccManager euiccMgr) {
+ if ((euiccMgr == null) || (!euiccMgr.isEnabled())) {
+ return null;
+ }
+ return euiccMgr.getEid();
+ }
+
+ protected void getEidOperation(Context context) {
+ EuiccManager euiccMgr = getEuiccManager(context);
+ String eid = getEidPerSlot(context, euiccMgr);
+ if (eid == null) {
+ eid = getDefaultEid(euiccMgr);
+ }
+ mEid.set(eid);
+ mBlocker.arrive();
+ }
+
+ protected String getEidPerSlot(Context context, EuiccManager euiccMgr) {
+ if (mSlotSimStatus.size() <= SimStatusDialogController.MAX_PHONE_COUNT_SINGLE_SIM) {
+ return null;
+ }
+
+ TelephonyManager telMgr = getTelephonyManager(context);
+ if (telMgr == null) {
+ return null;
+ }
+
+ List<UiccCardInfo> uiccCardInfoList = telMgr.getUiccCardsInfo();
+ if (uiccCardInfoList == null) {
+ return null;
+ }
+
+ // Collects all card ID from all eSIM(s) reported from SubscsriptionManager
+ final int [] cardIdList = IntStream.range(0, mSlotSimStatus.size())
+ .mapToObj(slotIdx -> mSlotSimStatus.getSubscriptionInfo(slotIdx))
+ .filter(Objects::nonNull)
+ .filter(SubscriptionInfo::isEmbedded)
+ .mapToInt(SubscriptionInfo::getCardId)
+ .sorted()
+ .distinct()
+ .toArray();
+ if (cardIdList.length == 0) {
+ return null;
+ }
+
+ /**
+ * Find EID from first slot which contains an eSIM and with card ID listed within
+ * the eSIM card ID provided by SubscsriptionManager.
+ */
+ return uiccCardInfoList.stream()
+ .filter(UiccCardInfo::isEuicc)
+ .filter(cardInfo -> {
+ int cardId = cardInfo.getCardId();
+ return Arrays.binarySearch(cardIdList, cardId) >= 0;
+ })
+ .map(cardInfo -> {
+ String eid = cardInfo.getEid();
+ if (TextUtils.isEmpty(eid)) {
+ eid = euiccMgr.createForCardId(cardInfo.getCardId()).getEid();
+ }
+ return eid;
+ })
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.java b/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.java
new file mode 100644
index 0000000..e0a8af6
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.simstatus;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.os.UserManager;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.deviceinfo.PhoneNumberUtil;
+import com.android.settings.network.SubscriptionUtil;
+import com.android.settings.network.telephony.TelephonyPreferenceDialog;
+import com.android.settingslib.Utils;
+import com.android.settingslib.qrcode.QrCodeGenerator;
+
+/**
+ * This is to show a preference regarding EID of SIM card.
+ */
+public class SimEidPreferenceController extends BasePreferenceController
+ implements DialogInterface.OnShowListener {
+
+ private static final String TAG = "SimEidPreferenceController";
+
+ private SlotSimStatus mSlotSimStatus;
+ private EidStatus mEidStatus;
+ private boolean mShowEidOnSummary;
+ private TelephonyPreferenceDialog mPreference;
+
+ /**
+ * Constructer.
+ * @param context Context
+ * @param preferenceKey is the key for Preference
+ */
+ public SimEidPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ /**
+ * Update status.
+ *
+ * @param slotSimStatus sim status per slot
+ * @param eidStatus status of EID
+ */
+ public void init(SlotSimStatus slotSimStatus, EidStatus eidStatus) {
+ mSlotSimStatus = slotSimStatus;
+ mEidStatus = eidStatus;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ if ((!SubscriptionUtil.isSimHardwareVisible(mContext)) || (mSlotSimStatus == null)) {
+ return;
+ }
+ TelephonyPreferenceDialog preference = (TelephonyPreferenceDialog)
+ screen.findPreference(getPreferenceKey());
+ if (preference == null) {
+ return;
+ }
+
+ preference.setTitle(getTitle());
+ mPreference = preference;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
+ TelephonyPreferenceDialog preferenceDialog = (TelephonyPreferenceDialog)preference;
+ preferenceDialog.setDialogTitle(getTitle());
+ preferenceDialog.setDialogMessage(mEidStatus.getEid());
+ preferenceDialog.setOnShowListener(this);
+ return true;
+ }
+ return super.handlePreferenceTreeClick(preference);
+ }
+
+ /**
+ * Construct title string.
+ * @return title string
+ */
+ @VisibleForTesting
+ protected CharSequence getTitle() {
+ int slotSize = (mSlotSimStatus == null) ? 0 : mSlotSimStatus.size();
+ if (slotSize <= SimStatusDialogController.MAX_PHONE_COUNT_SINGLE_SIM) {
+ return mContext.getString(R.string.status_eid);
+ }
+ // Only append slot index to title when more than 1 is available
+ for (int idxSlot = 0; idxSlot < slotSize; idxSlot++) {
+ SubscriptionInfo subInfo = mSlotSimStatus.getSubscriptionInfo(idxSlot);
+ if ((subInfo != null) && subInfo.isEmbedded()) {
+ return mContext.getString(R.string.eid_multi_sim, idxSlot+1);
+ }
+ }
+ return mContext.getString(R.string.status_eid);
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ String summary = mShowEidOnSummary ? mEidStatus.getEid() : null;
+ if (TextUtils.isEmpty(summary)) {
+ summary = mContext.getString(R.string.device_info_protected_single_press);
+ }
+ return summary;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (!SubscriptionUtil.isSimHardwareVisible(mContext)) {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ boolean isAvailable = SubscriptionUtil.isSimHardwareVisible(mContext) &&
+ mContext.getSystemService(UserManager.class).isAdminUser() &&
+ !Utils.isWifiOnly(mContext);
+ return isAvailable ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ /**
+ * Callback when dialog end of show().
+ */
+ public void onShow(DialogInterface dialog) {
+ Dialog dialogShwon = mPreference.getDialog();
+
+ dialogShwon.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.FLAG_SECURE);
+ dialogShwon.setCanceledOnTouchOutside(false);
+
+ TextView textView = dialogShwon.findViewById(R.id.esim_id_value);
+ textView.setText(PhoneNumberUtil.expandByTts(mEidStatus.getEid()));
+ textView.setTextIsSelectable(true);
+
+ ImageView qrCodeView = dialogShwon.findViewById(R.id.esim_id_qrcode);
+ qrCodeView.setImageBitmap(getEidQRcode(mEidStatus.getEid().toString(),
+ qrCodeView.getWidth()));
+
+ mShowEidOnSummary = true;
+ }
+
+ /**
+ * Get the QR code for EID
+ * @param eid is the EID string
+ * @param widthInPixel is the width of Bitmap in pixel
+ * @return a Bitmap of QR code
+ */
+ public Bitmap getEidQRcode(String eid, int widthInPixel) {
+ Bitmap qrCodeBitmap = null;
+ try {
+ qrCodeBitmap = QrCodeGenerator.encodeQrCode(eid, widthInPixel);
+ } catch (Exception exception) {
+ Log.w(TAG, "Error when creating QR code width " + widthInPixel, exception);
+ }
+ return qrCodeBitmap;
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java
index 16f04df..00819b5 100644
--- a/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java
@@ -17,12 +17,14 @@
package com.android.settings.deviceinfo.simstatus;
import android.content.Context;
+import android.os.UserManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
-import android.os.UserManager;
+import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
@@ -40,16 +42,12 @@
private static final String KEY_PREFERENCE_CATEGORY = "device_detail_category";
- private final SubscriptionManager mSubscriptionManager;
- private final List<Preference> mPreferenceList = new ArrayList<>();
-
private Fragment mFragment;
private SlotSimStatus mSlotSimStatus;
+ private Observer mSimChangeObserver;
public SimStatusPreferenceController(Context context, String prefKey) {
super(context, prefKey);
-
- mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
}
/**
@@ -103,26 +101,36 @@
// Add additional preferences for each sim in the device
for (int simSlotNumber = 0; simSlotNumber < mSlotSimStatus.size(); simSlotNumber++) {
final Preference multiSimPreference = createNewPreference(screen.getContext());
- multiSimPreference.setCopyingEnabled(true);
multiSimPreference.setOrder(mSlotSimStatus.getPreferenceOrdering(simSlotNumber));
multiSimPreference.setKey(mSlotSimStatus.getPreferenceKey(simSlotNumber));
category.addPreference(multiSimPreference);
- mPreferenceList.add(multiSimPreference);
}
}
@Override
public void updateState(Preference preference) {
- for (int simSlotNumber = 0; simSlotNumber < mPreferenceList.size(); simSlotNumber++) {
- final Preference simStatusPreference = mPreferenceList.get(simSlotNumber);
- simStatusPreference.setTitle(getPreferenceTitle(simSlotNumber /* sim slot */));
- simStatusPreference.setSummary(getCarrierName(simSlotNumber /* sim slot */));
+ final int simSlot = getSimSlotIndex();
+ if (mSimChangeObserver == null) {
+ mSimChangeObserver = x -> updateStateBySlot(preference, simSlot);
+ mSlotSimStatus.observe(mFragment.getViewLifecycleOwner(), mSimChangeObserver);
}
+ updateStateBySlot(preference, simSlot);
+ }
+
+ protected void updateStateBySlot(Preference preference, int simSlot) {
+ SubscriptionInfo subInfo = getSubscriptionInfo(simSlot);
+ preference.setEnabled(subInfo != null);
+ preference.setCopyingEnabled(subInfo != null);
+ preference.setTitle(getPreferenceTitle(simSlot));
+ preference.setSummary(getCarrierName(simSlot));
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
- final int simSlot = mPreferenceList.indexOf(preference);
+ if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
+ return false;
+ }
+ final int simSlot = getSimSlotIndex();
if (simSlot == -1) {
return false;
}
@@ -138,16 +146,7 @@
}
private SubscriptionInfo getSubscriptionInfo(int simSlot) {
- final List<SubscriptionInfo> subscriptionInfoList =
- mSubscriptionManager.getActiveSubscriptionInfoList();
- if (subscriptionInfoList != null) {
- for (SubscriptionInfo info : subscriptionInfoList) {
- if (info.getSimSlotIndex() == simSlot) {
- return info;
- }
- }
- }
- return null;
+ return (mSlotSimStatus == null) ? null : mSlotSimStatus.getSubscriptionInfo(simSlot);
}
private CharSequence getCarrierName(int simSlot) {
diff --git a/src/com/android/settings/deviceinfo/simstatus/SlotSimStatus.java b/src/com/android/settings/deviceinfo/simstatus/SlotSimStatus.java
index 033222a..b3aca97 100644
--- a/src/com/android/settings/deviceinfo/simstatus/SlotSimStatus.java
+++ b/src/com/android/settings/deviceinfo/simstatus/SlotSimStatus.java
@@ -17,24 +17,43 @@
package com.android.settings.deviceinfo.simstatus;
import android.content.Context;
-import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.util.Log;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
+
+import com.android.settings.network.SubscriptionsChangeListener;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Phaser;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
/**
* A class for showing a summary of status of sim slots.
*/
-public class SlotSimStatus {
+public class SlotSimStatus extends LiveData<Long>
+ implements DefaultLifecycleObserver,
+ SubscriptionsChangeListener.SubscriptionsChangeListenerClient {
private static final String TAG = "SlotSimStatus";
private final AtomicInteger mNumberOfSlots = new AtomicInteger(0);
+ private final ConcurrentHashMap<Integer, SubscriptionInfo> mSubscriptionMap =
+ new ConcurrentHashMap<Integer, SubscriptionInfo>();
private final Phaser mBlocker = new Phaser(1);
+ private final AtomicLong mDataVersion = new AtomicLong(0);
+
+ private Context mContext;
private int mBasePreferenceOrdering;
+ private SubscriptionsChangeListener mSubscriptionsChangeListener;
private static final String KEY_SIM_STATUS = "sim_status";
@@ -43,28 +62,71 @@
* @param context Context
*/
public SlotSimStatus(Context context) {
- this(context, null);
+ this(context, null, null);
}
/**
* Construct of class.
* @param context Context
* @param executor executor for offload to thread
+ * @param lifecycle Lifecycle
*/
- public SlotSimStatus(Context context, Executor executor) {
+ public SlotSimStatus(Context context, Executor executor, Lifecycle lifecycle) {
+ mContext = context;
if (executor == null) {
queryRecords(context);
} else {
- executor.execute(() -> queryRecords(context));
+ executor.execute(() -> asyncQueryRecords(context));
+ }
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ mSubscriptionsChangeListener = new SubscriptionsChangeListener(context, this);
+ mSubscriptionsChangeListener.start();
}
}
protected void queryRecords(Context context) {
+ queryDetails(context);
+ setValue(mDataVersion.incrementAndGet());
+ mBlocker.arrive();
+ }
+
+ protected void asyncQueryRecords(Context context) {
+ queryDetails(context);
+ postValue(mDataVersion.incrementAndGet());
+ mBlocker.arrive();
+ }
+
+ protected void updateRecords() {
+ queryDetails(mContext);
+ setValue(mDataVersion.incrementAndGet());
+ }
+
+ protected void queryDetails(Context context) {
TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
if (telMgr != null) {
mNumberOfSlots.set(telMgr.getPhoneCount());
}
- mBlocker.arrive();
+
+ SubscriptionManager subMgr = context.getSystemService(SubscriptionManager.class);
+ if (subMgr == null) {
+ mSubscriptionMap.clear();
+ return;
+ }
+
+ List<SubscriptionInfo> subInfoList = subMgr.getActiveSubscriptionInfoList();
+ if ((subInfoList == null) || (subInfoList.size() <= 0)) {
+ mSubscriptionMap.clear();
+ Log.d(TAG, "No active SIM.");
+ return;
+ }
+
+ mSubscriptionMap.clear();
+ subInfoList.forEach(subInfo -> {
+ int slotIndex = subInfo.getSimSlotIndex();
+ mSubscriptionMap.put(slotIndex, subInfo);
+ });
+ Log.d(TAG, "Number of active SIM: " + subInfoList.size());
}
protected void waitForResult() {
@@ -110,6 +172,19 @@
}
/**
+ * Get subscription based on slot index.
+ * @param slotIndex index of slot (starting from 0)
+ * @return SubscriptionInfo based on index of slot.
+ * {@code null} means no subscription on slot.
+ */
+ public SubscriptionInfo getSubscriptionInfo(int slotIndex) {
+ if (slotIndex >= size()) {
+ return null;
+ }
+ return mSubscriptionMap.get(slotIndex);
+ }
+
+ /**
* Get slot index based on Preference key
* @param prefKey is the preference key
* @return slot index.
@@ -124,4 +199,28 @@
}
return simSlotIndex - 1;
}
+
+ @Override
+ public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
+ if (airplaneModeEnabled) {
+ /**
+ * Only perform update when airplane mode ON.
+ * Relay on #onSubscriptionsChanged() when airplane mode OFF.
+ */
+ updateRecords();
+ }
+ }
+
+ @Override
+ public void onSubscriptionsChanged() {
+ updateRecords();
+ }
+
+ @Override
+ public void onDestroy(LifecycleOwner lifecycleOwner) {
+ if (mSubscriptionsChangeListener != null) {
+ mSubscriptionsChangeListener.stop();
+ }
+ lifecycleOwner.getLifecycle().removeObserver(this);
+ }
}
diff --git a/src/com/android/settings/display/ScreenSaverPreferenceController.java b/src/com/android/settings/display/ScreenSaverPreferenceController.java
index d6fef11..db4bc37 100644
--- a/src/com/android/settings/display/ScreenSaverPreferenceController.java
+++ b/src/com/android/settings/display/ScreenSaverPreferenceController.java
@@ -16,47 +16,41 @@
import android.content.Context;
import android.os.UserManager;
-import androidx.preference.Preference;
-
import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dream.DreamSettings;
-import com.android.settingslib.core.AbstractPreferenceController;
-public class ScreenSaverPreferenceController extends AbstractPreferenceController implements
+public class ScreenSaverPreferenceController extends BasePreferenceController implements
PreferenceControllerMixin {
- private static final String KEY_SCREEN_SAVER = "screensaver";
private final boolean mDreamsDisabledByAmbientModeSuppression;
- public ScreenSaverPreferenceController(Context context) {
- super(context);
+ public ScreenSaverPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+
mDreamsDisabledByAmbientModeSuppression = context.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
}
@Override
- public boolean isAvailable() {
+ public int getAvailabilityStatus() {
final boolean dreamsSupported = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsSupported);
final boolean dreamsOnlyEnabledForDockUser = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsOnlyEnabledForDockUser);
// TODO(b/257333623): Allow the Dock User to be non-SystemUser user in HSUM.
- return dreamsSupported && (!dreamsOnlyEnabledForDockUser || isSystemUser());
+ return (dreamsSupported && (!dreamsOnlyEnabledForDockUser || isSystemUser()))
+ ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
- public String getPreferenceKey() {
- return KEY_SCREEN_SAVER;
- }
-
- @Override
- public void updateState(Preference preference) {
+ public CharSequence getSummary() {
if (mDreamsDisabledByAmbientModeSuppression
&& AmbientDisplayAlwaysOnPreferenceController.isAodSuppressedByBedtime(mContext)) {
- preference.setSummary(R.string.screensaver_settings_when_to_dream_bedtime);
+ return mContext.getString(R.string.screensaver_settings_when_to_dream_bedtime);
} else {
- preference.setSummary(DreamSettings.getSummaryTextWithDreamName(mContext));
+ return DreamSettings.getSummaryTextWithDreamName(mContext);
}
}
diff --git a/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java b/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java
index 86d08cb..65ee5e0 100644
--- a/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java
+++ b/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java
@@ -24,6 +24,7 @@
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.utils.StringUtil;
public abstract class AdminGrantedPermissionsPreferenceControllerBase
extends AbstractPreferenceController implements PreferenceControllerMixin {
@@ -51,9 +52,8 @@
if (num == 0) {
mHasApps = false;
} else {
- preference.setSummary(mContext.getResources().getQuantityString(
- R.plurals.enterprise_privacy_number_packages_lower_bound,
- num, num));
+ preference.setSummary(StringUtil.getIcuPluralsString(mContext, num,
+ R.string.enterprise_privacy_number_packages_lower_bound));
mHasApps = true;
}
preference.setVisible(mHasApps);
diff --git a/src/com/android/settings/enterprise/CaCertsPreferenceControllerBase.java b/src/com/android/settings/enterprise/CaCertsPreferenceControllerBase.java
index c7dde5c..936df25 100644
--- a/src/com/android/settings/enterprise/CaCertsPreferenceControllerBase.java
+++ b/src/com/android/settings/enterprise/CaCertsPreferenceControllerBase.java
@@ -22,6 +22,7 @@
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.utils.StringUtil;
public abstract class CaCertsPreferenceControllerBase
extends AbstractPreferenceController implements PreferenceControllerMixin {
@@ -36,9 +37,8 @@
@Override
public void updateState(Preference preference) {
- final int certs = getNumberOfCaCerts();
- preference.setSummary(mContext.getResources().getQuantityString(
- R.plurals.enterprise_privacy_number_ca_certs, certs, certs));
+ preference.setSummary(StringUtil.getIcuPluralsString(mContext, getNumberOfCaCerts(),
+ R.string.enterprise_privacy_number_ca_certs));
}
@Override
diff --git a/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceController.java b/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceController.java
index 9bd4279..91ebeff 100644
--- a/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceController.java
+++ b/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceController.java
@@ -22,6 +22,7 @@
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.utils.StringUtil;
public class EnterpriseInstalledPackagesPreferenceController
extends AbstractPreferenceController implements PreferenceControllerMixin {
@@ -47,9 +48,8 @@
available = false;
} else {
available = true;
- preference.setSummary(mContext.getResources().getQuantityString(
- R.plurals.enterprise_privacy_number_packages_lower_bound, num,
- num));
+ preference.setSummary(StringUtil.getIcuPluralsString(mContext, num,
+ R.string.enterprise_privacy_number_packages_lower_bound));
}
preference.setVisible(available);
diff --git a/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceController.java b/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceController.java
index 3c090ed..3e83523 100644
--- a/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceController.java
+++ b/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceController.java
@@ -40,6 +40,7 @@
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.users.UserFeatureProvider;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
@@ -192,16 +193,16 @@
case CONTACTS:
return context.getString(R.string.default_contacts_app_title);
case PHONE:
- return context.getResources()
- .getQuantityString(R.plurals.default_phone_app_title, appCount);
+ return StringUtil.getIcuPluralsString(context, appCount,
+ R.string.default_phone_app_title);
case MAP:
return context.getString(R.string.default_map_app_title);
case EMAIL:
- return context.getResources()
- .getQuantityString(R.plurals.default_email_app_title, appCount);
+ return StringUtil.getIcuPluralsString(context, appCount,
+ R.string.default_email_app_title);
case CAMERA:
- return context.getResources()
- .getQuantityString(R.plurals.default_camera_app_title, appCount);
+ return StringUtil.getIcuPluralsString(context, appCount,
+ R.string.default_camera_app_title);
default:
throw new IllegalStateException("Unknown type of default " + typeOfDefault);
}
diff --git a/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java b/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java
index b2f9459..32abcc4 100644
--- a/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java
+++ b/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java
@@ -26,6 +26,7 @@
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.users.UserFeatureProvider;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.utils.StringUtil;
public class EnterpriseSetDefaultAppsPreferenceController
extends AbstractPreferenceController implements PreferenceControllerMixin {
@@ -44,8 +45,8 @@
@Override
public void updateState(Preference preference) {
final int num = getNumberOfEnterpriseSetDefaultApps();
- preference.setSummary(mContext.getResources().getQuantityString(
- R.plurals.enterprise_privacy_number_packages, num, num));
+ preference.setSummary(StringUtil.getIcuPluralsString(mContext, num,
+ R.string.enterprise_privacy_number_packages));
}
@Override
diff --git a/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerBase.java b/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerBase.java
index 322589e..f008b18 100644
--- a/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerBase.java
+++ b/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerBase.java
@@ -22,6 +22,7 @@
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.utils.StringUtil;
public abstract class FailedPasswordWipePreferenceControllerBase
extends AbstractPreferenceController implements PreferenceControllerMixin {
@@ -39,9 +40,8 @@
@Override
public void updateState(Preference preference) {
final int failedPasswordsBeforeWipe = getMaximumFailedPasswordsBeforeWipe();
- preference.setSummary(mContext.getResources().getQuantityString(
- R.plurals.enterprise_privacy_number_failed_password_wipe,
- failedPasswordsBeforeWipe, failedPasswordsBeforeWipe));
+ preference.setSummary(StringUtil.getIcuPluralsString(mContext, failedPasswordsBeforeWipe,
+ R.string.enterprise_privacy_number_failed_password_wipe));
}
@Override
diff --git a/src/com/android/settings/enterprise/ManageDeviceAdminPreferenceController.java b/src/com/android/settings/enterprise/ManageDeviceAdminPreferenceController.java
index 71bd3d1..90a1a1e 100644
--- a/src/com/android/settings/enterprise/ManageDeviceAdminPreferenceController.java
+++ b/src/com/android/settings/enterprise/ManageDeviceAdminPreferenceController.java
@@ -21,6 +21,7 @@
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.utils.StringUtil;
public class ManageDeviceAdminPreferenceController extends BasePreferenceController {
@@ -47,8 +48,8 @@
}
// TODO: override
- return mContext.getResources().getQuantityString(R.plurals.number_of_device_admins,
- activeAdmins, activeAdmins);
+ return StringUtil.getIcuPluralsString(mContext, activeAdmins,
+ R.string.number_of_device_admins);
}
@Override
diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSchedulePreferenceController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSchedulePreferenceController.java
index 0b129ef..dce1903 100644
--- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSchedulePreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSchedulePreferenceController.java
@@ -15,9 +15,10 @@
*/
package com.android.settings.fuelgauge.batterysaver;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
+
import android.content.ContentResolver;
import android.content.Context;
-import android.os.PowerManager;
import android.provider.Settings;
import android.provider.Settings.Global;
@@ -61,20 +62,14 @@
@Override
public CharSequence getSummary() {
final ContentResolver resolver = mContext.getContentResolver();
- final int mode = Settings.Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
- PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
- if (mode == PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE) {
+ final String mode = BatterySaverUtils.getBatterySaverScheduleKey(mContext);
+ if (KEY_PERCENTAGE.equals(mode)) {
final int threshold =
Settings.Global.getInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
- if (threshold <= 0) {
- return mContext.getText(R.string.battery_saver_auto_no_schedule);
- } else {
- return mContext.getString(R.string.battery_saver_auto_percentage_summary,
- Utils.formatPercentage(threshold));
- }
- } else {
- return mContext.getText(R.string.battery_saver_auto_routine);
+ return mContext.getString(R.string.battery_saver_auto_percentage_summary,
+ Utils.formatPercentage(threshold));
}
+ return mContext.getText(R.string.battery_saver_auto_no_schedule);
}
@Override
diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsController.java
index c584b9b..4266bea 100644
--- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsController.java
+++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsController.java
@@ -15,6 +15,9 @@
*/
package com.android.settings.fuelgauge.batterysaver;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
+
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
@@ -22,7 +25,6 @@
import android.provider.Settings;
import android.provider.Settings.Global;
import android.text.TextUtils;
-import android.util.Log;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
@@ -39,8 +41,6 @@
public class BatterySaverScheduleRadioButtonsController {
private static final String TAG = "BatterySaverScheduleRadioButtonsController";
- public static final String KEY_NO_SCHEDULE = "key_battery_saver_no_schedule";
- public static final String KEY_PERCENTAGE = "key_battery_saver_percentage";
public static final int TRIGGER_LEVEL_MIN = 10;
private Context mContext;
@@ -52,21 +52,6 @@
mSeekBarController = seekbar;
}
- public String getDefaultKey() {
- final ContentResolver resolver = mContext.getContentResolver();
- final int mode = Settings.Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
- PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
- if (mode == PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE) {
- final int threshold =
- Settings.Global.getInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
- return threshold <= 0 ? KEY_NO_SCHEDULE : KEY_PERCENTAGE;
- }
- // Convert the legacy routine mode into none.
- BatterySaverUtils.revertScheduleToNoneIfNeeded(mContext);
- Log.w(TAG, "Found the legacy routine mode and set into none");
- return KEY_NO_SCHEDULE;
- }
-
public boolean setDefaultKey(String key) {
if (key == null) {
return false;
diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java
index e6be684..687221e 100644
--- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java
+++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java
@@ -15,9 +15,10 @@
*/
package com.android.settings.fuelgauge.batterysaver;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
+
import android.content.ContentResolver;
import android.content.Context;
-import android.os.PowerManager;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.widget.SeekBar;
@@ -31,6 +32,7 @@
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.fuelgauge.BatterySaverUtils;
/**
* Responds to user actions in the Settings > Battery > Set a Schedule Screen for the seekbar.
@@ -99,25 +101,17 @@
public void updateSeekBar() {
final ContentResolver resolver = mContext.getContentResolver();
- // Note: this can also be obtained via PowerManager.getPowerSaveModeTrigger()
- final int mode = Settings.Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
- PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
- // if mode is "dynamic" we are in routine mode, percentage with non-zero threshold is
- // percentage mode, otherwise it is no schedule mode
- if (mode == PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE) {
+ final String mode = BatterySaverUtils.getBatterySaverScheduleKey(mContext);
+ if (KEY_PERCENTAGE.equals(mode)) {
final int threshold =
Settings.Global.getInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
- if (threshold <= 0) {
- mSeekBarPreference.setVisible(false);
- } else {
- final int currentSeekbarValue = Math.max(threshold / 5, MIN_SEEKBAR_VALUE);
- mSeekBarPreference.setVisible(true);
- mSeekBarPreference.setProgress(currentSeekbarValue);
- final CharSequence stateDescription = formatStateDescription(
- currentSeekbarValue * 5);
- mSeekBarPreference.setTitle(stateDescription);
- mSeekBarPreference.overrideSeekBarStateDescription(stateDescription);
- }
+ final int currentSeekbarValue = Math.max(threshold / 5, MIN_SEEKBAR_VALUE);
+ mSeekBarPreference.setVisible(true);
+ mSeekBarPreference.setProgress(currentSeekbarValue);
+ final CharSequence stateDescription = formatStateDescription(
+ currentSeekbarValue * 5);
+ mSeekBarPreference.setTitle(stateDescription);
+ mSeekBarPreference.overrideSeekBarStateDescription(stateDescription);
} else {
mSeekBarPreference.setVisible(false);
}
diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettings.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettings.java
index 0eca3a6..1a7fdaf 100644
--- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettings.java
+++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSettings.java
@@ -16,6 +16,9 @@
package com.android.settings.fuelgauge.batterysaver;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
+
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.database.ContentObserver;
@@ -27,7 +30,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
-import android.text.TextUtils;
import android.view.View;
import androidx.annotation.NonNull;
@@ -95,7 +97,7 @@
Settings.Secure.getUriFor(Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED),
false,
mSettingsObserver);
- mSaverScheduleKey = mRadioButtonController.getDefaultKey();
+ mSaverScheduleKey = BatterySaverUtils.getBatterySaverScheduleKey(mContext);
mSaverPercentage = getSaverPercentage();
}
@@ -125,13 +127,13 @@
candidates.add(new BatterySaverScheduleCandidateInfo(
context.getText(R.string.battery_saver_auto_no_schedule),
/* summary */ null,
- BatterySaverScheduleRadioButtonsController.KEY_NO_SCHEDULE,
+ KEY_NO_SCHEDULE,
/* enabled */ true));
BatterySaverUtils.revertScheduleToNoneIfNeeded(context);
candidates.add(new BatterySaverScheduleCandidateInfo(
context.getText(R.string.battery_saver_auto_percentage),
/* summary */ null,
- BatterySaverScheduleRadioButtonsController.KEY_PERCENTAGE,
+ KEY_PERCENTAGE,
/* enabled */ true));
return candidates;
@@ -157,7 +159,7 @@
@Override
protected String getDefaultKey() {
- return mRadioButtonController.getDefaultKey();
+ return BatterySaverUtils.getBatterySaverScheduleKey(mContext);
}
@Override
@@ -172,7 +174,8 @@
private void logPowerSaver() {
final int currentSaverPercentage = getSaverPercentage();
- final String currentSaverScheduleKey = mRadioButtonController.getDefaultKey();
+ final String currentSaverScheduleKey = BatterySaverUtils.getBatterySaverScheduleKey(
+ mContext);
if (mSaverScheduleKey.equals(currentSaverScheduleKey)
&& mSaverPercentage == currentSaverPercentage) {
return;
diff --git a/src/com/android/settings/homepage/contextualcards/conditional/HotspotConditionController.java b/src/com/android/settings/homepage/contextualcards/conditional/HotspotConditionController.java
index dd78807..2a08a1b 100644
--- a/src/com/android/settings/homepage/contextualcards/conditional/HotspotConditionController.java
+++ b/src/com/android/settings/homepage/contextualcards/conditional/HotspotConditionController.java
@@ -28,9 +28,9 @@
import android.os.UserManager;
import com.android.settings.R;
-import com.android.settings.TetherSettings;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.homepage.contextualcards.ContextualCard;
+import com.android.settings.network.tether.TetherSettings;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
diff --git a/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java b/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java
new file mode 100644
index 0000000..40c15ff
--- /dev/null
+++ b/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settingslib.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ModifierKeysPickerDialogFragment extends DialogFragment {
+
+ private Preference mPreference;
+ private String mKeyDefaultName;
+ private Context mContext;
+
+ public ModifierKeysPickerDialogFragment() {
+ }
+
+ public ModifierKeysPickerDialogFragment(Preference preference) {
+ mPreference = preference;
+ mKeyDefaultName = preference.getTitle().toString();
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ super.onCreateDialog(savedInstanceState);
+ mContext = getActivity();
+ String[] modifierKeys = new String[] {
+ mContext.getString(R.string.modifier_keys_caps_lock),
+ mContext.getString(R.string.modifier_keys_ctrl),
+ mContext.getString(R.string.modifier_keys_meta),
+ mContext.getString(R.string.modifier_keys_alt)};
+
+ View dialoglayout =
+ LayoutInflater.from(mContext).inflate(R.layout.modifier_key_picker_dialog, null);
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext);
+ dialogBuilder.setView(dialoglayout);
+
+ TextView summary = dialoglayout.findViewById(R.id.modifier_key_picker_summary);
+ CharSequence summaryText = mContext.getString(
+ R.string.modifier_keys_picker_summary, mKeyDefaultName);
+ summary.setText(summaryText);
+
+ List<String> list = new ArrayList<>();
+ for (int i = 0; i < modifierKeys.length; i++) {
+ list.add(modifierKeys[i]);
+ }
+ ModifierKeyAdapter adapter = new ModifierKeyAdapter(list);
+ ListView listView = dialoglayout.findViewById(R.id.modifier_key_picker);
+ listView.setAdapter(adapter);
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
+ adapter.setCurrentItem(i);
+ adapter.setClick(true);
+ adapter.notifyDataSetChanged();
+ }
+ });
+
+ AlertDialog modifierKeyDialog = dialogBuilder.create();
+ Button doneButton = dialoglayout.findViewById(R.id.modifier_key_done_button);
+ doneButton.setOnClickListener(v -> {
+ String selectedItem = list.get(adapter.getCurrentItem());
+ Spannable itemSummary;
+ if (selectedItem.equals(mKeyDefaultName)) {
+ itemSummary = new SpannableString(
+ mContext.getString(R.string.modifier_keys_default_summary));
+ itemSummary.setSpan(
+ new ForegroundColorSpan(getColorOfTextColorSecondary()),
+ 0, itemSummary.length(), 0);
+ // TODO(b/252812993): remapModifierKey
+ } else {
+ itemSummary = new SpannableString(selectedItem);
+ itemSummary.setSpan(
+ new ForegroundColorSpan(getColorOfColorAccentPrimaryVariant()),
+ 0, itemSummary.length(), 0);
+ }
+ mPreference.setSummary(itemSummary);
+ modifierKeyDialog.dismiss();
+ });
+
+ Button cancelButton = dialoglayout.findViewById(R.id.modifier_key_cancel_button);
+ cancelButton.setOnClickListener(v -> {
+ modifierKeyDialog.dismiss();
+ });
+
+ final Window window = modifierKeyDialog.getWindow();
+ window.setType(TYPE_SYSTEM_DIALOG);
+
+ return modifierKeyDialog;
+ }
+
+ class ModifierKeyAdapter extends BaseAdapter {
+ private int mCurrentItem = 0;
+ private boolean mIsClick = false;
+ private List<String> mList;
+
+ ModifierKeyAdapter(List<String> list) {
+ this.mList = list;
+ }
+
+ @Override
+ public int getCount() {
+ return mList.size();
+ }
+
+ @Override
+ public Object getItem(int i) {
+ return mList.get(i);
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ @Override
+ public View getView(int i, View view, ViewGroup viewGroup) {
+ if (view == null) {
+ view = LayoutInflater.from(mContext).inflate(R.layout.modifier_key_item, null);
+ }
+ TextView textView = view.findViewById(R.id.modifier_key_text);
+ ImageView checkIcon = view.findViewById(R.id.modifier_key_check_icon);
+ textView.setText(mList.get(i));
+ if (mCurrentItem == i && mIsClick) {
+ textView.setTextColor(getColorOfColorAccentPrimaryVariant());
+ checkIcon.setImageAlpha(255);
+ } else {
+ textView.setTextColor(getColorOfTextColorPrimary());
+ checkIcon.setImageAlpha(0);
+ }
+ return view;
+ }
+
+ public void setCurrentItem(int currentItem) {
+ this.mCurrentItem = currentItem;
+ }
+
+ public int getCurrentItem() {
+ return this.mCurrentItem;
+ }
+
+ public void setClick(boolean click) {
+ this.mIsClick = click;
+ }
+ }
+
+ private int getColorOfTextColorPrimary() {
+ return Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
+ }
+
+ private int getColorOfTextColorSecondary() {
+ return Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorSecondary);
+ }
+
+ private int getColorOfColorAccentPrimaryVariant() {
+ return Utils.getColorAttrDefaultColor(
+ mContext, com.android.internal.R.attr.colorAccentPrimaryVariant);
+ }
+}
diff --git a/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java b/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java
new file mode 100644
index 0000000..a7f8a37
--- /dev/null
+++ b/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+
+public class ModifierKeysPreferenceController extends BasePreferenceController {
+
+ private static String KEY_TAG = "modifier_keys_dialog_tag";
+ private static String KEY_RESTORE_PREFERENCE = "modifier_keys_restore";
+
+ private Fragment mParent;
+ private FragmentManager mFragmentManager;
+
+ public ModifierKeysPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ public void setFragment(Fragment parent) {
+ mParent = parent;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ // TODO: getModifierKeyRemapping()
+ // setTitle
+ // setSummary
+ if (mParent == null) {
+ return;
+ }
+ // The dialog screen depends on the previous selected key's fragment.
+ // In the rotation scenario, we should remove the previous dialog screen first.
+ clearPreviousDialog();
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (preference.getKey().equals(KEY_RESTORE_PREFERENCE)) {
+ return false;
+ }
+ showModifierKeysDialog(preference);
+ return true;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ private void showModifierKeysDialog(Preference preference) {
+ ModifierKeysPickerDialogFragment fragment =
+ new ModifierKeysPickerDialogFragment(preference);
+ fragment.setTargetFragment(mParent, 0);
+ fragment.show(mFragmentManager, KEY_TAG);
+ }
+
+ private void clearPreviousDialog() {
+ mFragmentManager = mParent.getFragmentManager();
+ DialogFragment preKeysDialogFragment =
+ (DialogFragment) mFragmentManager.findFragmentByTag(KEY_TAG);
+ if (preKeysDialogFragment != null) {
+ preKeysDialogFragment.dismiss();
+ }
+ }
+}
diff --git a/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java b/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java
new file mode 100644
index 0000000..0b605a0
--- /dev/null
+++ b/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settingslib.Utils;
+
+public class ModifierKeysResetDialogFragment extends DialogFragment {
+ private static final String MODIFIER_KEYS_CAPS_LOCK = "modifier_keys_caps_lock";
+ private static final String MODIFIER_KEYS_CTRL = "modifier_keys_ctrl";
+ private static final String MODIFIER_KEYS_META = "modifier_keys_meta";
+ private static final String MODIFIER_KEYS_ALT = "modifier_keys_alt";
+
+ private PreferenceScreen mScreen;
+ private String[] mKeys = {
+ MODIFIER_KEYS_CAPS_LOCK,
+ MODIFIER_KEYS_CTRL,
+ MODIFIER_KEYS_META,
+ MODIFIER_KEYS_ALT};
+
+ public ModifierKeysResetDialogFragment() {
+ }
+
+ public ModifierKeysResetDialogFragment(PreferenceScreen screen) {
+ mScreen = screen;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ super.onCreateDialog(savedInstanceState);
+ Context mContext = getActivity();
+ View dialoglayout =
+ LayoutInflater.from(mContext).inflate(R.layout.modifier_key_reset_dialog, null);
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext);
+ dialogBuilder.setView(dialoglayout);
+ AlertDialog modifierKeyResetDialog = dialogBuilder.create();
+
+ Button restoreButton = dialoglayout.findViewById(R.id.modifier_key_reset_restore_button);
+ restoreButton.setOnClickListener(v -> {
+ resetToDefault();
+ modifierKeyResetDialog.dismiss();
+ });
+
+ Button cancelButton = dialoglayout.findViewById(R.id.modifier_key_reset_cancel_button);
+ cancelButton.setOnClickListener(v -> {
+ modifierKeyResetDialog.dismiss();
+ });
+
+ final Window window = modifierKeyResetDialog.getWindow();
+ window.setType(TYPE_SYSTEM_DIALOG);
+
+ return modifierKeyResetDialog;
+ }
+
+ private void resetToDefault() {
+ Context mContext = getActivity();
+ for (int i = 0; i < mKeys.length; i++) {
+ Preference preference = mScreen.findPreference(mKeys[i]);
+ Spannable title = new SpannableString(
+ mContext.getString(R.string.modifier_keys_default_summary));
+ title.setSpan(
+ new ForegroundColorSpan(getColorOfTextColorSecondary()),
+ 0, title.length(), 0);
+ preference.setSummary(title);
+ }
+ // TODO(b/252812993): clearAllModifierKeyRemappings()
+ }
+
+ private int getColorOfTextColorSecondary() {
+ return Utils.getColorAttrDefaultColor(getActivity(), android.R.attr.textColorSecondary);
+ }
+}
diff --git a/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java b/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java
new file mode 100644
index 0000000..159cc11
--- /dev/null
+++ b/src/com/android/settings/inputmethod/ModifierKeysRestorePreferenceController.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.Utils;
+
+public class ModifierKeysRestorePreferenceController extends BasePreferenceController {
+
+ private static String KEY_TAG = "modifier_keys_dialog_tag";
+
+ private Fragment mParent;
+ private FragmentManager mFragmentManager;
+ private PreferenceScreen mScreen;
+
+ public ModifierKeysRestorePreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ public void setFragment(Fragment parent) {
+ mParent = parent;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ if (mParent == null) {
+ return;
+ }
+ mScreen = screen;
+ // The dialog screen depends on the previous selected key's fragment.
+ // In the rotation scenario, we should remove the previous dialog first.
+ clearPreviousDialog();
+ setResetKeyColor();
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (!preference.getKey().equals(getPreferenceKey())) {
+ return false;
+ }
+ showResetDialog();
+ return true;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ private void showResetDialog() {
+ ModifierKeysResetDialogFragment fragment =
+ new ModifierKeysResetDialogFragment(mScreen);
+ fragment.setTargetFragment(mParent, 0);
+ fragment.show(mFragmentManager, KEY_TAG);
+ }
+
+ private void setResetKeyColor() {
+ Preference preference = mScreen.findPreference(getPreferenceKey());
+ Spannable title = new SpannableString(
+ mParent.getActivity().getString(R.string.modifier_keys_reset_title));
+ title.setSpan(
+ new ForegroundColorSpan(getColorOfColorAccentPrimaryVariant()),
+ 0, title.length(), 0);
+ preference.setTitle(title);
+ }
+
+ private int getColorOfColorAccentPrimaryVariant() {
+ return Utils.getColorAttrDefaultColor(
+ mParent.getActivity(), com.android.internal.R.attr.colorAccentPrimaryVariant);
+ }
+
+ private void clearPreviousDialog() {
+ mFragmentManager = mParent.getFragmentManager();
+ DialogFragment preResetDialogFragment =
+ (DialogFragment) mFragmentManager.findFragmentByTag(KEY_TAG);
+ if (preResetDialogFragment != null) {
+ preResetDialogFragment.dismiss();
+ }
+ }
+}
diff --git a/src/com/android/settings/inputmethod/ModifierKeysSettings.java b/src/com/android/settings/inputmethod/ModifierKeysSettings.java
new file mode 100644
index 0000000..2353c2c
--- /dev/null
+++ b/src/com/android/settings/inputmethod/ModifierKeysSettings.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+@SearchIndexable
+public class ModifierKeysSettings extends DashboardFragment {
+
+ private static final String TAG = "ModifierKeysSettings";
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ use(ModifierKeysPreferenceController.class).setFragment(this /*parent*/);
+ use(ModifierKeysRestorePreferenceController.class).setFragment(this /*parent*/);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SETTINGS_KEYBOARDS_MODIFIER_KEYS;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.modifier_keys_settings;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.modifier_keys_settings) {
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ return FeatureFlagUtils
+ .isEnabled(
+ context, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
+ }
+ };
+}
diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
index 38ea840..7d43d7c 100644
--- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
+++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
@@ -67,6 +67,7 @@
private static final String KEYBOARD_OPTIONS_CATEGORY = "keyboard_options_category";
private static final String SHOW_VIRTUAL_KEYBOARD_SWITCH = "show_virtual_keyboard_switch";
private static final String KEYBOARD_SHORTCUTS_HELPER = "keyboard_shortcuts_helper";
+ private static final String MODIFIER_KEYS_SETTINGS = "modifier_keys_settings";
@NonNull
private final ArrayList<HardKeyboardDeviceInfo> mLastHardKeyboards = new ArrayList<>();
@@ -94,9 +95,14 @@
mShowVirtualKeyboardSwitch = Preconditions.checkNotNull(
(SwitchPreference) mKeyboardAssistanceCategory.findPreference(
SHOW_VIRTUAL_KEYBOARD_SWITCH));
+
mIsNewKeyboardSettings = FeatureFlagUtils.isEnabled(
getContext(), FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI);
- // TODO(b/247080921): Support shortcuts list & modifier keys
+ boolean isModifierKeySettingsEnabled = FeatureFlagUtils
+ .isEnabled(getContext(), FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
+ if (!isModifierKeySettingsEnabled) {
+ mKeyboardAssistanceCategory.removePreference(findPreference(MODIFIER_KEYS_SETTINGS));
+ }
}
@Override
diff --git a/src/com/android/settings/inputmethod/TouchGesturesButtonPreferenceController.java b/src/com/android/settings/inputmethod/TouchGesturesButtonPreferenceController.java
new file mode 100644
index 0000000..6e54689
--- /dev/null
+++ b/src/com/android/settings/inputmethod/TouchGesturesButtonPreferenceController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.ButtonPreference;
+
+public class TouchGesturesButtonPreferenceController extends BasePreferenceController {
+
+ private static final int ORDER_TOP = 0;
+ private static final int ORDER_BOTTOM = 100;
+ private static final String PREFERENCE_KEY = "trackpad_touch_gesture";
+
+ public TouchGesturesButtonPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ ButtonPreference buttonPreference =
+ (ButtonPreference) screen.findPreference(getPreferenceKey());
+ boolean touchGestureDeveloperMode = FeatureFlagUtils
+ .isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
+ if (getPreferenceKey().equals(PREFERENCE_KEY)) {
+ if (touchGestureDeveloperMode) {
+ buttonPreference.setOrder(ORDER_TOP);
+ } else {
+ buttonPreference.setOrder(ORDER_BOTTOM);
+ }
+ }
+ buttonPreference.setOnClickListener(v -> {
+ showTouchpadGestureEducation();
+ });
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ private void showTouchpadGestureEducation() {
+ // TODO: Waiting for the education UX design.
+ /* For example:
+ FragmentManager fragmentManager = mParent.getActivity().getSupportFragmentManager();
+ FragmentTransaction transaction = fragmentManager.beginTransaction();
+ TrackpadGestureDialogFragment fragment = new TrackpadGestureDialogFragment();
+ fragment.show(transaction, GESTURE_DIALOG_TAG);
+ */
+ }
+}
diff --git a/src/com/android/settings/inputmethod/TrackpadSettings.java b/src/com/android/settings/inputmethod/TrackpadSettings.java
new file mode 100644
index 0000000..436e3e6
--- /dev/null
+++ b/src/com/android/settings/inputmethod/TrackpadSettings.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+
+public class TrackpadSettings extends DashboardFragment {
+
+ private static final String TAG = "TrackpadSettings";
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SETTINGS_KEYBOARDS_TOUCHPAD;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.trackpad_settings;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.trackpad_settings) {
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ return FeatureFlagUtils
+ .isEnabled(context, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_TRACKPAD);
+ }
+ };
+}
diff --git a/src/com/android/settings/inputmethod/TrackpadSettingsController.java b/src/com/android/settings/inputmethod/TrackpadSettingsController.java
new file mode 100644
index 0000000..41be395
--- /dev/null
+++ b/src/com/android/settings/inputmethod/TrackpadSettingsController.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
+import com.android.settings.core.BasePreferenceController;
+
+public class TrackpadSettingsController extends BasePreferenceController {
+
+ public TrackpadSettingsController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ // TODO: Need to detect if trackpad is connected with device.
+ boolean isFeatureOn = FeatureFlagUtils
+ .isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_TRACKPAD);
+ return isFeatureOn ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/inputmethod/TrackpadTouchGestureSettings.java b/src/com/android/settings/inputmethod/TrackpadTouchGestureSettings.java
new file mode 100644
index 0000000..9884862
--- /dev/null
+++ b/src/com/android/settings/inputmethod/TrackpadTouchGestureSettings.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+
+public class TrackpadTouchGestureSettings extends DashboardFragment {
+
+ private static final String TAG = "TrackpadTouchGestureSettings";
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SETTINGS_KEYBOARDS_TOUCHPAD_GESTURE;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.trackpad_gesture_settings;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.trackpad_settings) {
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ return FeatureFlagUtils
+ .isEnabled(
+ context,
+ FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
+ }
+ };
+}
diff --git a/src/com/android/settings/inputmethod/TrackpadTouchGestureSettingsController.java b/src/com/android/settings/inputmethod/TrackpadTouchGestureSettingsController.java
new file mode 100644
index 0000000..8f04aee
--- /dev/null
+++ b/src/com/android/settings/inputmethod/TrackpadTouchGestureSettingsController.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
+import com.android.settings.core.BasePreferenceController;
+
+public class TrackpadTouchGestureSettingsController extends BasePreferenceController {
+
+ public TrackpadTouchGestureSettingsController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ boolean isFeatureOn = FeatureFlagUtils
+ .isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
+ return isFeatureOn ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java
index 5947a87..1d6fb47 100644
--- a/src/com/android/settings/localepicker/LocaleListEditor.java
+++ b/src/com/android/settings/localepicker/LocaleListEditor.java
@@ -46,6 +46,7 @@
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw;
+import com.android.settingslib.utils.StringUtil;
import java.util.ArrayList;
import java.util.List;
@@ -218,8 +219,8 @@
return;
}
- final String title = getResources().getQuantityString(R.plurals.dlg_remove_locales_title,
- checkedCount);
+ final String title = StringUtil.getIcuPluralsString(getContext(), checkedCount,
+ R.string.dlg_remove_locales_title);
mShowingRemoveDialog = true;
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
diff --git a/src/com/android/settings/location/AppLocationPermissionPreferenceController.java b/src/com/android/settings/location/AppLocationPermissionPreferenceController.java
index 90b3797..37bbd08 100644
--- a/src/com/android/settings/location/AppLocationPermissionPreferenceController.java
+++ b/src/com/android/settings/location/AppLocationPermissionPreferenceController.java
@@ -16,9 +16,12 @@
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.utils.StringUtil;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class AppLocationPermissionPreferenceController extends
@@ -56,9 +59,11 @@
if (mNumTotal == -1 || mNumHasLocation == -1) {
return mContext.getString(R.string.location_settings_loading_app_permission_stats);
}
- return mContext.getResources().getQuantityString(
- R.plurals.location_app_permission_summary_location_on, mNumHasLocation,
- mNumHasLocation, mNumTotal);
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", mNumHasLocation);
+ arguments.put("total", mNumTotal);
+ return StringUtil.getIcuPluralsString(mContext, arguments,
+ R.string.location_app_permission_summary_location_on);
} else {
return mContext.getString(R.string.location_app_permission_summary_location_off);
}
diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java
index 7b7cdca..a2ade47 100644
--- a/src/com/android/settings/network/NetworkProviderSettings.java
+++ b/src/com/android/settings/network/NetworkProviderSettings.java
@@ -85,6 +85,7 @@
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.search.Indexable;
import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.LayoutPreference;
@@ -1130,22 +1131,21 @@
private String getSavedNetworkSettingsSummaryText(
int numSavedNetworks, int numSavedSubscriptions) {
- if (getResources() == null) {
+ if (getContext() == null) {
Log.w(TAG, "getSavedNetworkSettingsSummaryText shouldn't run if resource is not ready");
return null;
}
if (numSavedSubscriptions == 0) {
- return getResources().getQuantityString(R.plurals.wifi_saved_access_points_summary,
- numSavedNetworks, numSavedNetworks);
+ return StringUtil.getIcuPluralsString(getContext(), numSavedNetworks,
+ R.string.wifi_saved_access_points_summary);
} else if (numSavedNetworks == 0) {
- return getResources().getQuantityString(
- R.plurals.wifi_saved_passpoint_access_points_summary,
- numSavedSubscriptions, numSavedSubscriptions);
+ return StringUtil.getIcuPluralsString(getContext(), numSavedSubscriptions,
+ R.string.wifi_saved_passpoint_access_points_summary);
} else {
final int numTotalEntries = numSavedNetworks + numSavedSubscriptions;
- return getResources().getQuantityString(R.plurals.wifi_saved_all_access_points_summary,
- numTotalEntries, numTotalEntries);
+ return StringUtil.getIcuPluralsString(getContext(), numTotalEntries,
+ R.string.wifi_saved_all_access_points_summary);
}
}
diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/network/tether/TetherSettings.java
similarity index 98%
rename from src/com/android/settings/TetherSettings.java
rename to src/com/android/settings/network/tether/TetherSettings.java
index 331a86b..a674722 100644
--- a/src/com/android/settings/TetherSettings.java
+++ b/src/com/android/settings/network/tether/TetherSettings.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings;
+package com.android.settings.network.tether;
import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
import static android.net.ConnectivityManager.TETHERING_USB;
@@ -53,6 +53,9 @@
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
+import com.android.settings.R;
+import com.android.settings.RestrictedSettingsFragment;
+import com.android.settings.Utils;
import com.android.settings.core.FeatureFlags;
import com.android.settings.datausage.DataSaverBackend;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -69,7 +72,7 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
-/*
+/**
* Displays preferences for Tethering.
*/
@SearchIndexable
@@ -368,8 +371,9 @@
registerReceiver();
mEthernetListener = new EthernetListener(this);
- if (mEm != null)
+ if (mEm != null) {
mEm.addInterfaceStateListener(r -> mHandler.post(r), mEthernetListener);
+ }
updateUsbState();
updateBluetoothAndEthernetState();
@@ -384,8 +388,9 @@
}
getActivity().unregisterReceiver(mTetherChangeReceiver);
mTm.unregisterTetheringEventCallback(mTetheringEventCallback);
- if (mEm != null)
+ if (mEm != null) {
mEm.removeInterfaceStateListener(mEthernetListener);
+ }
mTetherChangeReceiver = null;
mStartTetheringCallback = null;
mTetheringEventCallback = null;
diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java
index 648b644..0c05039 100644
--- a/src/com/android/settings/password/ChooseLockPassword.java
+++ b/src/com/android/settings/password/ChooseLockPassword.java
@@ -780,46 +780,38 @@
messages.add(getString(R.string.lockpassword_illegal_character));
break;
case NOT_ENOUGH_UPPER_CASE:
- messages.add(getResources().getQuantityString(
- R.plurals.lockpassword_password_requires_uppercase,
- error.requirement, error.requirement));
+ messages.add(StringUtil.getIcuPluralsString(getContext(), error.requirement,
+ R.string.lockpassword_password_requires_uppercase));
break;
case NOT_ENOUGH_LOWER_CASE:
- messages.add(getResources().getQuantityString(
- R.plurals.lockpassword_password_requires_lowercase,
- error.requirement, error.requirement));
+ messages.add(StringUtil.getIcuPluralsString(getContext(), error.requirement,
+ R.string.lockpassword_password_requires_lowercase));
break;
case NOT_ENOUGH_LETTERS:
- messages.add(getResources().getQuantityString(
- R.plurals.lockpassword_password_requires_letters,
- error.requirement, error.requirement));
+ messages.add(StringUtil.getIcuPluralsString(getContext(), error.requirement,
+ R.string.lockpassword_password_requires_letters));
break;
case NOT_ENOUGH_DIGITS:
- messages.add(getResources().getQuantityString(
- R.plurals.lockpassword_password_requires_numeric,
- error.requirement, error.requirement));
+ messages.add(StringUtil.getIcuPluralsString(getContext(), error.requirement,
+ R.string.lockpassword_password_requires_numeric));
break;
case NOT_ENOUGH_SYMBOLS:
- messages.add(getResources().getQuantityString(
- R.plurals.lockpassword_password_requires_symbols,
- error.requirement, error.requirement));
+ messages.add(StringUtil.getIcuPluralsString(getContext(), error.requirement,
+ R.string.lockpassword_password_requires_symbols));
break;
case NOT_ENOUGH_NON_LETTER:
- messages.add(getResources().getQuantityString(
- R.plurals.lockpassword_password_requires_nonletter,
- error.requirement, error.requirement));
+ messages.add(StringUtil.getIcuPluralsString(getContext(), error.requirement,
+ R.string.lockpassword_password_requires_nonletter));
break;
case NOT_ENOUGH_NON_DIGITS:
- messages.add(getResources().getQuantityString(
- R.plurals.lockpassword_password_requires_nonnumerical,
- error.requirement, error.requirement));
+ messages.add(StringUtil.getIcuPluralsString(getContext(), error.requirement,
+ R.string.lockpassword_password_requires_nonnumerical));
break;
case TOO_SHORT:
- messages.add(getResources().getQuantityString(
+ messages.add(StringUtil.getIcuPluralsString(getContext(), error.requirement,
mIsAlphaMode
- ? R.plurals.lockpassword_password_too_short
- : R.plurals.lockpassword_pin_too_short,
- error.requirement, error.requirement));
+ ? R.string.lockpassword_password_too_short
+ : R.string.lockpassword_pin_too_short));
break;
case TOO_SHORT_WHEN_ALL_NUMERIC:
messages.add(
@@ -827,11 +819,10 @@
R.string.lockpassword_password_too_short_all_numeric));
break;
case TOO_LONG:
- messages.add(getResources().getQuantityString(
- mIsAlphaMode
- ? R.plurals.lockpassword_password_too_long
- : R.plurals.lockpassword_pin_too_long,
- error.requirement + 1, error.requirement + 1));
+ messages.add(StringUtil.getIcuPluralsString(getContext(),
+ error.requirement + 1, mIsAlphaMode
+ ? R.string.lockpassword_password_too_long
+ : R.string.lockpassword_pin_too_long));
break;
case CONTAINS_SEQUENCE:
messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
diff --git a/src/com/android/settings/privacy/AccessibilityUsagePreferenceController.java b/src/com/android/settings/privacy/AccessibilityUsagePreferenceController.java
index a36d3ce..a78ccee 100644
--- a/src/com/android/settings/privacy/AccessibilityUsagePreferenceController.java
+++ b/src/com/android/settings/privacy/AccessibilityUsagePreferenceController.java
@@ -25,6 +25,7 @@
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.utils.StringUtil;
import java.util.List;
@@ -60,7 +61,7 @@
@Override
public CharSequence getSummary() {
- return mContext.getResources().getQuantityString(R.plurals.accessibility_usage_summary,
- mEnabledServiceInfos.size(), mEnabledServiceInfos.size());
+ return StringUtil.getIcuPluralsString(mContext, mEnabledServiceInfos.size(),
+ R.string.accessibility_usage_summary);
}
}
diff --git a/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceController.java b/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceController.java
index 9cc50f4..8943878 100644
--- a/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceController.java
+++ b/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceController.java
@@ -26,6 +26,7 @@
import com.android.settings.core.BasePreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.security.SecurityFeatureProvider;
+import com.android.settingslib.utils.StringUtil;
public class ManageTrustAgentsPreferenceController extends BasePreferenceController {
@@ -56,9 +57,8 @@
preference.setSummary(R.string.disabled_because_no_backup_security);
} else if (numberOfTrustAgent > 0) {
preference.setEnabled(true);
- preference.setSummary(mContext.getResources().getQuantityString(
- R.plurals.manage_trust_agents_summary_on,
- numberOfTrustAgent, numberOfTrustAgent));
+ preference.setSummary(StringUtil.getIcuPluralsString(mContext, numberOfTrustAgent,
+ R.string.manage_trust_agents_summary_on));
} else {
preference.setEnabled(true);
preference.setSummary(R.string.manage_trust_agents_summary);
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index bf15ddf..9eab400 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -20,6 +20,7 @@
import com.android.settings.spa.app.AllAppListPageProvider
import com.android.settings.spa.app.AppsMainPageProvider
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
+import com.android.settings.spa.app.backgroundinstall.BackgroundInstalledAppsPageProvider
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
@@ -66,6 +67,7 @@
LanguageAndInputPageProvider,
AppLanguagesPageProvider,
UsageStatsPageProvider,
+ BackgroundInstalledAppsPageProvider,
) + togglePermissionAppListTemplate.createPageProviders(),
rootPages = listOf(
SettingsPage.create(HomePageProvider.name),
diff --git a/src/com/android/settings/spa/app/AppsMain.kt b/src/com/android/settings/spa/app/AppsMain.kt
index 4b47278..e9eca1f 100644
--- a/src/com/android/settings/spa/app/AppsMain.kt
+++ b/src/com/android/settings/spa/app/AppsMain.kt
@@ -22,7 +22,9 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.android.settings.R
+import com.android.settings.spa.app.backgroundinstall.BackgroundInstalledAppsPageProvider
import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
+import com.android.settings.spa.home.HomePageProvider
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
@@ -45,6 +47,7 @@
RegularScaffold(title = getTitle(arguments)) {
AllAppListPageProvider.buildInjectEntry().build().UiLayout()
SpecialAppAccessPageProvider.EntryItem()
+ BackgroundInstalledAppsPageProvider.EntryItem()
}
}
@@ -70,6 +73,7 @@
return listOf(
AllAppListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
SpecialAppAccessPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ BackgroundInstalledAppsPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
}
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index 3a4d3f6..1f7cc4d 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -95,7 +95,7 @@
AppSettingsPreference(app)
AppAllServicesPreference(app)
- // TODO: notification_settings
+ AppNotificationPreference(app)
AppPermissionPreference(app)
AppStoragePreference(app)
InstantAppDomainsPreference(app)
diff --git a/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt b/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt
new file mode 100644
index 0000000..e1792a9
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt
@@ -0,0 +1,67 @@
+/*
+* Copyright (C) 2022 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.settings.spa.app.appinfo
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.settings.R
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.notification.app.AppNotificationSettings
+import com.android.settings.spa.notification.AppNotificationRepository
+import com.android.settings.spa.notification.IAppNotificationRepository
+import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+
+@OptIn(ExperimentalLifecycleComposeApi::class)
+@Composable
+fun AppNotificationPreference(
+ app: ApplicationInfo,
+ repository: IAppNotificationRepository = rememberContext(::AppNotificationRepository),
+) {
+ val context = LocalContext.current
+ val summaryFlow = remember(app) {
+ flow {
+ emit(repository.getNotificationSummary(app))
+ }.flowOn(Dispatchers.IO)
+ }
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.notifications_label)
+ override val summary = summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder)
+ )
+ override val onClick = { navigateToAppNotificationSettings(context, app) }
+ })
+}
+
+private fun navigateToAppNotificationSettings(context: Context, app: ApplicationInfo) {
+ AppInfoDashboardFragment.startAppInfoFragment(
+ AppNotificationSettings::class.java,
+ app,
+ context,
+ AppInfoSettingsProvider.METRICS_CATEGORY,
+ )
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProvider.kt b/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProvider.kt
new file mode 100644
index 0000000..9cf9516
--- /dev/null
+++ b/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProvider.kt
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.backgroundinstall
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.IBackgroundInstallControlService
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.content.pm.ParceledListSlice
+import android.net.Uri
+import android.os.Bundle
+import android.os.ServiceManager
+import android.provider.DeviceConfig
+import android.util.Log
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.produceState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.util.asyncMap
+import com.android.settingslib.spa.framework.util.formatString
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.SettingsBody
+import com.android.settingslib.spaprivileged.model.app.AppEntry
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.template.app.AppList
+import com.android.settingslib.spaprivileged.template.app.AppListButtonItem
+import com.android.settingslib.spaprivileged.template.app.AppListInput
+import com.android.settingslib.spaprivileged.template.app.AppListItemModel
+import com.android.settingslib.spaprivileged.template.app.AppListPage
+import com.google.common.annotations.VisibleForTesting
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.withContext
+
+private const val KEY_GROUPING_MONTH = "key_grouping_by_month"
+const val DEFAULT_GROUPING_MONTH_VALUE = 6
+const val MONTH_IN_MILLIS = 2629800000L
+const val KEY_BIC_UI_ENABLED = "key_bic_ui_enabled"
+const val BACKGROUND_INSTALL_CONTROL_FLAG = PackageManager.MATCH_ALL.toLong()
+
+object BackgroundInstalledAppsPageProvider : SettingsPageProvider {
+ override val name = "BackgroundInstalledAppsPage"
+ private var backgroundInstallService = IBackgroundInstallControlService.Stub.asInterface(
+ ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE))
+ private var featureIsDisabled = featureIsDisabled()
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ if(featureIsDisabled) return
+ BackgroundInstalledAppList()
+ }
+
+ @Composable
+ fun EntryItem() {
+ if(featureIsDisabled) return
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.background_install_title)
+ override val summary = generatePreferenceSummary()
+ override val onClick = navigator(name)
+ })
+ }
+
+ fun buildInjectEntry() = SettingsEntryBuilder
+ .createInject(owner = SettingsPage.create(name))
+ .setSearchDataFn { null }
+ .setUiLayoutFn { EntryItem() }
+
+ private fun featureIsDisabled() = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
+ KEY_BIC_UI_ENABLED, false)
+
+ @Composable
+ private fun generatePreferenceSummary(): State<String> {
+ val context = LocalContext.current
+ return produceState(initialValue = stringResource(R.string.summary_placeholder)) {
+ withContext(Dispatchers.IO) {
+ val backgroundInstalledApps =
+ backgroundInstallService.getBackgroundInstalledPackages(
+ BACKGROUND_INSTALL_CONTROL_FLAG, context.user.identifier
+ ).list.size
+ value = context.formatString(
+ R.string.background_install_preference_summary,
+ "count" to backgroundInstalledApps
+ )
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun setDisableFeature(disableFeature : Boolean): BackgroundInstalledAppsPageProvider {
+ featureIsDisabled = disableFeature
+ return this
+ }
+
+ @VisibleForTesting
+ fun setBackgroundInstallControlService(bic: IBackgroundInstallControlService):
+ BackgroundInstalledAppsPageProvider {
+ backgroundInstallService = bic
+ return this
+ }
+}
+
+@Composable
+fun BackgroundInstalledAppList(
+ appList: @Composable AppListInput<BackgroundInstalledAppListWithGroupingAppRecord>.() -> Unit
+ = { AppList() },
+) {
+ AppListPage(
+ title = stringResource(R.string.background_install_title),
+ listModel = rememberContext(::BackgroundInstalledAppsWithGroupingListModel),
+ noItemMessage = stringResource(R.string.background_install_feature_list_no_entry),
+ appList = appList,
+ header = {
+ Box(Modifier.padding(SettingsDimension.itemPadding)) {
+ SettingsBody(stringResource(R.string.background_install_summary))
+ }
+ }
+ )
+}
+/*
+Based on PackageManagerService design, and it looks like the suggested replacement in the deprecate
+notes suggest that we use PackageInstaller.uninstall which does not guarantee a pop up would open
+and depends on the calling application. Seems like further investigation is needed before we can
+move over to the new API.
+ */
+@Suppress
+@VisibleForTesting
+fun startUninstallActivity(context: Context,
+ packageName: String,
+ forAllUsers: Boolean = false) {
+ val packageUri = Uri.parse("package:${packageName}")
+ val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri).apply {
+ putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, forAllUsers)
+ }
+ context.startActivityAsUser(intent, context.user)
+}
+
+data class BackgroundInstalledAppListWithGroupingAppRecord(
+ override val app: ApplicationInfo,
+ val dateOfInstall: Long,
+) : AppRecord
+
+class BackgroundInstalledAppsWithGroupingListModel(private val context: Context)
+ : AppListModel<BackgroundInstalledAppListWithGroupingAppRecord> {
+
+ companion object {
+ private const val tag = "AppListModel<BackgroundInstalledAppListWithGroupingAppRecord>"
+ }
+
+ private var backgroundInstallService = IBackgroundInstallControlService.Stub.asInterface(
+ ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE))
+
+ @VisibleForTesting
+ fun setBackgroundInstallControlService(bic: IBackgroundInstallControlService) {
+ backgroundInstallService = bic
+ }
+ @Composable
+ override fun AppListItemModel<BackgroundInstalledAppListWithGroupingAppRecord>.AppItem() {
+ val context = LocalContext.current
+ AppListButtonItem(
+ onClick = AppInfoSettingsProvider.navigator(app = record.app),
+ onButtonClick = { startUninstallActivity(context, record.app.packageName) },
+ buttonIcon = Icons.Outlined.Delete,
+ buttonIconDescription = stringResource(
+ R.string.background_install_uninstall_button_description))
+ }
+
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ userIdFlow.combine(appListFlow) { userId, appList ->
+ appList.asyncMap { app ->
+ BackgroundInstalledAppListWithGroupingAppRecord(
+ app = app,
+ dateOfInstall = context.packageManager.getPackageInfoAsUser(app.packageName,
+ PackageManager.PackageInfoFlags.of(0), userId).firstInstallTime
+ )
+ }
+ }
+
+ @Composable
+ override fun getSummary(option: Int, record: BackgroundInstalledAppListWithGroupingAppRecord)
+ = null
+
+ @Suppress
+ override fun filter(
+ userIdFlow: Flow<Int>,
+ option: Int,
+ recordListFlow: Flow<List<BackgroundInstalledAppListWithGroupingAppRecord>>
+ ): Flow<List<BackgroundInstalledAppListWithGroupingAppRecord>> {
+ if(backgroundInstallService == null) {
+ Log.e(tag, "Failed to retrieve Background Install Control Service")
+ return flowOf()
+ }
+ return userIdFlow.combine(recordListFlow) { userId, recordList ->
+ val appList = (backgroundInstallService.getBackgroundInstalledPackages(
+ PackageManager.MATCH_ALL.toLong(), userId) as ParceledListSlice<PackageInfo>).list
+ val appNameList = appList.map { it.packageName }
+ recordList.filter { record -> record.app.packageName in appNameList }
+ }
+ }
+
+ override fun getComparator(
+ option: Int,
+ ): Comparator<AppEntry<BackgroundInstalledAppListWithGroupingAppRecord>> =
+ compareByDescending { it.record.dateOfInstall }
+
+ override fun getGroupTitle(option: Int, record: BackgroundInstalledAppListWithGroupingAppRecord)
+ : String {
+ val groupByMonth = getGroupSeparationByMonth()
+ return when (record.dateOfInstall > System.currentTimeMillis()
+ - (groupByMonth * MONTH_IN_MILLIS)) {
+ true -> context.formatString(R.string.background_install_before, "count" to groupByMonth)
+ else -> context.formatString(R.string.background_install_after, "count" to groupByMonth)
+ }
+ }
+}
+
+private fun getGroupSeparationByMonth(): Int {
+ val month = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SETTINGS_UI, KEY_GROUPING_MONTH)
+ return try {
+ if (month.isNullOrBlank()) {
+ DEFAULT_GROUPING_MONTH_VALUE
+ } else {
+ month.toInt()
+ }
+ } catch (e: Exception) {
+ Log.d(
+ BackgroundInstalledAppsPageProvider.name, "Error parsing list grouping value: " +
+ "${e.message} falling back to default value: $DEFAULT_GROUPING_MONTH_VALUE")
+ DEFAULT_GROUPING_MONTH_VALUE
+ }
+}
diff --git a/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt b/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt
index 5efe322..6466e03 100644
--- a/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt
+++ b/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt
@@ -34,4 +34,5 @@
override val footerResId = R.string.allow_manage_external_storage_description
override val appOp = AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE
override val permission = Manifest.permission.MANAGE_EXTERNAL_STORAGE
+ override val setModeByUid = true
}
diff --git a/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt b/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt
index 92e369d..6c7678a 100644
--- a/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt
+++ b/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt
@@ -34,4 +34,5 @@
override val footerResId = R.string.media_management_apps_description
override val appOp = AppOpsManager.OP_MANAGE_MEDIA
override val permission = Manifest.permission.MANAGE_MEDIA
+ override val setModeByUid = true
}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/notification/AppNotificationRepository.kt b/src/com/android/settings/spa/notification/AppNotificationRepository.kt
index 7ec5d3e..fe8babb 100644
--- a/src/com/android/settings/spa/notification/AppNotificationRepository.kt
+++ b/src/com/android/settings/spa/notification/AppNotificationRepository.kt
@@ -31,8 +31,10 @@
import android.os.ServiceManager
import android.util.Log
import com.android.settings.R
+import com.android.settingslib.spa.framework.util.formatString
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.userId
import java.util.concurrent.TimeUnit
import kotlin.math.max
import kotlin.math.roundToInt
@@ -50,6 +52,11 @@
var sentCount: Int = 0,
)
+interface IAppNotificationRepository {
+ /** Gets the notification summary for the given application. */
+ fun getNotificationSummary(app: ApplicationInfo): String
+}
+
class AppNotificationRepository(
private val context: Context,
private val packageManagers: IPackageManagers = PackageManagers,
@@ -59,7 +66,7 @@
private val notificationManager: INotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE)
),
-) {
+) : IAppNotificationRepository {
fun getAggregatedUsageEvents(userIdFlow: Flow<Int>): Flow<Map<String, NotificationSentState>> =
userIdFlow.map { userId ->
val aggregatedStats = mutableMapOf<String, NotificationSentState>()
@@ -115,6 +122,58 @@
}
}
+ override fun getNotificationSummary(app: ApplicationInfo): String {
+ if (!isEnabled(app)) return context.getString(R.string.off)
+ val channelCount = getChannelCount(app)
+ if (channelCount == 0) {
+ return calculateFrequencySummary(getSentCount(app))
+ }
+ val blockedChannelCount = getBlockedChannelCount(app)
+ if (channelCount == blockedChannelCount) return context.getString(R.string.off)
+ val frequencySummary = calculateFrequencySummary(getSentCount(app))
+ if (blockedChannelCount == 0) return frequencySummary
+ return context.getString(
+ R.string.notifications_enabled_with_info,
+ frequencySummary,
+ context.formatString(
+ R.string.notifications_categories_off, "count" to blockedChannelCount
+ )
+ )
+ }
+
+ private fun getSentCount(app: ApplicationInfo): Int {
+ var sentCount = 0
+ queryEventsForPackageForUser(app).forEachNotificationEvent { sentCount++ }
+ return sentCount
+ }
+
+ private fun queryEventsForPackageForUser(app: ApplicationInfo): UsageEvents? {
+ val now = System.currentTimeMillis()
+ val startTime = now - TimeUnit.DAYS.toMillis(DAYS_TO_CHECK)
+ return try {
+ usageStatsManager.queryEventsForPackageForUser(
+ startTime, now, app.userId, app.packageName, context.packageName
+ )
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed IUsageStatsManager.queryEventsForPackageForUser(): ", e)
+ null
+ }
+ }
+
+ private fun getChannelCount(app: ApplicationInfo): Int = try {
+ notificationManager.getNumNotificationChannelsForPackage(app.packageName, app.uid, false)
+ } catch (e: Exception) {
+ Log.w(TAG, "Error calling INotificationManager", e)
+ 0
+ }
+
+ private fun getBlockedChannelCount(app: ApplicationInfo): Int = try {
+ notificationManager.getBlockedChannelCount(app.packageName, app.uid)
+ } catch (e: Exception) {
+ Log.w(TAG, "Error calling INotificationManager", e)
+ 0
+ }
+
fun calculateFrequencySummary(sentCount: Int): String {
val dailyFrequency = (sentCount.toFloat() / DAYS_TO_CHECK).roundToInt()
return if (dailyFrequency > 0) {
diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java
index 7728b3e..9b4a4b9 100644
--- a/src/com/android/settings/users/UserDetailsSettings.java
+++ b/src/com/android/settings/users/UserDetailsSettings.java
@@ -61,6 +61,7 @@
private static final String KEY_SWITCH_USER = "switch_user";
private static final String KEY_ENABLE_TELEPHONY = "enable_calling";
private static final String KEY_REMOVE_USER = "remove_user";
+ private static final String KEY_GRANT_ADMIN = "user_grant_admin";
private static final String KEY_APP_AND_CONTENT_ACCESS = "app_and_content_access";
private static final String KEY_APP_COPYING = "app_copying";
@@ -72,6 +73,7 @@
private static final int DIALOG_SETUP_USER = 3;
private static final int DIALOG_CONFIRM_RESET_GUEST = 4;
private static final int DIALOG_CONFIRM_RESET_GUEST_AND_SWITCH_USER = 5;
+ private static final int DIALOG_CONFIRM_REVOKE_ADMIN = 6;
/** Whether to enable the app_copying fragment. */
private static final boolean SHOW_APP_COPYING_PREF = false;
@@ -91,6 +93,8 @@
Preference mAppCopyingPref;
@VisibleForTesting
Preference mRemoveUserPref;
+ @VisibleForTesting
+ SwitchPreference mGrantAdminPref;
@VisibleForTesting
/** The user being studied (not the user doing the studying). */
@@ -179,6 +183,12 @@
mMetricsFeatureProvider.action(getActivity(),
SettingsEnums.ACTION_DISABLE_USER_CALL);
enableCallsAndSms(false);
+ } else if (preference == mGrantAdminPref) {
+ if (Boolean.FALSE.equals(newValue)) {
+ showDialog(DIALOG_CONFIRM_REVOKE_ADMIN);
+ return false;
+ }
+ updateUserAdminStatus(true);
}
return true;
}
@@ -192,6 +202,8 @@
return SettingsEnums.DIALOG_USER_REMOVE;
case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS:
return SettingsEnums.DIALOG_USER_ENABLE_CALLING_AND_SMS;
+ case DIALOG_CONFIRM_REVOKE_ADMIN:
+ return SettingsEnums.DIALOG_REVOKE_USER_ADMIN;
case DIALOG_SETUP_USER:
return SettingsEnums.DIALOG_USER_SETUP;
default:
@@ -235,6 +247,9 @@
return UserDialogs.createRemoveGuestDialog(getActivity(),
(dialog, which) -> switchUser());
}
+ case DIALOG_CONFIRM_REVOKE_ADMIN:
+ return UserDialogs.createConfirmRevokeAdmin(getActivity(),
+ (dialog, which) -> updateUserAdminStatus(false));
}
throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
}
@@ -277,6 +292,9 @@
mRemoveUserPref = findPreference(KEY_REMOVE_USER);
mAppAndContentAccessPref = findPreference(KEY_APP_AND_CONTENT_ACCESS);
mAppCopyingPref = findPreference(KEY_APP_COPYING);
+ mGrantAdminPref = findPreference(KEY_GRANT_ADMIN);
+
+ mGrantAdminPref.setChecked(mUserInfo.isAdmin());
mSwitchUserPref.setTitle(
context.getString(com.android.settingslib.R.string.user_switch_to_user,
@@ -289,10 +307,15 @@
mSwitchUserPref.setSelectable(true);
mSwitchUserPref.setOnPreferenceClickListener(this);
}
-
+ //TODO(b/261700461): remove preference for supervised user
+ //TODO(b/262371063): check whether multiple admins allowed, not for HSUM
+ if (mUserInfo.isMain() || mUserInfo.isGuest() || !UserManager.isHeadlessSystemUserMode()) {
+ removePreference(KEY_GRANT_ADMIN);
+ }
if (!mUserManager.isAdminUser()) { // non admin users can't remove users and allow calls
removePreference(KEY_ENABLE_TELEPHONY);
removePreference(KEY_REMOVE_USER);
+ removePreference(KEY_GRANT_ADMIN);
removePreference(KEY_APP_AND_CONTENT_ACCESS);
removePreference(KEY_APP_COPYING);
} else {
@@ -339,6 +362,7 @@
mRemoveUserPref.setOnPreferenceClickListener(this);
mPhonePref.setOnPreferenceChangeListener(this);
+ mGrantAdminPref.setOnPreferenceChangeListener(this);
mAppAndContentAccessPref.setOnPreferenceClickListener(this);
mAppCopyingPref.setOnPreferenceClickListener(this);
}
@@ -401,6 +425,20 @@
mUserManager.setUserRestriction(UserManager.DISALLOW_SMS, !enabled, userHandle);
}
+ /**
+ * Sets admin status of selected user. Method is called when toggle in
+ * user details settings is switched.
+ * @param isSetAdmin indicates if user admin status needs to be set to true.
+ */
+ private void updateUserAdminStatus(boolean isSetAdmin) {
+ mGrantAdminPref.setChecked(isSetAdmin);
+ if (!isSetAdmin) {
+ mUserManager.revokeUserAdmin(mUserInfo.id);
+ } else if ((mUserInfo.flags & UserInfo.FLAG_ADMIN) == 0) {
+ mUserManager.setUserAdmin(mUserInfo.id);
+ }
+ }
+
private void removeUser() {
mUserManager.removeUser(mUserInfo.id);
finishFragment();
diff --git a/src/com/android/settings/users/UserDialogs.java b/src/com/android/settings/users/UserDialogs.java
index c2f2528..1e26eff 100644
--- a/src/com/android/settings/users/UserDialogs.java
+++ b/src/com/android/settings/users/UserDialogs.java
@@ -202,4 +202,19 @@
.setNegativeButton(android.R.string.cancel, null)
.create();
}
+
+ /**
+ * Creates a dialog to confirm that the admin privileges of the user should be revoked.
+ *
+ * @param onConfirmListener Callback object for positive action
+ */
+ public static Dialog createConfirmRevokeAdmin(Context context,
+ DialogInterface.OnClickListener onConfirmListener) {
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.user_revoke_admin_confirm_title)
+ .setMessage(R.string.user_revoke_admin_confirm_message)
+ .setPositiveButton(R.string.remove, onConfirmListener)
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ }
}
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index da6339b..54d0b45 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -84,6 +84,7 @@
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw;
import com.android.settingslib.users.EditUserInfoController;
+import com.android.settingslib.users.GrantAdminDialogController;
import com.android.settingslib.users.UserCreatingDialog;
import com.android.settingslib.utils.ThreadUtils;
@@ -156,6 +157,7 @@
private static final int DIALOG_CONFIRM_RESET_AND_RESTART_GUEST = 13;
private static final int DIALOG_CONFIRM_EXIT_GUEST_EPHEMERAL = 14;
private static final int DIALOG_CONFIRM_EXIT_GUEST_NON_EPHEMERAL = 15;
+ private static final int DIALOG_GRANT_ADMIN = 16;
private static final int MESSAGE_UPDATE_LIST = 1;
private static final int MESSAGE_USER_CREATED = 2;
@@ -215,6 +217,9 @@
private static SparseArray<Bitmap> sDarkDefaultUserBitmapCache = new SparseArray<>();
private MultiUserSwitchBarController mSwitchBarController;
+
+ private GrantAdminDialogController mGrantAdminDialogController =
+ new GrantAdminDialogController();
private EditUserInfoController mEditUserInfoController =
new EditUserInfoController(Utils.FILE_PROVIDER_AUTHORITY);
private AddUserWhenLockedPreferenceController mAddUserWhenLockedPreferenceController;
@@ -228,6 +233,7 @@
private CharSequence mPendingUserName;
private Drawable mPendingUserIcon;
+ private boolean mGrantAdmin;
// A place to cache the generated default avatar
private Drawable mDefaultIconDrawable;
@@ -287,7 +293,6 @@
mSwitchBarController = new MultiUserSwitchBarController(activity,
new MainSwitchBarController(switchBar), this /* listener */);
getSettingsLifecycle().addObserver(mSwitchBarController);
-
boolean openUserEditDialog = getIntent().getBooleanExtra(
EXTRA_OPEN_DIALOG_USER_PROFILE_EDITOR, false);
if (switchBar.isChecked() && openUserEditDialog) {
@@ -306,7 +311,7 @@
}
mGuestUserAutoCreated = getPrefContext().getResources().getBoolean(
- com.android.internal.R.bool.config_guestUserAutoCreated);
+ com.android.internal.R.bool.config_guestUserAutoCreated);
mAddUserWhenLockedPreferenceController = new AddUserWhenLockedPreferenceController(
activity, KEY_ADD_USER_WHEN_LOCKED);
@@ -712,18 +717,27 @@
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
- showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_USER);
if (!longMessageDisplayed) {
preferences.edit().putBoolean(
KEY_ADD_USER_LONG_MESSAGE_DISPLAYED,
true).apply();
}
+ //TODO(b/262371063): check whether multiple admins allowed,
+ // not for HSUM
+ if (UserManager.isHeadlessSystemUserMode()) {
+ showDialog(DIALOG_GRANT_ADMIN);
+ } else {
+ showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_USER);
+ }
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
return dlg;
}
+ case DIALOG_GRANT_ADMIN: {
+ return buildGrantAdminDialog();
+ }
case DIALOG_CHOOSE_USER_TYPE: {
List<HashMap<String, String>> data = new ArrayList<HashMap<String, String>>();
HashMap<String, String> addUserItem = new HashMap<String, String>();
@@ -931,6 +945,19 @@
return d;
}
+ private Dialog buildGrantAdminDialog() {
+ return mGrantAdminDialogController.createDialog(
+ getActivity(),
+ (grantAdmin) -> {
+ mGrantAdmin = grantAdmin;
+ showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_USER);
+ },
+ () -> {
+ mGrantAdmin = false;
+ }
+ );
+ }
+
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
@@ -938,6 +965,8 @@
return SettingsEnums.DIALOG_USER_REMOVE;
case DIALOG_USER_CANNOT_MANAGE:
return SettingsEnums.DIALOG_USER_CANNOT_MANAGE;
+ case DIALOG_GRANT_ADMIN:
+ return SettingsEnums.DIALOG_GRANT_USER_ADMIN;
case DIALOG_ADD_USER:
return SettingsEnums.DIALOG_USER_ADD;
case DIALOG_CHOOSE_USER_TYPE:
@@ -1031,6 +1060,9 @@
userName,
mUserManager.USER_TYPE_FULL_SECONDARY,
0);
+ if (mGrantAdmin) {
+ mUserManager.setUserAdmin(user.id);
+ }
} else {
user = mUserManager.createRestrictedProfile(userName);
}
@@ -1351,20 +1383,20 @@
mGuestResetPreference.setVisible(true);
boolean isGuestFirstLogin = Settings.Secure.getIntForUser(
- getContext().getContentResolver(),
- SETTING_GUEST_HAS_LOGGED_IN,
- 0,
- UserHandle.myUserId()) <= 1;
+ getContext().getContentResolver(),
+ SETTING_GUEST_HAS_LOGGED_IN,
+ 0,
+ UserHandle.myUserId()) <= 1;
String guestExitSummary;
if (mUserCaps.mIsEphemeral) {
guestExitSummary = getContext().getString(
- R.string.guest_notification_ephemeral);
+ R.string.guest_notification_ephemeral);
} else if (isGuestFirstLogin) {
guestExitSummary = getContext().getString(
- R.string.guest_notification_non_ephemeral);
+ R.string.guest_notification_non_ephemeral);
} else {
guestExitSummary = getContext().getString(
- R.string.guest_notification_non_ephemeral_non_first_login);
+ R.string.guest_notification_non_ephemeral_non_first_login);
}
mGuestExitPreference.setSummary(guestExitSummary);
}
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index fbed5d2..2a16664 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -75,6 +75,7 @@
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.search.Indexable;
import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.wifi.WifiSavedConfigUtils;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiEntry.ConnectCallback;
@@ -915,17 +916,19 @@
private String getSavedNetworkSettingsSummaryText(
int numSavedNetworks, int numSavedSubscriptions) {
+ if (getContext() == null) {
+ return "";
+ }
if (numSavedSubscriptions == 0) {
- return getResources().getQuantityString(R.plurals.wifi_saved_access_points_summary,
- numSavedNetworks, numSavedNetworks);
+ return StringUtil.getIcuPluralsString(getContext(), numSavedNetworks,
+ R.string.wifi_saved_access_points_summary);
} else if (numSavedNetworks == 0) {
- return getResources().getQuantityString(
- R.plurals.wifi_saved_passpoint_access_points_summary,
- numSavedSubscriptions, numSavedSubscriptions);
+ return StringUtil.getIcuPluralsString(getContext(), numSavedSubscriptions,
+ R.string.wifi_saved_passpoint_access_points_summary);
} else {
final int numTotalEntries = numSavedNetworks + numSavedSubscriptions;
- return getResources().getQuantityString(R.plurals.wifi_saved_all_access_points_summary,
- numTotalEntries, numTotalEntries);
+ return StringUtil.getIcuPluralsString(getContext(), numTotalEntries,
+ R.string.wifi_saved_all_access_points_summary);
}
}
diff --git a/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java
index c73bffa..522d697 100644
--- a/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java
+++ b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java
@@ -376,15 +376,17 @@
final WifiCallingQueryImsState queryState = queryImsState(subId);
if (queryState.isWifiCallingProvisioned()) {
final boolean currentValue = isWifiCallingEnabled();
- final boolean newValue = intent.getBooleanExtra(EXTRA_TOGGLE_STATE,
- currentValue);
+ final boolean newValue = !(intent.getBooleanExtra(EXTRA_TOGGLE_STATE,
+ currentValue));
final Intent activationAppIntent =
getWifiCallingCarrierActivityIntent(subId);
- if ((newValue == currentValue) && activationAppIntent == null) {
+ // 1. If activationApp is not null, users only can turn off WFC, or
+ // 2. Turn on/off directly if there is no activationApp.
+ if ((newValue != currentValue) && (activationAppIntent == null || !newValue)) {
// If either the action is to turn off wifi calling setting
// or there is no activation involved - Update the setting
final ImsMmTelManager imsMmTelManager = getImsMmTelManager(subId);
- imsMmTelManager.setVoWiFiSettingEnabled(!newValue);
+ imsMmTelManager.setVoWiFiSettingEnabled(newValue);
} else {
Log.w(TAG, "action not taken: subId " + subId
+ " from " + currentValue + " to " + newValue);
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
index 40e6494..a35ef45 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
@@ -29,6 +29,9 @@
import android.content.Context;
import android.content.pm.PackageManager;
+import android.hardware.input.InputManager;
+import android.util.FeatureFlagUtils;
+import android.view.InputDevice;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
@@ -37,6 +40,7 @@
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
import com.android.settings.connecteddevice.dock.DockUpdater;
+import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
@@ -68,9 +72,13 @@
@Mock
private DockUpdater mConnectedDockUpdater;
@Mock
+ private StylusDeviceUpdater mStylusDeviceUpdater;
+ @Mock
private PreferenceScreen mPreferenceScreen;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private PreferenceManager mPreferenceManager;
+ @Mock
+ private InputManager mInputManager;
private ShadowApplicationPackageManager mPackageManager;
private PreferenceGroup mPreferenceGroup;
@@ -82,7 +90,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = spy(RuntimeEnvironment.application);
mPreference = new Preference(mContext);
mPreference.setKey(PREFERENCE_KEY_1);
mPackageManager = (ShadowApplicationPackageManager) Shadows.shadowOf(
@@ -91,11 +99,16 @@
when(mPreferenceGroup.getPreferenceManager()).thenReturn(mPreferenceManager);
doReturn(mContext).when(mDashboardFragment).getContext();
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
+ when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
+ when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{});
mConnectedDeviceGroupController = new ConnectedDeviceGroupController(mContext);
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
- mConnectedUsbDeviceUpdater, mConnectedDockUpdater);
+ mConnectedUsbDeviceUpdater, mConnectedDockUpdater, mStylusDeviceUpdater);
mConnectedDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
+
+ FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES,
+ true);
}
@Test
@@ -146,6 +159,7 @@
verify(mConnectedUsbDeviceUpdater).registerCallback();
verify(mConnectedDockUpdater).registerCallback();
verify(mConnectedBluetoothDeviceUpdater).refreshPreference();
+ verify(mStylusDeviceUpdater).registerCallback();
}
@Test
@@ -155,6 +169,7 @@
verify(mConnectedBluetoothDeviceUpdater).unregisterCallback();
verify(mConnectedUsbDeviceUpdater).unregisterCallback();
verify(mConnectedDockUpdater).unregisterCallback();
+ verify(mStylusDeviceUpdater).unregisterCallback();
}
@Test
@@ -163,7 +178,7 @@
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
- mConnectedUsbDeviceUpdater, null);
+ mConnectedUsbDeviceUpdater, null, null);
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
UNSUPPORTED_ON_DEVICE);
@@ -175,7 +190,7 @@
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
- mConnectedUsbDeviceUpdater, null);
+ mConnectedUsbDeviceUpdater, null, null);
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
AVAILABLE_UNSEARCHABLE);
@@ -187,7 +202,7 @@
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, true);
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
- mConnectedUsbDeviceUpdater, null);
+ mConnectedUsbDeviceUpdater, null, null);
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
AVAILABLE_UNSEARCHABLE);
@@ -199,7 +214,40 @@
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
- mConnectedUsbDeviceUpdater, mConnectedDockUpdater);
+ mConnectedUsbDeviceUpdater, mConnectedDockUpdater, null);
+
+ assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
+ AVAILABLE_UNSEARCHABLE);
+ }
+
+
+ @Test
+ public void getAvailabilityStatus_noUsiStylusFeature_returnUnSupported() {
+ mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
+ mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
+ mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
+ when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{0});
+ when(mInputManager.getInputDevice(0)).thenReturn(new InputDevice.Builder().setSources(
+ InputDevice.SOURCE_DPAD).setExternal(false).build());
+
+ mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
+ mConnectedUsbDeviceUpdater, null, mStylusDeviceUpdater);
+
+ assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
+ UNSUPPORTED_ON_DEVICE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_haveUsiStylusFeature_returnSupported() {
+ mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
+ mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
+ mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
+ when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{0});
+ when(mInputManager.getInputDevice(0)).thenReturn(new InputDevice.Builder().setSources(
+ InputDevice.SOURCE_STYLUS).setExternal(false).build());
+
+ mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
+ mConnectedUsbDeviceUpdater, mConnectedDockUpdater, mStylusDeviceUpdater);
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
AVAILABLE_UNSEARCHABLE);
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java
new file mode 100644
index 0000000..10f3727
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdaterTest.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.stylus;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.BatteryState;
+import android.hardware.input.InputManager;
+import android.os.SystemClock;
+import android.view.InputDevice;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.connecteddevice.DevicePreferenceCallback;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+
+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;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class StylusDeviceUpdaterTest {
+
+ private Context mContext;
+ private StylusDeviceUpdater mStylusDeviceUpdater;
+ private InputDevice mStylusDevice;
+ private InputDevice mOtherDevice;
+
+ @Mock
+ private SettingsActivity mSettingsActivity;
+ @Mock
+ private DashboardFragment mDashboardFragment;
+ @Mock
+ private DevicePreferenceCallback mDevicePreferenceCallback;
+ @Mock
+ private InputManager mInputManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = spy(RuntimeEnvironment.application);
+
+ doReturn(mContext).when(mDashboardFragment).getContext();
+ doReturn(mInputManager).when(mContext).getSystemService(InputManager.class);
+ doReturn(new int[]{}).when(mInputManager).getInputDeviceIds();
+
+ mStylusDeviceUpdater = spy(
+ new StylusDeviceUpdater(mContext, mDashboardFragment, mDevicePreferenceCallback));
+ mStylusDeviceUpdater.setPreferenceContext(mContext);
+
+ doReturn(new int[]{0, 1}).when(mInputManager).getInputDeviceIds();
+ mOtherDevice = new InputDevice.Builder().setId(0).setName("other").setSources(
+ InputDevice.SOURCE_DPAD).build();
+ doReturn(mOtherDevice).when(mInputManager).getInputDevice(0);
+ mStylusDevice = new InputDevice.Builder().setId(1).setName("Pen").setExternal(
+ false).setSources(
+ InputDevice.SOURCE_STYLUS).build();
+ doReturn(mStylusDevice).when(mInputManager).getInputDevice(1);
+ }
+
+ @Test
+ public void registerCallback_registersBatteryListener() {
+ mStylusDeviceUpdater.registerCallback();
+
+ verify(mInputManager, times(1)).addInputDeviceBatteryListener(eq(1), any(),
+ any());
+ }
+
+ @Test
+ public void registerCallback_registersInputDeviceListener() {
+ mStylusDeviceUpdater.registerCallback();
+
+ verify(mInputManager, times(1)).registerInputDeviceListener(eq(mStylusDeviceUpdater),
+ any());
+ }
+
+ @Test
+ public void onInputDeviceAdded_internalStylus_registersBatteryListener() {
+ mStylusDeviceUpdater.onInputDeviceAdded(1);
+
+ verify(mInputManager, times(1)).addInputDeviceBatteryListener(eq(1), any(),
+ any());
+ }
+
+ @Test
+ public void onInputDeviceAdded_nonStylus_doesNotRegisterBatteryListener() {
+ mStylusDeviceUpdater.onInputDeviceAdded(0);
+
+ verify(mInputManager, never()).addInputDeviceBatteryListener(eq(1), any(),
+ any());
+ }
+
+ @Test
+ public void click_usiPreference_launchUsiDetailsPage() {
+ doReturn(mSettingsActivity).when(mDashboardFragment).getContext();
+ doReturn(true).when(mStylusDeviceUpdater).isUsiConnectionValid();
+ doReturn(false).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+ mStylusDeviceUpdater.forceUpdate();
+ mStylusDeviceUpdater.mLastDetectedUsiId = 1;
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ mStylusDeviceUpdater.mUsiPreference.performClick();
+
+ assertThat(mStylusDeviceUpdater.mUsiPreference.getTitle().toString()).isEqualTo(
+ mContext.getString(R.string.stylus_connected_devices_title));
+ verify(mSettingsActivity).startActivity(intentCaptor.capture());
+ assertThat(intentCaptor.getValue().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(StylusUsiDetailsFragment.class.getName());
+ }
+
+ @Test
+ public void forceUpdate_addsUsiPreference_validUsiDevice() {
+ doReturn(true).when(mStylusDeviceUpdater).isUsiConnectionValid();
+ doReturn(false).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+
+ mStylusDeviceUpdater.forceUpdate();
+
+ assertThat(mStylusDeviceUpdater.mUsiPreference).isNotNull();
+ }
+
+ @Test
+ public void forceUpdate_doesNotAddPreference_invalidUsiDevice() {
+ doReturn(false).when(mStylusDeviceUpdater).isUsiConnectionValid();
+ doReturn(false).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+
+ mStylusDeviceUpdater.forceUpdate();
+
+ assertThat(mStylusDeviceUpdater.mUsiPreference).isNull();
+ }
+
+ @Test
+ public void forceUpdate_removesUsiPreference_existingPreference_invalidUsiDevice() {
+ doReturn(true).when(mStylusDeviceUpdater).isUsiConnectionValid();
+ doReturn(false).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+
+ mStylusDeviceUpdater.forceUpdate();
+
+ doReturn(false).when(mStylusDeviceUpdater).isUsiConnectionValid();
+ mStylusDeviceUpdater.forceUpdate();
+
+ assertThat(mStylusDeviceUpdater.mUsiPreference).isNull();
+ }
+
+ @Test
+ public void forceUpdate_doesNotAddUsiPreference_bluetoothStylusConnected() {
+ doReturn(true).when(mStylusDeviceUpdater).isUsiConnectionValid();
+ doReturn(true).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+
+ mStylusDeviceUpdater.forceUpdate();
+
+ assertThat(mStylusDeviceUpdater.mUsiPreference).isNull();
+ }
+
+ @Test
+ public void forceUpdate_addsUsiPreference_bluetoothStylusDisconnected() {
+ doReturn(true).when(mStylusDeviceUpdater).isUsiConnectionValid();
+ doReturn(true).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+ mStylusDeviceUpdater.forceUpdate();
+
+ doReturn(false).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+ mStylusDeviceUpdater.forceUpdate();
+
+ assertThat(mStylusDeviceUpdater.mUsiPreference).isNotNull();
+ }
+
+ @Test
+ public void forceUpdate_removesUsiPreference_existingPreference_bluetoothStylusConnected() {
+ doReturn(true).when(mStylusDeviceUpdater).isUsiConnectionValid();
+ doReturn(false).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+ mStylusDeviceUpdater.forceUpdate();
+ doReturn(true).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+
+ mStylusDeviceUpdater.forceUpdate();
+
+ assertThat(mStylusDeviceUpdater.mUsiPreference).isNull();
+ }
+
+ @Test
+ public void onBatteryStateChanged_detectsValidUsi() {
+ BatteryState batteryState = mock(BatteryState.class);
+ doReturn(true).when(batteryState).isPresent();
+ doReturn(0.5f).when(batteryState).getCapacity();
+
+ mStylusDeviceUpdater.onBatteryStateChanged(1, SystemClock.uptimeMillis(),
+ batteryState);
+
+ assertThat(mStylusDeviceUpdater.isUsiConnectionValid()).isTrue();
+ }
+
+ @Test
+ public void onBatteryStateChanged_detectsInvalidUsi_batteryNotPresent() {
+ doReturn(false).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+ BatteryState batteryState = mock(BatteryState.class);
+ doReturn(false).when(batteryState).isPresent();
+
+ mStylusDeviceUpdater.onBatteryStateChanged(1, SystemClock.uptimeMillis(),
+ batteryState);
+
+ assertThat(mStylusDeviceUpdater.isUsiConnectionValid()).isFalse();
+ }
+
+ @Test
+ public void onBatteryStateChanged_ddetectsInvalidUsi_staleBatteryEventTime() {
+ doReturn(false).when(mStylusDeviceUpdater).hasConnectedBluetoothStylusDevice();
+ BatteryState batteryState = mock(BatteryState.class);
+ doReturn(true).when(batteryState).isPresent();
+ doReturn(0.5f).when(batteryState).getCapacity();
+
+ mStylusDeviceUpdater.onBatteryStateChanged(1, 0, batteryState);
+
+ assertThat(mStylusDeviceUpdater.isUsiConnectionValid()).isFalse();
+ }
+
+ @Test
+ public void detectsConnectedBluetoothStylus() {
+ InputDevice stylusDevice = new InputDevice.Builder().setId(1).setName("Pen").setSources(
+ InputDevice.SOURCE_STYLUS)
+ .build();
+ doReturn(stylusDevice).when(mInputManager).getInputDevice(1);
+ doReturn("04:52:C7:0B:D8:3C").when(mInputManager).getInputDeviceBluetoothAddress(1);
+
+ assertThat(mStylusDeviceUpdater.hasConnectedBluetoothStylusDevice()).isTrue();
+ }
+
+ @Test
+ public void detectsDisconnectedBluetoothStylus() {
+ InputDevice stylusDevice = new InputDevice.Builder().setId(1).setName("Pen").setSources(
+ InputDevice.SOURCE_STYLUS).build();
+ doReturn(stylusDevice).when(mInputManager).getInputDevice(1);
+ doReturn(null).when(mInputManager).getInputDeviceBluetoothAddress(1);
+
+ assertThat(mStylusDeviceUpdater.hasConnectedBluetoothStylusDevice()).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderControllerTest.java
index 27b1de5..3aad02e 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderControllerTest.java
@@ -109,7 +109,7 @@
assertThat(((TextView) mLayoutPreference.findViewById(
R.id.entity_header_title)).getText().toString()).isEqualTo(
- mContext.getString(R.string.stylus_usi_header_title));
+ mContext.getString(R.string.stylus_connected_devices_title));
}
@Test
diff --git a/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java
index 61fca2c..41aca50 100644
--- a/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java
@@ -16,6 +16,7 @@
package com.android.settings.datetime;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
@@ -36,10 +37,13 @@
import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneDetectorStatus;
import android.content.Context;
+import android.location.LocationManager;
import android.os.UserHandle;
import androidx.preference.Preference;
+import com.android.settings.R;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,6 +62,8 @@
private Preference mPreference;
@Mock
private TimeManager mTimeManager;
+ @Mock
+ private LocationManager mLocationManager;
@Before
public void setUp() {
@@ -67,6 +73,9 @@
mPreference = new Preference(mContext);
when(mContext.getSystemService(TimeManager.class)).thenReturn(mTimeManager);
+ when(mContext.getSystemService(LocationManager.class)).thenReturn(mLocationManager);
+
+ when(mLocationManager.isLocationEnabled()).thenReturn(true);
}
@Test
@@ -221,10 +230,35 @@
assertThat(controller.isEnabled()).isFalse();
}
+ @Test
+ public void getSummary() {
+ AutoTimeZonePreferenceController controller = new AutoTimeZonePreferenceController(
+ mContext, mCallback, false /* fromSUW */);
+
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(
+ /* autoSupported= */true, /* autoEnabled= */true, /* telephonySupported= */
+ true);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+ when(mTimeManager.updateTimeZoneConfiguration(Mockito.any())).thenReturn(true);
+
+ assertThat(controller.getSummary()).isEqualTo("");
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(
+ /* autoSupported= */true, /* autoEnabled= */true, /* telephonySupported= */
+ false);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+ when(mTimeManager.updateTimeZoneConfiguration(Mockito.any())).thenReturn(true);
+
+ assertThat(controller.getSummary()).isEqualTo(
+ mContext.getString(R.string.auto_zone_requires_location_summary));
+ }
+
private static TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
- boolean autoSupported, boolean autoEnabled) {
+ boolean autoSupported, boolean autoEnabled, boolean telephonySupported) {
TimeZoneDetectorStatus status = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
- new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING),
+ new TelephonyTimeZoneAlgorithmStatus(
+ telephonySupported ? DETECTION_ALGORITHM_STATUS_RUNNING
+ : DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED),
new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
PROVIDER_STATUS_NOT_READY, null,
PROVIDER_STATUS_NOT_PRESENT, null));
@@ -242,4 +276,9 @@
.build();
return new TimeZoneCapabilitiesAndConfig(status, capabilities, config);
}
+
+ private static TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
+ boolean autoSupported, boolean autoEnabled) {
+ return createCapabilitiesAndConfig(autoSupported, autoEnabled, false);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/datetime/LocationProviderStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/LocationProviderStatusPreferenceControllerTest.java
new file mode 100644
index 0000000..4f9f1cc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/datetime/LocationProviderStatusPreferenceControllerTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.datetime;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.time.Capabilities;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
+import android.app.time.TimeManager;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
+import android.content.Context;
+import android.location.LocationManager;
+import android.os.UserHandle;
+import android.service.timezone.TimeZoneProviderStatus;
+
+import androidx.annotation.Nullable;
+
+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.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class LocationProviderStatusPreferenceControllerTest {
+
+ private Context mContext;
+ @Mock
+ private TimeManager mTimeManager;
+ @Mock
+ private LocationManager mLocationManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+
+ when(mContext.getSystemService(TimeManager.class)).thenReturn(mTimeManager);
+ when(mContext.getSystemService(LocationManager.class)).thenReturn(mLocationManager);
+ when(mLocationManager.isLocationEnabled()).thenReturn(true);
+ when(mContext.getString(
+ R.string.location_time_zone_detection_status_summary_blocked_by_settings))
+ .thenReturn("BBS");
+ }
+
+ @Test
+ public void testCapabilityStatus() {
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(false,
+ DEPENDENCY_STATUS_OK, DEPENDENCY_STATUS_OK);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+ LocationProviderStatusPreferenceController controller =
+ new LocationProviderStatusPreferenceController(mContext, "LPSPC");
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(true, DEPENDENCY_STATUS_OK,
+ DEPENDENCY_STATUS_OK);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(false,
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, DEPENDENCY_STATUS_OK);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE_UNSEARCHABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(true,
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, DEPENDENCY_STATUS_OK);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE_UNSEARCHABLE);
+ }
+
+ @Test
+ public void testProviderStatus_primaryCertain() {
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(false,
+ DEPENDENCY_STATUS_OK, DEPENDENCY_STATUS_OK);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+ LocationProviderStatusPreferenceController controller =
+ new LocationProviderStatusPreferenceController(mContext, "LPSPC");
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(false,
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, DEPENDENCY_STATUS_OK);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE_UNSEARCHABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(false, DEPENDENCY_STATUS_OK,
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(false, DEPENDENCY_STATUS_OK,
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void testProviderStatus_primaryUncertain() {
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(false,
+ DEPENDENCY_STATUS_OK, DEPENDENCY_STATUS_OK, PROVIDER_STATUS_IS_CERTAIN);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+ LocationProviderStatusPreferenceController controller =
+ new LocationProviderStatusPreferenceController(mContext, "LPSPC");
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(false,
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, DEPENDENCY_STATUS_OK,
+ PROVIDER_STATUS_IS_CERTAIN);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE_UNSEARCHABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(false, DEPENDENCY_STATUS_OK,
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, PROVIDER_STATUS_IS_UNCERTAIN);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE_UNSEARCHABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(false, DEPENDENCY_STATUS_OK,
+ DEPENDENCY_STATUS_OK, PROVIDER_STATUS_IS_UNCERTAIN);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void testProviderStatus_nullProviders() {
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(false,
+ null, null);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+ LocationProviderStatusPreferenceController controller =
+ new LocationProviderStatusPreferenceController(mContext, "LPSPC");
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(false,
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS, null);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE_UNSEARCHABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(false, DEPENDENCY_STATUS_OK, null);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+
+ capabilitiesAndConfig = createCapabilitiesAndConfig(false, null,
+ DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS);
+ when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE_UNSEARCHABLE);
+ }
+
+ private TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(boolean capabilitySupported,
+ @Nullable Integer primary, @Nullable Integer secondary) {
+ return createCapabilitiesAndConfig(capabilitySupported, primary, secondary,
+ PROVIDER_STATUS_IS_CERTAIN);
+ }
+
+ private TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(boolean capabilitySupported,
+ @Nullable Integer primary, @Nullable Integer secondary, int primaryProviderStatus) {
+ TimeZoneDetectorStatus status = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING),
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ primaryProviderStatus, primary != null
+ ? new TimeZoneProviderStatus.Builder().setLocationDetectionDependencyStatus(
+ primary).build() : null, PROVIDER_STATUS_IS_CERTAIN, secondary != null
+ ? new TimeZoneProviderStatus.Builder().setLocationDetectionDependencyStatus(
+ secondary).build() : null));
+
+ TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(
+ UserHandle.SYSTEM).setConfigureAutoDetectionEnabledCapability(
+ Capabilities.CAPABILITY_POSSESSED).setConfigureGeoDetectionEnabledCapability(
+ capabilitySupported ? Capabilities.CAPABILITY_POSSESSED
+ : Capabilities.CAPABILITY_NOT_SUPPORTED).setSetManualTimeZoneCapability(
+ Capabilities.CAPABILITY_POSSESSED).build();
+
+ return new TimeZoneCapabilitiesAndConfig(status, capabilities,
+ new TimeZoneConfiguration.Builder().build());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceControllerTest.java
index eea07fe..5cfe404 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceControllerTest.java
@@ -34,6 +34,8 @@
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.Observer;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
@@ -46,6 +48,7 @@
import java.util.List;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -125,7 +128,7 @@
@Test
public void displayPreference_multiSim_shouldAddSecondPreference() {
when(mTelephonyManager.getPhoneCount()).thenReturn(2);
- SlotSimStatus slotSimStatus = new SlotSimStatus(mContext);
+ SlotSimStatus slotSimStatus = new TestSlotSimStatus(mContext);
mController.init(mFragment, slotSimStatus);
mController.displayPreference(mScreen);
@@ -133,10 +136,11 @@
verify(mCategory).addPreference(mSecondSimPreference);
}
+ @Ignore
@Test
public void updateState_singleSim_shouldSetSingleSimTitleAndSummary() {
when(mTelephonyManager.getPhoneCount()).thenReturn(1);
- SlotSimStatus slotSimStatus = new SlotSimStatus(mContext);
+ SlotSimStatus slotSimStatus = new TestSlotSimStatus(mContext);
mController.init(mFragment, slotSimStatus);
mController.displayPreference(mScreen);
@@ -146,10 +150,11 @@
verify(mFirstSimPreference).setSummary(anyString());
}
+ @Ignore
@Test
public void updateState_multiSim_shouldSetMultiSimTitleAndSummary() {
when(mTelephonyManager.getPhoneCount()).thenReturn(2);
- SlotSimStatus slotSimStatus = new SlotSimStatus(mContext);
+ SlotSimStatus slotSimStatus = new TestSlotSimStatus(mContext);
mController.init(mFragment, slotSimStatus);
mController.displayPreference(mScreen);
@@ -163,12 +168,13 @@
verify(mSecondSimPreference).setSummary(anyString());
}
+ @Ignore
@Test
public void handlePreferenceTreeClick_shouldStartDialogFragment() {
when(mFragment.getChildFragmentManager()).thenReturn(
mock(FragmentManager.class, Answers.RETURNS_DEEP_STUBS));
when(mTelephonyManager.getPhoneCount()).thenReturn(2);
- SlotSimStatus slotSimStatus = new SlotSimStatus(mContext);
+ SlotSimStatus slotSimStatus = new TestSlotSimStatus(mContext);
mController.init(mFragment, slotSimStatus);
mController.displayPreference(mScreen);
@@ -180,7 +186,7 @@
@Test
public void updateDynamicRawDataToIndex_notAddToSearch_emptySimSlot() {
doReturn(null).when(mSubscriptionManager).getActiveSubscriptionInfoList();
- SlotSimStatus slotSimStatus = new SlotSimStatus(mContext);
+ SlotSimStatus slotSimStatus = new TestSlotSimStatus(mContext);
List<SearchIndexableRaw> rawData = new ArrayList<SearchIndexableRaw>();
mController.init(mFragment, slotSimStatus);
@@ -191,10 +197,11 @@
@Test
public void updateDynamicRawDataToIndex_addToSearch_simInSimSlot() {
+ when(mTelephonyManager.getPhoneCount()).thenReturn(1);
doReturn(false).when(mSubscriptionInfo).isEmbedded();
doReturn(List.of(mSubscriptionInfo)).when(mSubscriptionManager)
.getActiveSubscriptionInfoList();
- SlotSimStatus slotSimStatus = new SlotSimStatus(mContext);
+ SlotSimStatus slotSimStatus = new TestSlotSimStatus(mContext);
List<SearchIndexableRaw> rawData = new ArrayList<SearchIndexableRaw>();
mController.init(mFragment, slotSimStatus);
@@ -203,23 +210,16 @@
assertThat(rawData.size()).isEqualTo(1);
}
- @Test
- public void updateDynamicRawDataToIndex_addEsimToSearch_esimInSimSlot() {
- doReturn(true).when(mSubscriptionInfo).isEmbedded();
- doReturn(List.of(mSubscriptionInfo)).when(mSubscriptionManager)
- .getActiveSubscriptionInfoList();
- SlotSimStatus slotSimStatus = new SlotSimStatus(mContext);
- List<SearchIndexableRaw> rawData = new ArrayList<SearchIndexableRaw>();
-
- mController.init(mFragment, slotSimStatus);
- mController.updateDynamicRawDataToIndex(rawData);
-
- assertThat(rawData.size()).isEqualTo(1);
- assertThat(rawData.get(0).keywords.contains("eid")).isTrue();
- }
-
private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName);
when(mContext.getSystemService(serviceName)).thenReturn(service);
}
+
+ private class TestSlotSimStatus extends SlotSimStatus {
+ public TestSlotSimStatus(Context context) {
+ super(context);
+ }
+
+ public void observe(LifecycleOwner owner, Observer observer) {}
+ }
}
diff --git a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java
index 06e4107..77f5d13 100644
--- a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java
+++ b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java
@@ -33,6 +33,7 @@
import com.android.settings.R;
import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.utils.StringUtil;
import org.junit.Before;
import org.junit.Test;
@@ -80,6 +81,8 @@
}).when(mFeatureFactory.applicationFeatureProvider)
.calculateNumberOfAppsWithAdminGrantedPermissions(eq(mPermissions),
eq(async), any());
+ when(mContext.getResources().getString(R.string.enterprise_privacy_number_packages_lower_bound))
+ .thenReturn("Minimum # apps");
}
@Test
@@ -92,8 +95,8 @@
assertThat(preference.isVisible()).isFalse();
setNumberOfPackagesWithAdminGrantedPermissions(20, true /* async */);
- when(mContext.getResources().getQuantityString(
- R.plurals.enterprise_privacy_number_packages_lower_bound, 20, 20))
+ when(StringUtil.getIcuPluralsString(mContext, 20,
+ R.string.enterprise_privacy_number_packages_lower_bound))
.thenReturn("minimum 20 apps");
mController.updateState(preference);
assertThat(preference.getSummary()).isEqualTo("minimum 20 apps");
diff --git a/tests/robotests/src/com/android/settings/enterprise/CaCertsPreferenceControllerTestBase.java b/tests/robotests/src/com/android/settings/enterprise/CaCertsPreferenceControllerTestBase.java
index e26915b..96bfff4 100644
--- a/tests/robotests/src/com/android/settings/enterprise/CaCertsPreferenceControllerTestBase.java
+++ b/tests/robotests/src/com/android/settings/enterprise/CaCertsPreferenceControllerTestBase.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -29,6 +31,7 @@
import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.utils.StringUtil;
import org.junit.Before;
import org.junit.Test;
@@ -59,8 +62,10 @@
public void testUpdateState() {
final Preference preference = new Preference(mContext, null, 0, 0);
- when(mContext.getResources().getQuantityString(R.plurals.enterprise_privacy_number_ca_certs,
- 10, 10)).thenReturn("10 certs");
+ when(mContext.getResources().getString(R.string.enterprise_privacy_number_ca_certs))
+ .thenReturn("# certs");
+ when(StringUtil.getIcuPluralsString(mContext, 10,
+ R.string.enterprise_privacy_number_ca_certs)).thenReturn("10 certs");
mockGetNumberOfCaCerts(10);
mController.updateState(preference);
assertThat(preference.getSummary()).isEqualTo("10 certs");
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
index c32d2d7..c823c1d 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
@@ -33,6 +33,7 @@
import com.android.settings.R;
import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.utils.StringUtil;
import org.junit.Before;
import org.junit.Test;
@@ -75,6 +76,8 @@
}
}).when(mFeatureFactory.applicationFeatureProvider)
.calculateNumberOfPolicyInstalledApps(eq(async), any());
+ when(mContext.getResources().getString(R.string.enterprise_privacy_number_packages_lower_bound))
+ .thenReturn("Minimum # apps");
}
@Test
@@ -87,8 +90,8 @@
assertThat(preference.isVisible()).isFalse();
setNumberOfEnterpriseInstalledPackages(20, true /* async */);
- when(mContext.getResources().getQuantityString(
- R.plurals.enterprise_privacy_number_packages_lower_bound, 20, 20))
+ when(StringUtil.getIcuPluralsString(mContext, 20,
+ R.string.enterprise_privacy_number_packages_lower_bound))
.thenReturn("minimum 20 apps");
mController.updateState(preference);
assertThat(preference.getSummary()).isEqualTo("minimum 20 apps");
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceControllerTest.java
index 7a2aa5c..b1e2c0c 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceControllerTest.java
@@ -93,7 +93,7 @@
when(mContext.getString(R.string.default_browser_title)).thenReturn(BROWSER_TITLE);
Resources resources = spy(mContext.getResources());
when(mContext.getResources()).thenReturn(resources);
- when(resources.getQuantityString(R.plurals.default_phone_app_title, 2))
+ when(resources.getString(R.string.default_phone_app_title))
.thenReturn(PHONE_TITLE);
when(mContext.getString(R.string.app_names_concatenation_template_2))
.thenReturn("%1$s, %2$s");
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java
index 7a38feb..02d1f64 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java
@@ -39,6 +39,7 @@
import com.android.settings.applications.EnterpriseDefaultApps;
import com.android.settings.applications.UserAppInfo;
import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.utils.StringUtil;
import org.junit.Before;
import org.junit.Test;
@@ -101,8 +102,10 @@
setEnterpriseSetDefaultApps(EnterpriseDefaultApps.CALENDAR.getIntents(), 16);
setEnterpriseSetDefaultApps(EnterpriseDefaultApps.CONTACTS.getIntents(), 32);
setEnterpriseSetDefaultApps(EnterpriseDefaultApps.PHONE.getIntents(), 64);
- when(mContext.getResources().getQuantityString(R.plurals.enterprise_privacy_number_packages,
- 127, 127)).thenReturn("127 apps");
+ when(mContext.getResources().getString(R.string.enterprise_privacy_number_packages))
+ .thenReturn("# apps");
+ when(StringUtil.getIcuPluralsString(mContext, 127,
+ R.string.enterprise_privacy_number_packages)).thenReturn("127 apps");
// As setEnterpriseSetDefaultApps uses fake Users, we need to list them via UserManager.
configureUsers(64);
diff --git a/tests/robotests/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerTestBase.java b/tests/robotests/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerTestBase.java
index 03854bf..b82919e 100644
--- a/tests/robotests/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerTestBase.java
+++ b/tests/robotests/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerTestBase.java
@@ -29,6 +29,7 @@
import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.utils.StringUtil;
import org.junit.Before;
import org.junit.Test;
@@ -68,8 +69,10 @@
final Preference preference = new Preference(mContext, null, 0, 0);
setMaximumFailedPasswordsBeforeWipe(10);
- when(mContext.getResources().getQuantityString(
- R.plurals.enterprise_privacy_number_failed_password_wipe, 10, 10))
+ when(mContext.getResources().getString(
+ R.string.enterprise_privacy_number_failed_password_wipe)).thenReturn("# attempts");
+ when(StringUtil.getIcuPluralsString(mContext, 10,
+ R.string.enterprise_privacy_number_failed_password_wipe))
.thenReturn("10 attempts");
mController.updateState(preference);
assertThat(preference.getSummary()).isEqualTo("10 attempts");
diff --git a/tests/robotests/src/com/android/settings/enterprise/ManageDeviceAdminPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/ManageDeviceAdminPreferenceControllerTest.java
index 492a863..a2a0de4 100644
--- a/tests/robotests/src/com/android/settings/enterprise/ManageDeviceAdminPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/ManageDeviceAdminPreferenceControllerTest.java
@@ -28,6 +28,7 @@
import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.utils.StringUtil;
import org.junit.Before;
import org.junit.Test;
@@ -70,7 +71,8 @@
when(mFeatureFactory.enterprisePrivacyFeatureProvider
.getNumberOfActiveDeviceAdminsForCurrentUserAndManagedProfile()).thenReturn(5);
- when(mResources.getQuantityString(R.plurals.number_of_device_admins, 5, 5))
+ when(mResources.getString(R.string.number_of_device_admins)).thenReturn("# active apps");
+ when(StringUtil.getIcuPluralsString(mContext, 5, R.string.number_of_device_admins))
.thenReturn("5 active apps");
mController.updateState(preference);
assertThat(preference.getSummary()).isEqualTo("5 active apps");
diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java
index a26ef8c..52d1083 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java
@@ -174,7 +174,7 @@
}
private void initializeOneLayout() {
- final KeyboardLayout[] keyboardLayouts = {new KeyboardLayout("", "", "", 1, null, 1, 1)};
+ final KeyboardLayout[] keyboardLayouts = {new KeyboardLayout("", "", "", 1, null, 0, 1, 1)};
when(mInputManager.getKeyboardLayoutsForInputDevice(
any(InputDeviceIdentifier.class))).thenReturn(
keyboardLayouts);
@@ -183,8 +183,8 @@
}
private void initializeTwoLayouts() {
- final KeyboardLayout[] keyboardLayouts = {new KeyboardLayout("", "", "", 1, null, 1, 1),
- new KeyboardLayout("", "", "", 2, null, 2, 2)};
+ final KeyboardLayout[] keyboardLayouts = {new KeyboardLayout("", "", "", 1, null, 0, 1, 1),
+ new KeyboardLayout("", "", "", 2, null, 0, 2, 2)};
when(mInputManager.getKeyboardLayoutsForInputDevice(any(InputDeviceIdentifier.class))).
thenReturn(keyboardLayouts);
diff --git a/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java
index 4303489..df9dc27 100644
--- a/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java
@@ -13,6 +13,7 @@
import com.android.settings.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.utils.StringUtil;
import org.junit.Before;
import org.junit.Test;
@@ -22,6 +23,9 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import java.util.HashMap;
+import java.util.Map;
+
@RunWith(RobolectricTestRunner.class)
public class AppLocationPermissionPreferenceControllerTest {
@@ -86,8 +90,11 @@
mController.mNumHasLocation = 1;
mController.mNumTotal = 1;
- assertThat(mController.getSummary()).isEqualTo(mContext.getResources().getQuantityString(
- R.plurals.location_app_permission_summary_location_on, 1, 1, 1));
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", 1);
+ arguments.put("total", 1);
+ assertThat(mController.getSummary()).isEqualTo(StringUtil.getIcuPluralsString(mContext,
+ arguments, R.string.location_app_permission_summary_location_on));
}
@Test
@@ -96,7 +103,10 @@
mController.mNumHasLocation = 5;
mController.mNumTotal = 10;
- assertThat(mController.getSummary()).isEqualTo(mContext.getResources().getQuantityString(
- R.plurals.location_app_permission_summary_location_on, 5, 5, 10));
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", 5);
+ arguments.put("total", 10);
+ assertThat(mController.getSummary()).isEqualTo(StringUtil.getIcuPluralsString(mContext,
+ arguments, R.string.location_app_permission_summary_location_on));
}
}
diff --git a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
index 5573ca3..cd75bef 100644
--- a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
@@ -75,6 +75,7 @@
import com.android.settings.wifi.LongPressWifiEntryPreference;
import com.android.settings.wifi.WifiConfigController2;
import com.android.settings.wifi.WifiDialog2;
+import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.LayoutPreference;
import com.android.wifitrackerlib.WifiEntry;
@@ -206,9 +207,8 @@
assertThat(mNetworkProviderSettings.mSavedNetworksPreference.isVisible()).isTrue();
assertThat(mNetworkProviderSettings.mSavedNetworksPreference.getSummary()).isEqualTo(
- mContext.getResources().getQuantityString(
- R.plurals.wifi_saved_access_points_summary,
- NUM_NETWORKS, NUM_NETWORKS));
+ StringUtil.getIcuPluralsString(mContext, NUM_NETWORKS,
+ R.string.wifi_saved_access_points_summary));
}
@Test
@@ -220,9 +220,8 @@
assertThat(mNetworkProviderSettings.mSavedNetworksPreference.isVisible()).isTrue();
assertThat(mNetworkProviderSettings.mSavedNetworksPreference.getSummary()).isEqualTo(
- mContext.getResources().getQuantityString(
- R.plurals.wifi_saved_passpoint_access_points_summary,
- NUM_NETWORKS, NUM_NETWORKS));
+ StringUtil.getIcuPluralsString(mContext, NUM_NETWORKS,
+ R.string.wifi_saved_passpoint_access_points_summary));
}
@Test
@@ -234,9 +233,8 @@
assertThat(mNetworkProviderSettings.mSavedNetworksPreference.isVisible()).isTrue();
assertThat(mNetworkProviderSettings.mSavedNetworksPreference.getSummary()).isEqualTo(
- mContext.getResources().getQuantityString(
- R.plurals.wifi_saved_all_access_points_summary,
- NUM_NETWORKS * 2, NUM_NETWORKS * 2));
+ StringUtil.getIcuPluralsString(mContext, NUM_NETWORKS * 2,
+ R.string.wifi_saved_all_access_points_summary));
}
@Test
diff --git a/tests/robotests/src/com/android/settings/TetherSettingsTest.java b/tests/robotests/src/com/android/settings/network/tether/TetherSettingsTest.java
similarity index 96%
rename from tests/robotests/src/com/android/settings/TetherSettingsTest.java
rename to tests/robotests/src/com/android/settings/network/tether/TetherSettingsTest.java
index 79814b3..2aeb44f 100644
--- a/tests/robotests/src/com/android/settings/TetherSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/network/tether/TetherSettingsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings;
+package com.android.settings.network.tether;
import static android.content.Intent.ACTION_MEDIA_SHARED;
import static android.content.Intent.ACTION_MEDIA_UNSHARED;
@@ -53,6 +53,7 @@
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
+import com.android.settings.R;
import com.android.settings.core.FeatureFlags;
import com.android.settingslib.RestrictedSwitchPreference;
@@ -141,7 +142,7 @@
when(mTetheringManager.getTetherableUsbRegexs()).thenReturn(new String[0]);
final List<String> niks =
- TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext);
+ TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext);
assertThat(niks).contains(TetherSettings.KEY_USB_TETHER_SETTINGS);
}
@@ -154,7 +155,7 @@
when(mTetheringManager.getTetherableUsbRegexs()).thenReturn(new String[]{"fakeRegex"});
final List<String> niks =
- TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext);
+ TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext);
assertThat(niks).doesNotContain(TetherSettings.KEY_USB_TETHER_SETTINGS);
}
@@ -164,7 +165,7 @@
when(mTetheringManager.getTetherableBluetoothRegexs()).thenReturn(new String[0]);
final List<String> niks =
- TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext);
+ TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext);
assertThat(niks).contains(TetherSettings.KEY_ENABLE_BLUETOOTH_TETHERING);
}
@@ -176,7 +177,7 @@
.thenReturn(new String[]{"fakeRegex"});
final List<String> niks =
- TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext);
+ TetherSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext);
assertThat(niks).doesNotContain(TetherSettings.KEY_ENABLE_BLUETOOTH_TETHERING);
}
@@ -362,7 +363,7 @@
}
private void updateOnlyBluetoothState(TetherSettings tetherSettings) {
- doReturn(mTetheringManager).when(tetherSettings)
+ doReturn(mTetheringManager).when(mContext)
.getSystemService(Context.TETHERING_SERVICE);
when(mTetheringManager.getTetherableIfaces()).thenReturn(new String[0]);
when(mTetheringManager.getTetheredIfaces()).thenReturn(new String[0]);
diff --git a/tests/robotests/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceControllerTest.java
index dde2f5d..33a62f4 100644
--- a/tests/robotests/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceControllerTest.java
@@ -29,6 +29,7 @@
import com.android.settings.R;
import com.android.settings.security.trustagent.TrustAgentManager.TrustAgentComponentInfo;
import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.utils.StringUtil;
import org.junit.Before;
import org.junit.Test;
@@ -114,8 +115,8 @@
assertThat(mPreference.isEnabled()).isTrue();
assertThat(mPreference.getSummary())
- .isEqualTo(mContext.getResources().getQuantityString(
- R.plurals.manage_trust_agents_summary_on, 1, 1));
+ .isEqualTo(StringUtil.getIcuPluralsString(mContext, 1,
+ R.string.manage_trust_agents_summary_on));
}
@Test
diff --git a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java
index 14ca76f..5cd513e 100644
--- a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java
@@ -22,6 +22,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -84,6 +85,7 @@
})
public class UserDetailsSettingsTest {
+ private static final String KEY_GRANT_ADMIN = "user_grant_admin";
private static final String KEY_SWITCH_USER = "switch_user";
private static final String KEY_ENABLE_TELEPHONY = "enable_calling";
private static final String KEY_REMOVE_USER = "remove_user";
@@ -103,6 +105,8 @@
@Mock
private SwitchPreference mPhonePref;
@Mock
+ private SwitchPreference mGrantAdminPref;
+ @Mock
private Preference mRemoveUserPref;
@Mock
private Preference mAppAndContentAccessPref;
@@ -144,6 +148,7 @@
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
doReturn(mSwitchUserPref).when(mFragment).findPreference(KEY_SWITCH_USER);
+ doReturn(mGrantAdminPref).when(mFragment).findPreference(KEY_GRANT_ADMIN);
doReturn(mPhonePref).when(mFragment).findPreference(KEY_ENABLE_TELEPHONY);
doReturn(mRemoveUserPref).when(mFragment).findPreference(KEY_REMOVE_USER);
doReturn(mAppAndContentAccessPref)
@@ -678,6 +683,38 @@
assertThat(result).isFalse();
}
+ @Test
+ public void initialize_userSelected_shouldShowGrantAdminPref_HSUM() {
+ setupSelectedUser();
+ ShadowUserManager.setIsHeadlessSystemUserMode(true);
+ mFragment.initialize(mActivity, mArguments);
+ assertTrue(UserManager.isHeadlessSystemUserMode());
+ verify(mFragment, never()).removePreference(KEY_GRANT_ADMIN);
+ }
+
+ @Test
+ public void initialize_userSelected_shouldNotShowGrantAdminPref() {
+ setupSelectedUser();
+ mFragment.initialize(mActivity, mArguments);
+ verify(mFragment).removePreference(KEY_GRANT_ADMIN);
+ }
+
+ @Test
+ public void initialize_mainUserSelected_shouldShowGrantAdminPref_HSUM() {
+ setupSelectedMainUser();
+ ShadowUserManager.setIsHeadlessSystemUserMode(true);
+ mFragment.initialize(mActivity, mArguments);
+ verify(mFragment).removePreference(KEY_GRANT_ADMIN);
+ }
+
+ @Test
+ public void initialize_guestSelected_shouldNotShowGrantAdminPref_HSUM() {
+ setupSelectedGuest();
+ ShadowUserManager.setIsHeadlessSystemUserMode(true);
+ mFragment.initialize(mActivity, mArguments);
+ verify(mFragment).removePreference(KEY_GRANT_ADMIN);
+ }
+
private void setupSelectedUser() {
mArguments.putInt("user_id", 1);
mUserInfo = new UserInfo(1, "Tom", null,
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/WifiSettingsTest.java
index b7c7270..17d8099 100644
--- a/tests/robotests/src/com/android/settings/wifi/WifiSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/WifiSettingsTest.java
@@ -56,6 +56,7 @@
import com.android.settings.datausage.DataUsagePreference;
import com.android.settings.testutils.shadow.ShadowDataUsageUtils;
import com.android.settings.testutils.shadow.ShadowFragment;
+import com.android.settingslib.utils.StringUtil;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
@@ -130,9 +131,8 @@
assertThat(mWifiSettings.mSavedNetworksPreference.isVisible()).isTrue();
assertThat(mWifiSettings.mSavedNetworksPreference.getSummary()).isEqualTo(
- mContext.getResources().getQuantityString(
- R.plurals.wifi_saved_access_points_summary,
- NUM_NETWORKS, NUM_NETWORKS));
+ StringUtil.getIcuPluralsString(mContext, NUM_NETWORKS,
+ R.string.wifi_saved_access_points_summary));
}
@Test
@@ -144,9 +144,8 @@
assertThat(mWifiSettings.mSavedNetworksPreference.isVisible()).isTrue();
assertThat(mWifiSettings.mSavedNetworksPreference.getSummary()).isEqualTo(
- mContext.getResources().getQuantityString(
- R.plurals.wifi_saved_passpoint_access_points_summary,
- NUM_NETWORKS, NUM_NETWORKS));
+ StringUtil.getIcuPluralsString(mContext, NUM_NETWORKS,
+ R.string.wifi_saved_passpoint_access_points_summary));
}
@Test
@@ -158,9 +157,8 @@
assertThat(mWifiSettings.mSavedNetworksPreference.isVisible()).isTrue();
assertThat(mWifiSettings.mSavedNetworksPreference.getSummary()).isEqualTo(
- mContext.getResources().getQuantityString(
- R.plurals.wifi_saved_all_access_points_summary,
- NUM_NETWORKS*2, NUM_NETWORKS*2));
+ StringUtil.getIcuPluralsString(mContext, NUM_NETWORKS * 2,
+ R.string.wifi_saved_all_access_points_summary));
}
@Test
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppNotificationPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppNotificationPreferenceTest.kt
new file mode 100644
index 0000000..c54d35f
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppNotificationPreferenceTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.appinfo
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.R
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.notification.app.AppNotificationSettings
+import com.android.settings.spa.notification.IAppNotificationRepository
+import com.android.settingslib.spa.testutils.delay
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoSession
+import org.mockito.Spy
+import org.mockito.quality.Strictness
+
+@RunWith(AndroidJUnit4::class)
+class AppNotificationPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private lateinit var mockSession: MockitoSession
+
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val repository = object : IAppNotificationRepository {
+ override fun getNotificationSummary(app: ApplicationInfo) = SUMMARY
+ }
+
+ @Before
+ fun setUp() {
+ mockSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .mockStatic(AppInfoDashboardFragment::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ }
+
+ @After
+ fun tearDown() {
+ mockSession.finishMocking()
+ }
+
+ @Test
+ fun title_displayed() {
+ setContent()
+
+ composeTestRule.onNodeWithText(context.getString(R.string.notifications_label))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun summary_displayed() {
+ setContent()
+
+ composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+ }
+
+ @Test
+ fun onClick_startActivity() {
+ setContent()
+
+ composeTestRule.onRoot().performClick()
+ composeTestRule.delay()
+
+ ExtendedMockito.verify {
+ AppInfoDashboardFragment.startAppInfoFragment(
+ AppNotificationSettings::class.java,
+ APP,
+ context,
+ AppInfoSettingsProvider.METRICS_CATEGORY,
+ )
+ }
+ }
+
+ private fun setContent() {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ AppNotificationPreference(app = APP, repository = repository)
+ }
+ }
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ const val UID = 123
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = UID
+ }
+ const val SUMMARY = "Summary"
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt
new file mode 100644
index 0000000..8e1757f
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.backgroundinstall
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.IBackgroundInstallControlService
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.content.pm.ParceledListSlice
+import android.net.Uri
+import android.os.UserHandle
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
+import com.android.settingslib.spa.testutils.any
+import com.android.settingslib.spaprivileged.template.app.AppListItemModel
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@RunWith(AndroidJUnit4::class)
+class BackgroundInstalledAppsPageProviderTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var mockContext: Context
+
+ @Mock
+ private lateinit var mockPackageManager: PackageManager
+
+ @Mock
+ private lateinit var mockBackgroundInstallControlService: IBackgroundInstallControlService
+
+ private var packageInfoFlagsCaptor =
+ ArgumentCaptor.forClass(PackageManager.PackageInfoFlags::class.java)
+
+ private var intentCaptor =
+ ArgumentCaptor.forClass(Intent::class.java)
+
+ private val fakeNavControllerWrapper = FakeNavControllerWrapper()
+
+ @Before
+ fun setup() {
+ Mockito.`when`(mockContext.packageManager).thenReturn(mockPackageManager)
+ }
+ @Test
+ fun allAppListPageProvider_name() {
+ Truth.assertThat(BackgroundInstalledAppsPageProvider.name)
+ .isEqualTo(EXPECTED_PROVIDER_NAME)
+ }
+
+ @Test
+ fun injectEntry_title() {
+ Mockito.`when`(mockBackgroundInstallControlService
+ .getBackgroundInstalledPackages(any(Long::class.java), any(Int::class.java)))
+ .thenReturn(ParceledListSlice(listOf()))
+ setInjectEntry(false)
+
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.background_install_title)).assertIsDisplayed()
+ }
+
+ @Test
+ fun injectEntry_title_disabled() {
+ setInjectEntry(true)
+
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.background_install_title)).assertDoesNotExist()
+ }
+
+ @Test
+ fun injectEntry_summary() {
+ Mockito.`when`(mockBackgroundInstallControlService
+ .getBackgroundInstalledPackages(any(Long::class.java), any(Int::class.java)))
+ .thenReturn(ParceledListSlice(listOf()))
+ setInjectEntry(false)
+
+ composeTestRule.onNodeWithText("0 apps").assertIsDisplayed()
+ }
+
+ @Test
+ fun injectEntry_summary_disabled() {
+ setInjectEntry(true)
+
+ composeTestRule.onNodeWithText("0 apps").assertDoesNotExist()
+ }
+
+ @Test
+ fun injectEntry_onClick_navigate() {
+ Mockito.`when`(mockBackgroundInstallControlService
+ .getBackgroundInstalledPackages(any(Long::class.java), any(Int::class.java)))
+ .thenReturn(ParceledListSlice(listOf()))
+ setInjectEntry(false)
+
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.background_install_title)).performClick()
+
+ Truth.assertThat(fakeNavControllerWrapper.navigateCalledWith)
+ .isEqualTo(EXPECTED_PROVIDER_NAME)
+ }
+
+ private fun setInjectEntry(disableFeature: Boolean = false) {
+ composeTestRule.setContent {
+ fakeNavControllerWrapper.Wrapper {
+ BackgroundInstalledAppsPageProvider
+ .setBackgroundInstallControlService(mockBackgroundInstallControlService)
+ .setDisableFeature(disableFeature)
+ .buildInjectEntry().build().UiLayout()
+ }
+ }
+ }
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ BackgroundInstalledAppList()
+ }
+
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.background_install_title)).assertIsDisplayed()
+ }
+
+ @Test
+ fun item_labelDisplayed() {
+ setItemContent()
+
+ composeTestRule.onNodeWithText(TEST_LABEL).assertIsDisplayed()
+ }
+
+ @Test
+ fun item_onClick_navigate() {
+ setItemContent()
+
+ composeTestRule.onNodeWithText(TEST_LABEL).performClick()
+
+ Truth.assertThat(fakeNavControllerWrapper.navigateCalledWith)
+ .isEqualTo("AppInfoSettings/package.name/0")
+ }
+
+ @Suppress
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun startUninstallActivity_success() = runTest {
+ val expectedPackageUri = Uri.parse("package:package.name")
+ val mockUserHandle = UserHandle(0)
+ Mockito.`when`(mockContext.user).thenReturn(mockUserHandle)
+ Mockito.`when`(mockContext.startActivityAsUser(
+ intentCaptor.capture(),
+ eq(mockUserHandle)
+ )).then { }
+
+ startUninstallActivity(mockContext, TEST_PACKAGE_NAME)
+
+ Truth.assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_UNINSTALL_PACKAGE)
+ Truth.assertThat(intentCaptor.value.data).isEqualTo(expectedPackageUri)
+ Truth.assertThat(intentCaptor.value.extras?.getBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS))
+ .isEqualTo(false)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun backgroundInstalledAppsWithGroupingListModel_getGroupTitleOne() = runTest {
+ val listModel = BackgroundInstalledAppsWithGroupingListModel(context)
+
+ val actualGroupTitle = listModel
+ .getGroupTitle(0,
+ BackgroundInstalledAppListWithGroupingAppRecord(
+ APP,
+ System.currentTimeMillis()
+ ))
+
+ Truth.assertThat(actualGroupTitle).isEqualTo("Apps installed in the last 6 months")
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun backgroundInstalledAppsWithGroupingListModel_getGroupTitleTwo() = runTest {
+ val listModel = BackgroundInstalledAppsWithGroupingListModel(context)
+
+ val actualGroupTitle = listModel
+ .getGroupTitle(0,
+ BackgroundInstalledAppListWithGroupingAppRecord(
+ APP,
+ 0L
+ ))
+
+ Truth.assertThat(actualGroupTitle).isEqualTo("Apps installed more than 6 months ago")
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun backgroundInstalledAppsWithGroupingListModel_transform() = runTest {
+ val listModel = BackgroundInstalledAppsWithGroupingListModel(mockContext)
+ Mockito.`when`(mockPackageManager.getPackageInfoAsUser(
+ eq(TEST_PACKAGE_NAME),
+ packageInfoFlagsCaptor.capture(),
+ eq(TEST_USER_ID))
+ )
+ .thenReturn(PACKAGE_INFO)
+ val recordListFlow = listModel.transform(flowOf(TEST_USER_ID), flowOf(listOf(APP)))
+
+ val recordList = recordListFlow.first()
+
+ Truth.assertThat(recordList).hasSize(1)
+ Truth.assertThat(recordList[0].app).isSameInstanceAs(APP)
+ Truth.assertThat(packageInfoFlagsCaptor.value.value).isEqualTo(EXPECTED_PACKAGE_INFO_FLAG)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun backgroundInstalledAppsWithGroupingListModel_filter() = runTest {
+ val listModel = BackgroundInstalledAppsWithGroupingListModel(mockContext)
+ listModel.setBackgroundInstallControlService(mockBackgroundInstallControlService)
+ Mockito.`when`(mockBackgroundInstallControlService.getBackgroundInstalledPackages(
+ PackageManager.MATCH_ALL.toLong(),
+ TEST_USER_ID
+ )).thenReturn(ParceledListSlice(listOf(PACKAGE_INFO)))
+
+ val recordListFlow = listModel.filter(
+ flowOf(TEST_USER_ID),
+ 0,
+ flowOf(listOf(APP_RECORD_WITH_PACKAGE_MATCH, APP_RECORD_WITHOUT_PACKAGE_MATCH))
+ )
+
+
+ val recordList = recordListFlow.first()
+ Truth.assertThat(recordList).hasSize(1)
+ Truth.assertThat(recordList[0]).isSameInstanceAs(APP_RECORD_WITH_PACKAGE_MATCH)
+ }
+
+ private fun setItemContent() {
+ composeTestRule.setContent {
+ BackgroundInstalledAppList {
+ fakeNavControllerWrapper.Wrapper {
+ with(BackgroundInstalledAppsWithGroupingListModel(context)) {
+ AppListItemModel(
+ record = BackgroundInstalledAppListWithGroupingAppRecord(
+ app = APP,
+ dateOfInstall = TEST_FIRST_INSTALL_TIME),
+ label = TEST_LABEL,
+ summary = stateOf(TEST_SUMMARY),
+ ).AppItem()
+ }
+ }
+ }
+ }
+ }
+
+ private companion object {
+ private const val TEST_USER_ID = 0
+ private const val TEST_PACKAGE_NAME = "package.name"
+ private const val TEST_NO_MATCH_PACKAGE_NAME = "no.match"
+ private const val TEST_LABEL = "Label"
+ private const val TEST_SUMMARY = "Summary"
+ private const val TEST_FIRST_INSTALL_TIME = 0L
+ private const val EXPECTED_PROVIDER_NAME = "BackgroundInstalledAppsPage"
+ private const val EXPECTED_PACKAGE_INFO_FLAG = 0L
+
+ val APP = ApplicationInfo().apply {
+ packageName = TEST_PACKAGE_NAME
+ }
+ val APP_NO_RECORD = ApplicationInfo().apply {
+ packageName = TEST_NO_MATCH_PACKAGE_NAME
+ }
+ val APP_RECORD_WITH_PACKAGE_MATCH = BackgroundInstalledAppListWithGroupingAppRecord(
+ APP,
+ TEST_FIRST_INSTALL_TIME
+ )
+ val APP_RECORD_WITHOUT_PACKAGE_MATCH = BackgroundInstalledAppListWithGroupingAppRecord(
+ APP_NO_RECORD,
+ TEST_FIRST_INSTALL_TIME
+ )
+ val PACKAGE_INFO = PackageInfo().apply {
+ packageName = TEST_PACKAGE_NAME
+ applicationInfo = APP
+ firstInstallTime = TEST_FIRST_INSTALL_TIME
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt
new file mode 100644
index 0000000..f5d422d
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.specialaccess
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AllFilesAccessTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val listModel = AllFilesAccessListModel(context)
+
+ @Test
+ fun pageTitleResId() {
+ assertThat(listModel.pageTitleResId).isEqualTo(R.string.manage_external_storage_title)
+ }
+
+ @Test
+ fun switchTitleResId() {
+ assertThat(listModel.switchTitleResId).isEqualTo(R.string.permit_manage_external_storage)
+ }
+
+ @Test
+ fun footerResId() {
+ assertThat(listModel.footerResId)
+ .isEqualTo(R.string.allow_manage_external_storage_description)
+ }
+
+ @Test
+ fun appOp() {
+ assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE)
+ }
+
+ @Test
+ fun permission() {
+ assertThat(listModel.permission).isEqualTo(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ }
+
+ @Test
+ fun setModeByUid() {
+ assertThat(listModel.setModeByUid).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt
new file mode 100644
index 0000000..b56d997
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.specialaccess
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MediaManagementAppsTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val listModel = MediaManagementAppsListModel(context)
+
+ @Test
+ fun pageTitleResId() {
+ assertThat(listModel.pageTitleResId).isEqualTo(R.string.media_management_apps_title)
+ }
+
+ @Test
+ fun switchTitleResId() {
+ assertThat(listModel.switchTitleResId)
+ .isEqualTo(R.string.media_management_apps_toggle_label)
+ }
+
+ @Test
+ fun footerResId() {
+ assertThat(listModel.footerResId).isEqualTo(R.string.media_management_apps_description)
+ }
+
+ @Test
+ fun appOp() {
+ assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_MANAGE_MEDIA)
+ }
+
+ @Test
+ fun permission() {
+ assertThat(listModel.permission).isEqualTo(Manifest.permission.MANAGE_MEDIA)
+ }
+
+ @Test
+ fun setModeByUid() {
+ assertThat(listModel.setModeByUid).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/notification/AppNotificationRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/spa/notification/AppNotificationRepositoryTest.kt
index 7a5bc9f..a1d8d3f 100644
--- a/tests/spa_unit/src/com/android/settings/spa/notification/AppNotificationRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/notification/AppNotificationRepositoryTest.kt
@@ -29,8 +29,10 @@
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
import com.android.settingslib.spa.testutils.any
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
+import com.android.settingslib.spaprivileged.model.app.userId
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -89,6 +91,39 @@
return channel
}
+ private fun mockIsEnabled(app: ApplicationInfo, enabled: Boolean) {
+ whenever(notificationManager.areNotificationsEnabledForPackage(app.packageName, app.uid))
+ .thenReturn(enabled)
+ }
+
+ private fun mockChannelCount(app: ApplicationInfo, count: Int) {
+ whenever(
+ notificationManager.getNumNotificationChannelsForPackage(
+ app.packageName,
+ app.uid,
+ false,
+ )
+ ).thenReturn(count)
+ }
+
+ private fun mockBlockedChannelCount(app: ApplicationInfo, count: Int) {
+ whenever(notificationManager.getBlockedChannelCount(app.packageName, app.uid))
+ .thenReturn(count)
+ }
+
+ private fun mockSentCount(app: ApplicationInfo, sentCount: Int) {
+ val events = (1..sentCount).map {
+ UsageEvents.Event().apply {
+ mEventType = UsageEvents.Event.NOTIFICATION_INTERRUPTION
+ }
+ }
+ whenever(
+ usageStatsManager.queryEventsForPackageForUser(
+ any(), any(), eq(app.userId), eq(app.packageName), any()
+ )
+ ).thenReturn(UsageEvents(events, arrayOf()))
+ }
+
@Test
fun getAggregatedUsageEvents() = runTest {
val events = listOf(
@@ -120,8 +155,7 @@
@Test
fun isEnabled() {
- whenever(notificationManager.areNotificationsEnabledForPackage(APP.packageName, APP.uid))
- .thenReturn(true)
+ mockIsEnabled(app = APP, enabled = true)
val isEnabled = repository.isEnabled(APP)
@@ -212,6 +246,61 @@
}
@Test
+ fun getNotificationSummary_notEnabled() {
+ mockIsEnabled(app = APP, enabled = false)
+
+ val summary = repository.getNotificationSummary(APP)
+
+ assertThat(summary).isEqualTo(context.getString(R.string.off))
+ }
+
+ @Test
+ fun getNotificationSummary_noChannel() {
+ mockIsEnabled(app = APP, enabled = true)
+ mockChannelCount(app = APP, count = 0)
+ mockSentCount(app = APP, sentCount = 1)
+
+ val summary = repository.getNotificationSummary(APP)
+
+ assertThat(summary).isEqualTo("About 1 notification per week")
+ }
+
+ @Test
+ fun getNotificationSummary_allChannelsBlocked() {
+ mockIsEnabled(app = APP, enabled = true)
+ mockChannelCount(app = APP, count = 2)
+ mockBlockedChannelCount(app = APP, count = 2)
+
+ val summary = repository.getNotificationSummary(APP)
+
+ assertThat(summary).isEqualTo(context.getString(R.string.off))
+ }
+
+ @Test
+ fun getNotificationSummary_noChannelBlocked() {
+ mockIsEnabled(app = APP, enabled = true)
+ mockChannelCount(app = APP, count = 2)
+ mockSentCount(app = APP, sentCount = 2)
+ mockBlockedChannelCount(app = APP, count = 0)
+
+ val summary = repository.getNotificationSummary(APP)
+
+ assertThat(summary).isEqualTo("About 2 notifications per week")
+ }
+
+ @Test
+ fun getNotificationSummary_someChannelsBlocked() {
+ mockIsEnabled(app = APP, enabled = true)
+ mockChannelCount(app = APP, count = 2)
+ mockSentCount(app = APP, sentCount = 3)
+ mockBlockedChannelCount(app = APP, count = 1)
+
+ val summary = repository.getNotificationSummary(APP)
+
+ assertThat(summary).isEqualTo("About 3 notifications per week / 1 category turned off")
+ }
+
+ @Test
fun calculateFrequencySummary_daily() {
val summary = repository.calculateFrequencySummary(4)
diff --git a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java
new file mode 100644
index 0000000..5848326
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.credentials;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Looper;
+
+import androidx.lifecycle.Lifecycle;
+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.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class CredentialManagerPreferenceControllerTest {
+
+ private Context mContext;
+ private PreferenceScreen mScreen;
+ private PreferenceCategory mCredentialsPreferenceCategory;
+
+ @Before
+ public void setUp() {
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ if (Looper.myLooper() == null) {
+ Looper.prepare(); // needed to create the preference screen
+ }
+ mScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+ mCredentialsPreferenceCategory = new PreferenceCategory(mContext);
+ mCredentialsPreferenceCategory.setKey("credentials_test");
+ mScreen.addPreference(mCredentialsPreferenceCategory);
+ }
+
+ @Test
+ // Tests that getAvailabilityStatus() does not throw an exception if it's called before the
+ // Controller is initialized (this can happen during indexing).
+ public void getAvailabilityStatus_withoutInit_returnsUnavailable() {
+ CredentialManagerPreferenceController controller =
+ new CredentialManagerPreferenceController(
+ mContext, mCredentialsPreferenceCategory.getKey());
+ assertThat(controller.isConnected()).isFalse();
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_noServices_returnsUnavailable() {
+ CredentialManagerPreferenceController controller =
+ createControllerWithServices(Collections.emptyList());
+ assertThat(controller.isConnected()).isFalse();
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_withServices_returnsAvailable() {
+ CredentialManagerPreferenceController controller =
+ createControllerWithServices(Lists.newArrayList(createServiceInfo()));
+ assertThat(controller.isConnected()).isFalse();
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void displayPreference_noServices_noPreferencesAdded() {
+ CredentialManagerPreferenceController controller =
+ createControllerWithServices(Collections.emptyList());
+ controller.displayPreference(mScreen);
+ assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void displayPreference_withServices_preferencesAdded() {
+ CredentialManagerPreferenceController controller =
+ createControllerWithServices(Lists.newArrayList(createServiceInfo()));
+ controller.displayPreference(mScreen);
+ assertThat(controller.isConnected()).isFalse();
+ assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void getAvailabilityStatus_handlesToggleAndSave() {
+ CredentialManagerPreferenceController controller =
+ createControllerWithServices(
+ Lists.newArrayList(
+ createServiceInfo("com.android.provider1", "ClassA"),
+ createServiceInfo("com.android.provider1", "ClassB"),
+ createServiceInfo("com.android.provider2", "ClassA"),
+ createServiceInfo("com.android.provider3", "ClassA"),
+ createServiceInfo("com.android.provider4", "ClassA"),
+ createServiceInfo("com.android.provider5", "ClassA"),
+ createServiceInfo("com.android.provider6", "ClassA")));
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ assertThat(controller.isConnected()).isFalse();
+
+ // Ensure that we stay under 5 providers.
+ assertThat(controller.togglePackageNameEnabled("com.android.provider1")).isTrue();
+ assertThat(controller.togglePackageNameEnabled("com.android.provider2")).isTrue();
+ assertThat(controller.togglePackageNameEnabled("com.android.provider3")).isTrue();
+ assertThat(controller.togglePackageNameEnabled("com.android.provider4")).isTrue();
+ assertThat(controller.togglePackageNameEnabled("com.android.provider5")).isTrue();
+ assertThat(controller.togglePackageNameEnabled("com.android.provider6")).isFalse();
+
+ // Check that they are all actually registered.
+ Set<String> enabledProviders = controller.getEnabledProviders();
+ assertThat(enabledProviders.size()).isEqualTo(5);
+ assertThat(enabledProviders.contains("com.android.provider1")).isTrue();
+ assertThat(enabledProviders.contains("com.android.provider2")).isTrue();
+ assertThat(enabledProviders.contains("com.android.provider3")).isTrue();
+ assertThat(enabledProviders.contains("com.android.provider4")).isTrue();
+ assertThat(enabledProviders.contains("com.android.provider5")).isTrue();
+ assertThat(enabledProviders.contains("com.android.provider6")).isFalse();
+
+ // Check that the settings string has the right component names.
+ List<String> enabledServices = controller.getEnabledSettings();
+ assertThat(enabledServices.size()).isEqualTo(6);
+ assertThat(enabledServices.contains("com.android.provider1/ClassA")).isTrue();
+ assertThat(enabledServices.contains("com.android.provider1/ClassB")).isTrue();
+ assertThat(enabledServices.contains("com.android.provider2/ClassA")).isTrue();
+ assertThat(enabledServices.contains("com.android.provider3/ClassA")).isTrue();
+ assertThat(enabledServices.contains("com.android.provider4/ClassA")).isTrue();
+ assertThat(enabledServices.contains("com.android.provider5/ClassA")).isTrue();
+ assertThat(enabledServices.contains("com.android.provider6/ClassA")).isFalse();
+
+ // Toggle the provider disabled.
+ controller.togglePackageNameDisabled("com.android.provider2");
+
+ // Check that the provider was removed from the list of providers.
+ Set<String> currentlyEnabledProviders = controller.getEnabledProviders();
+ assertThat(currentlyEnabledProviders.size()).isEqualTo(4);
+ assertThat(currentlyEnabledProviders.contains("com.android.provider1")).isTrue();
+ assertThat(currentlyEnabledProviders.contains("com.android.provider2")).isFalse();
+ assertThat(currentlyEnabledProviders.contains("com.android.provider3")).isTrue();
+ assertThat(currentlyEnabledProviders.contains("com.android.provider4")).isTrue();
+ assertThat(currentlyEnabledProviders.contains("com.android.provider5")).isTrue();
+ assertThat(currentlyEnabledProviders.contains("com.android.provider6")).isFalse();
+
+ // Check that the provider was removed from the list of services stored in the setting.
+ List<String> currentlyEnabledServices = controller.getEnabledSettings();
+ assertThat(currentlyEnabledServices.size()).isEqualTo(5);
+ assertThat(currentlyEnabledServices.contains("com.android.provider1/ClassA")).isTrue();
+ assertThat(currentlyEnabledServices.contains("com.android.provider1/ClassB")).isTrue();
+ assertThat(currentlyEnabledServices.contains("com.android.provider3/ClassA")).isTrue();
+ assertThat(currentlyEnabledServices.contains("com.android.provider4/ClassA")).isTrue();
+ assertThat(currentlyEnabledServices.contains("com.android.provider5/ClassA")).isTrue();
+ assertThat(currentlyEnabledServices.contains("com.android.provider6/ClassA")).isFalse();
+ }
+
+ private CredentialManagerPreferenceController createControllerWithServices(
+ List<ServiceInfo> availableServices) {
+ CredentialManagerPreferenceController controller =
+ new CredentialManagerPreferenceController(
+ mContext, mCredentialsPreferenceCategory.getKey());
+ controller.init(() -> mock(Lifecycle.class), availableServices, new HashSet<>());
+ return controller;
+ }
+
+ private ServiceInfo createServiceInfo() {
+ return createServiceInfo("com.android.provider", "CredManProvider");
+ }
+
+ private ServiceInfo createServiceInfo(String packageName, String className) {
+ ServiceInfo si = new ServiceInfo();
+ si.packageName = packageName;
+ si.name = className;
+ si.nonLocalizedLabel = "test";
+
+ si.applicationInfo = new ApplicationInfo();
+ si.applicationInfo.packageName = packageName;
+ si.applicationInfo.nonLocalizedLabel = "test";
+
+ return si;
+ }
+}
diff --git a/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java
index bff9d41..5f02b04 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java
@@ -41,7 +41,6 @@
import com.android.settings.testutils.ResourcesUtils;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -103,7 +102,6 @@
}
@Test
- @Ignore
public void getAvailabilityStatus_isNotVoiceCapable_shouldBeUNSUPPORTED_ON_DEVICE() {
when(mTelephonyManager.isVoiceCapable()).thenReturn(false);
@@ -123,7 +121,6 @@
}
@Test
- @Ignore
public void updateState_singleSim_shouldUpdateTitleAndPhoneNumber() {
final String phoneNumber = "1111111111";
doReturn(phoneNumber).when(mController).getFormattedPhoneNumber(mSubscriptionInfo);
@@ -137,7 +134,6 @@
}
@Test
- @Ignore
public void updateState_multiSim_shouldUpdateTitleAndPhoneNumberOfMultiplePreferences() {
final String phoneNumber = "1111111111";
doReturn(phoneNumber).when(mController).getFormattedPhoneNumber(mSubscriptionInfo);
@@ -155,7 +151,6 @@
}
@Test
- @Ignore
public void getSummary_cannotGetActiveSubscriptionInfo_shouldShowUnknown() {
when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null);
@@ -167,7 +162,6 @@
}
@Test
- @Ignore
public void getSummary_getEmptySubscriptionInfo_shouldShowUnknown() {
List<SubscriptionInfo> infos = new ArrayList<>();
when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(infos);
diff --git a/tests/unit/src/com/android/settings/deviceinfo/simstatus/SlotSimStatusTest.java b/tests/unit/src/com/android/settings/deviceinfo/simstatus/SlotSimStatusTest.java
index 4c17d15..3136af7 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/simstatus/SlotSimStatusTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/simstatus/SlotSimStatusTest.java
@@ -23,8 +23,13 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.os.Bundle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
+import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -36,15 +41,27 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
import java.util.concurrent.Executor;
@RunWith(AndroidJUnit4.class)
public class SlotSimStatusTest {
+ private static final int SUB_ID_1 = 3;
+ private static final int SUB_ID_2 = 8;
+
+ @Mock
+ private SubscriptionManager mSubscriptionManager;
@Mock
private TelephonyManager mTelephonyManager;
@Mock
+ private SubscriptionInfo mSubscriptionInfo1;
+ @Mock
+ private SubscriptionInfo mSubscriptionInfo2;
+ @Mock
private Executor mExecutor;
+ @Mock
+ private Lifecycle mLifecycle;
@Captor
private ArgumentCaptor<Runnable> mRunnableCaptor;
@@ -56,13 +73,20 @@
mContext = spy(ApplicationProvider.getApplicationContext());
mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager);
+ mockService(Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class,
+ mSubscriptionManager);
}
@Test
public void size_returnNumberOfPhone_whenQuery() {
doReturn(2).when(mTelephonyManager).getPhoneCount();
- SlotSimStatus target = new SlotSimStatus(mContext);
+ SlotSimStatus target = new SlotSimStatus(mContext, null, null) {
+ @Override
+ protected void postValue(Long value) {}
+ @Override
+ protected void setValue(Long value) {}
+ };
assertEquals(new Integer(target.size()), new Integer(2));
}
@@ -71,7 +95,12 @@
public void size_returnNumberOfPhone_whenQueryInBackgroundThread() {
doReturn(2).when(mTelephonyManager).getPhoneCount();
- SlotSimStatus target = new SlotSimStatus(mContext, mExecutor);
+ SlotSimStatus target = new SlotSimStatus(mContext, mExecutor, mLifecycle) {
+ @Override
+ protected void postValue(Long value) {}
+ @Override
+ protected void setValue(Long value) {}
+ };
verify(mExecutor).execute(mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
@@ -83,7 +112,12 @@
public void getPreferenceOrdering_returnOrdering_whenQuery() {
doReturn(2).when(mTelephonyManager).getPhoneCount();
- SlotSimStatus target = new SlotSimStatus(mContext);
+ SlotSimStatus target = new SlotSimStatus(mContext, null, null) {
+ @Override
+ protected void postValue(Long value) {}
+ @Override
+ protected void setValue(Long value) {}
+ };
target.setBasePreferenceOrdering(30);
assertEquals(new Integer(target.getPreferenceOrdering(1)), new Integer(32));
@@ -93,10 +127,36 @@
public void getPreferenceKey_returnKey_whenQuery() {
doReturn(2).when(mTelephonyManager).getPhoneCount();
- SlotSimStatus target = new SlotSimStatus(mContext);
+ SlotSimStatus target = new SlotSimStatus(mContext, null, null) {
+ @Override
+ protected void postValue(Long value) {}
+ @Override
+ protected void setValue(Long value) {}
+ };
target.setBasePreferenceOrdering(50);
- assertEquals(target.getPreferenceKey(1), "sim_status52");
+ assertEquals(target.getPreferenceKey(1), "sim_status2");
+ }
+
+ @Test
+ public void getSubscriptionInfo_returnSubscriptionInfo_whenActive() {
+ doReturn(SUB_ID_1).when(mSubscriptionInfo1).getSubscriptionId();
+ doReturn(0).when(mSubscriptionInfo1).getSimSlotIndex();
+ doReturn(SUB_ID_2).when(mSubscriptionInfo2).getSubscriptionId();
+ doReturn(1).when(mSubscriptionInfo2).getSimSlotIndex();
+
+ doReturn(List.of(mSubscriptionInfo1, mSubscriptionInfo2))
+ .when(mSubscriptionManager).getActiveSubscriptionInfoList();
+ doReturn(2).when(mTelephonyManager).getPhoneCount();
+
+ SlotSimStatus target = new SlotSimStatus(mContext, null, null) {
+ @Override
+ protected void postValue(Long value) {}
+ @Override
+ protected void setValue(Long value) {}
+ };
+
+ assertEquals(target.getSubscriptionInfo(1), mSubscriptionInfo2);
}
private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
diff --git a/tests/unit/src/com/android/settings/display/ScreenSaverPreferenceControllerTest.java b/tests/unit/src/com/android/settings/display/ScreenSaverPreferenceControllerTest.java
index 484138e..180ea90 100644
--- a/tests/unit/src/com/android/settings/display/ScreenSaverPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/display/ScreenSaverPreferenceControllerTest.java
@@ -45,11 +45,13 @@
private ScreenSaverPreferenceController mController;
+ private final String mPrefKey = "test_screensaver";
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mController = new ScreenSaverPreferenceController(mContext);
+ mController = new ScreenSaverPreferenceController(mContext, mPrefKey);
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
diff --git a/tests/unit/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsControllerTest.java b/tests/unit/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsControllerTest.java
index f708f6c..bb2d2ef 100644
--- a/tests/unit/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsControllerTest.java
+++ b/tests/unit/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsControllerTest.java
@@ -15,6 +15,8 @@
*/
package com.android.settings.fuelgauge.batterysaver;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.ContentResolver;
@@ -48,40 +50,13 @@
}
@Test
- public void getDefaultKey_routine_returnsCorrectValue() {
- Settings.Global.putInt(mResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
- PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC);
- assertThat(mController.getDefaultKey())
- .isEqualTo(BatterySaverScheduleRadioButtonsController.KEY_NO_SCHEDULE);
- }
-
- @Test
- public void getDefaultKey_automatic_returnsCorrectValue() {
- Settings.Global.putInt(mResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
- PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
- Settings.Global.putInt(mResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 5);
- assertThat(mController.getDefaultKey())
- .isEqualTo(BatterySaverScheduleRadioButtonsController.KEY_PERCENTAGE);
- }
-
- @Test
- public void getDefaultKey_none_returnsCorrectValue() {
- Settings.Global.putInt(mResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
- PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
- Settings.Global.putInt(mResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
- assertThat(mController.getDefaultKey())
- .isEqualTo(BatterySaverScheduleRadioButtonsController.KEY_NO_SCHEDULE);
- }
-
-
- @Test
public void setDefaultKey_percentage_shouldSuppressNotification() {
Secure.putInt(
mContext.getContentResolver(), Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Settings.Global.putInt(mResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
Settings.Global.putInt(mResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 5);
- mController.setDefaultKey(BatterySaverScheduleRadioButtonsController.KEY_PERCENTAGE);
+ mController.setDefaultKey(KEY_PERCENTAGE);
final int result = Settings.Secure.getInt(mResolver,
Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0);
diff --git a/tests/unit/src/com/android/settings/network/InternetPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/InternetPreferenceControllerTest.java
index cd9d2a3..8beeffb 100644
--- a/tests/unit/src/com/android/settings/network/InternetPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/network/InternetPreferenceControllerTest.java
@@ -56,7 +56,6 @@
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -178,7 +177,6 @@
any(Handler.class));
}
- @Ignore
@Test
@UiThreadTest
public void onPause_shouldUnregisterCallback() {
diff --git a/tests/unit/src/com/android/settings/network/InternetResetHelperTest.java b/tests/unit/src/com/android/settings/network/InternetResetHelperTest.java
index a527822..3fe6882 100644
--- a/tests/unit/src/com/android/settings/network/InternetResetHelperTest.java
+++ b/tests/unit/src/com/android/settings/network/InternetResetHelperTest.java
@@ -42,7 +42,6 @@
import com.android.settingslib.utils.HandlerInjector;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -242,7 +241,6 @@
verify(mRecoveryWorker).triggerRestart();
}
- @Ignore
@Test
public void checkRecovering_isRecovering_showResetting() {
when(mRecoveryWorker.isRecovering()).thenReturn(true);
@@ -252,7 +250,6 @@
verify(mResettingPreference).setVisible(true);
}
- @Ignore
@Test
public void checkRecovering_isNotRecovering_doNotShowResetting() {
when(mRecoveryWorker.isRecovering()).thenReturn(false);
diff --git a/tests/unit/src/com/android/settings/network/ProviderModelSliceTest.java b/tests/unit/src/com/android/settings/network/ProviderModelSliceTest.java
index 553fefd..345631c 100644
--- a/tests/unit/src/com/android/settings/network/ProviderModelSliceTest.java
+++ b/tests/unit/src/com/android/settings/network/ProviderModelSliceTest.java
@@ -66,7 +66,6 @@
import com.android.wifitrackerlib.WifiPickerTracker;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -463,7 +462,6 @@
}
}
- @Ignore
@Test
@UiThreadTest
public void onNotifyChange_FirstTimeDisableToggleState_showDialog() {
diff --git a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
index e06e0a5..63dca7e 100644
--- a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
+++ b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
@@ -459,7 +459,6 @@
}
@Test
- @Ignore
public void isSimHardwareVisible_configAsVisible_returnTrue() {
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getBoolean(R.bool.config_show_sim_info))
diff --git a/tests/unit/src/com/android/settings/wifi/RequestToggleWiFiActivityTest.java b/tests/unit/src/com/android/settings/wifi/RequestToggleWiFiActivityTest.java
index 8810386..0e3dd40 100644
--- a/tests/unit/src/com/android/settings/wifi/RequestToggleWiFiActivityTest.java
+++ b/tests/unit/src/com/android/settings/wifi/RequestToggleWiFiActivityTest.java
@@ -34,7 +34,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -77,7 +76,6 @@
}
@Test
- @Ignore
public void getAppLabel_nullPackageName_returnNull() {
fakeCallingPackage(null);
@@ -85,7 +83,6 @@
}
@Test
- @Ignore
public void getAppLabel_settingsPackageName_returnNotNull() {
fakeCallingPackage("com.android.settings");