Merge "Migrate VpnSettings from RestrictedSettingsFragment to RestrictedDashboardFragment." into main
diff --git a/Android.bp b/Android.bp
index baf9914..861f95f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -84,6 +84,7 @@
"net-utils-framework-common",
"app-usage-event-protos-lite",
"battery-event-protos-lite",
+ "power-anomaly-event-protos-lite",
"settings-contextual-card-protos-lite",
"settings-log-bridge-protos-lite",
"settings-telephony-protos-lite",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6702ed1..cae7096 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -644,7 +644,6 @@
<activity android:name="Settings$FaceSettingsActivity"
android:label="@string/security_settings_face_preference_title"
android:exported="true"
- android:configChanges="orientation|screenSize"
android:icon="@drawable/ic_face_header">
<intent-filter>
<action android:name="android.settings.FACE_SETTINGS" />
@@ -660,7 +659,6 @@
android:label="@string/security_settings_face_preference_title"
android:exported="false"
android:icon="@drawable/ic_face_header"
- android:configChanges="orientation|screenSize"
android:taskAffinity="com.android.settings.root">
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.biometrics.face.FaceSettings" />
diff --git a/res/drawable/battery_tips_all_rounded_bg.xml b/res/drawable/battery_tips_all_rounded_bg.xml
new file mode 100644
index 0000000..4f61f54
--- /dev/null
+++ b/res/drawable/battery_tips_all_rounded_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/settingslib_dialog_background" />
+ <corners android:radius="@dimen/battery_tips_card_corner_radius_normal" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/battery_tips_half_rounded_bottom_bg.xml b/res/drawable/battery_tips_half_rounded_bottom_bg.xml
new file mode 100644
index 0000000..7766de6
--- /dev/null
+++ b/res/drawable/battery_tips_half_rounded_bottom_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/settingslib_dialog_background"/>
+ <corners
+ android:topLeftRadius="@dimen/battery_tips_card_corner_radius_small"
+ android:topRightRadius="@dimen/battery_tips_card_corner_radius_small"
+ android:bottomLeftRadius="@dimen/battery_tips_card_corner_radius_normal"
+ android:bottomRightRadius="@dimen/battery_tips_card_corner_radius_normal"
+ />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/battery_tips_half_rounded_top_bg.xml b/res/drawable/battery_tips_half_rounded_top_bg.xml
new file mode 100644
index 0000000..aba1a4f
--- /dev/null
+++ b/res/drawable/battery_tips_half_rounded_top_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/settingslib_dialog_background"/>
+ <corners
+ android:topLeftRadius="@dimen/battery_tips_card_corner_radius_normal"
+ android:topRightRadius="@dimen/battery_tips_card_corner_radius_normal"
+ android:bottomLeftRadius="@dimen/battery_tips_card_corner_radius_small"
+ android:bottomRightRadius="@dimen/battery_tips_card_corner_radius_small"
+ />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/ic_battery_charger.xml b/res/drawable/ic_battery_charger.xml
new file mode 100644
index 0000000..4406a56
--- /dev/null
+++ b/res/drawable/ic_battery_charger.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2023 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="48dp"
+ android:height="48dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="?android:attr/colorAccent"
+ android:pathData="M442,780L518,780L518,698L660,542L660,351Q660,351 660,351Q660,351 660,351L300,351Q300,351 300,351Q300,351 300,351L300,542L442,697.7L442,780ZM382,840L382,722L240,566L240,351Q240,326.25 257.63,308.63Q275.25,291 300,291L372,291L342,321L342,120L402,120L402,291L558,291L558,120L618,120L618,321L588,291L660,291Q684.75,291 702.38,308.63Q720,326.25 720,351L720,566L578,722L578,840L382,840ZM480,565L480,565L480,565L480,565Q480,565 480,565Q480,565 480,565L480,565Q480,565 480,565Q480,565 480,565L480,565L480,565L480,565L480,565Z"/>
+</vector>
+
diff --git a/res/drawable/ic_battery_tips_close.xml b/res/drawable/ic_battery_tips_close.xml
new file mode 100644
index 0000000..7ef571b
--- /dev/null
+++ b/res/drawable/ic_battery_tips_close.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2023 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">
+ <path
+ android:fillColor="?android:attr/textColorSecondary"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
+</vector>
diff --git a/res/drawable/ic_battery_tips_close_icon.xml b/res/drawable/ic_battery_tips_close_icon.xml
new file mode 100644
index 0000000..b766474
--- /dev/null
+++ b/res/drawable/ic_battery_tips_close_icon.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright (C) 2023 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp">
+ <item>
+ <shape android:shape="oval">
+ <size
+ android:width="24dp"
+ android:height="24dp" />
+ <solid android:color="?android:attr/colorBackground" />
+ </shape>
+ </item>
+ <item android:drawable="@drawable/ic_battery_tips_close"
+ android:gravity="center"
+ android:width="16dp"
+ android:height="16dp"/>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/ic_battery_tips_lightbulb.xml b/res/drawable/ic_battery_tips_lightbulb.xml
new file mode 100644
index 0000000..f1449f9
--- /dev/null
+++ b/res/drawable/ic_battery_tips_lightbulb.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2023 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="32dp"
+ android:height="32dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/colorAccent"
+ android:pathData="M7,20h4c0,1.1 -0.9,2 -2,2S7,21.1 7,20zM5,19h8v-2H5V19zM16.5,9.5c0,3.82 -2.66,5.86 -3.77,6.5H5.27C4.16,15.36 1.5,13.32 1.5,9.5C1.5,5.36 4.86,2 9,2S16.5,5.36 16.5,9.5zM14.5,9.5C14.5,6.47 12.03,4 9,4S3.5,6.47 3.5,9.5c0,2.47 1.49,3.89 2.35,4.5h6.3C13.01,13.39 14.5,11.97 14.5,9.5zM21.37,7.37L20,8l1.37,0.63L22,10l0.63,-1.37L24,8l-1.37,-0.63L22,6L21.37,7.37zM19,6l0.94,-2.06L22,3l-2.06,-0.94L19,0l-0.94,2.06L16,3l2.06,0.94L19,6z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_battery_tips_thumb_down.xml b/res/drawable/ic_battery_tips_thumb_down.xml
new file mode 100644
index 0000000..cd7656b
--- /dev/null
+++ b/res/drawable/ic_battery_tips_thumb_down.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2023 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="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="?android:attr/colorAccent"
+ android:pathData="M242,120L686,120L686,632L408,920L369,889Q363,884 360,875Q357,866 357,853L357,843L402,632L103,632Q79,632 61,614Q43,596 43,572L43,490.16Q43,483 41.5,475.5Q40,468 43,461L169,171Q177.88,149.75 198.6,134.88Q219.31,120 242,120ZM626,180L229,180Q229,180 229,180Q229,180 229,180L103,479L103,572Q103,572 103,572Q103,572 103,572L476,572L423,821L626,607L626,180ZM626,607L626,607L626,572L626,572Q626,572 626,572Q626,572 626,572L626,479L626,180Q626,180 626,180Q626,180 626,180L626,180L626,607ZM686,632L686,572L819,572L819,180L686,180L686,120L879,120L879,632L686,632Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_battery_tips_thumb_up.xml b/res/drawable/ic_battery_tips_thumb_up.xml
new file mode 100644
index 0000000..b1d4cb6
--- /dev/null
+++ b/res/drawable/ic_battery_tips_thumb_up.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2023 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="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="?android:attr/colorAccent"
+ android:pathData="M716,840L272,840L272,328L550,40L589,71Q595,76 598,85Q601,94 601,107L601,117L556,328L855,328Q879,328 897,346Q915,364 915,388L915,469.84Q915,477 916.5,484.5Q918,492 915,499L789,789Q780.12,810.25 759.41,825.13Q738.69,840 716,840ZM332,780L729,780Q729,780 729,780Q729,780 729,780L855,481L855,388Q855,388 855,388Q855,388 855,388L482,388L535,139L332,353L332,780ZM332,353L332,353L332,388L332,388Q332,388 332,388Q332,388 332,388L332,481L332,780Q332,780 332,780Q332,780 332,780L332,780L332,353ZM272,328L272,388L139,388L139,780L272,780L272,840L79,840L79,328L272,328Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_lock_none.xml b/res/drawable/ic_lock_none.xml
index 31069b7..54b9bb4 100644
--- a/res/drawable/ic_lock_none.xml
+++ b/res/drawable/ic_lock_none.xml
@@ -18,7 +18,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
<path
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h2c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"
android:fillColor="?android:attr/colorAccent"/>
diff --git a/res/drawable/ic_lock_pin.xml b/res/drawable/ic_lock_pin.xml
index 587f49c..4614f53 100644
--- a/res/drawable/ic_lock_pin.xml
+++ b/res/drawable/ic_lock_pin.xml
@@ -18,7 +18,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
<path
android:pathData="M6,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM6,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM6,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM16,6c0,1.1 0.9,2 2,2s2,-0.9 2,-2 -0.9,-2 -2,-2 -2,0.9 -2,2zM12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"
android:fillColor="?android:attr/colorAccent"/>
diff --git a/res/drawable/ic_lock_swipe.xml b/res/drawable/ic_lock_swipe.xml
index f7e78b8..fb8302d 100644
--- a/res/drawable/ic_lock_swipe.xml
+++ b/res/drawable/ic_lock_swipe.xml
@@ -18,7 +18,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
<path
android:pathData="M20.5,2v2.02C18.18,2.13 15.22,1 12,1S5.82,2.13 3.5,4.02V2H2v3.5V7h1.5H7V5.5H4.09c2.11,-1.86 4.88,-3 7.91,-3s5.79,1.14 7.91,3H17V7h3.5H22V5.5V2H20.5z"
android:fillColor="?android:attr/colorAccent"/>
diff --git a/res/drawable/ic_password.xml b/res/drawable/ic_password.xml
index 341e544..cf3b408 100644
--- a/res/drawable/ic_password.xml
+++ b/res/drawable/ic_password.xml
@@ -18,7 +18,8 @@
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
- android:viewportWidth="24.0">
+ android:viewportWidth="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="?android:attr/colorAccent"
android:pathData="M21.5,9.39l-1.63,0l0.81,-1.42l-0.86,-0.5l-0.82,1.42l-0.82,-1.42l-0.86,0.5l0.81,1.42l-1.63,0l0,1l1.63,0l-0.81,1.41l0.86,0.5l0.82,-1.41l0.82,1.41l0.86,-0.5l-0.81,-1.41l1.63,0z" />
diff --git a/res/drawable/ic_pattern.xml b/res/drawable/ic_pattern.xml
index 788eaa7..e56fb00 100644
--- a/res/drawable/ic_pattern.xml
+++ b/res/drawable/ic_pattern.xml
@@ -18,7 +18,8 @@
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
- android:viewportWidth="24.0">
+ android:viewportWidth="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="?android:attr/colorAccent"
android:pathData="M4,4m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0" />
diff --git a/res/drawable/ic_pin.xml b/res/drawable/ic_pin.xml
index 682e934..8520ec1 100644
--- a/res/drawable/ic_pin.xml
+++ b/res/drawable/ic_pin.xml
@@ -18,7 +18,8 @@
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
- android:viewportWidth="24.0">
+ android:viewportWidth="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="?android:attr/colorAccent"
android:pathData="M20,4L4,4A2,2 0,0 0,2 6L2,18a2,2 0,0 0,2 2L20,20a2,2 0,0 0,2 -2L22,6A2,2 0,0 0,20 4ZM7.1,15L5.9,15L5.9,10.2L4.7,10.2L4.7,9L7.1,9v6ZM13.2,11.4A1.2,1.2 0,0 1,12 12.6L10.8,12.6v1.2h2.4L13.2,15L9.6,15L9.6,12.6a1.2,1.2 0,0 1,1.2 -1.2L12,11.4L12,10.2L9.6,10.2L9.6,9L12,9a1.2,1.2 0,0 1,1.2 1.2v1.2ZM19.3,11.1a0.9,0.9 0,0 1,-0.9 0.9,0.9 0.9,0 0,1 0.9,0.9v0.9A1.2,1.2 0,0 1,18.1 15L15.7,15L15.7,13.8h2.4L18.1,12.6L16.9,12.6L16.9,11.4h1.2L18.1,10.2L15.7,10.2L15.7,9h2.4a1.2,1.2 0,0 1,1.2 1.2v0.9Z" />
diff --git a/res/layout/battery_tips_card.xml b/res/layout/battery_tips_card.xml
new file mode 100644
index 0000000..d2edb51
--- /dev/null
+++ b/res/layout/battery_tips_card.xml
@@ -0,0 +1,125 @@
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/battery_tips_card"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@+id/tips_card"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/battery_tips_all_rounded_bg"
+ android:orientation="vertical"
+ android:padding="24dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:src="@drawable/ic_battery_tips_lightbulb" />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <ImageButton
+ android:id="@+id/dismiss_button"
+ style="@style/Banner.Dismiss.SettingsLib"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginEnd="0dp"
+ android:src="@drawable/ic_battery_tips_close_icon" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:maxLines="2"
+ android:textAlignment="viewStart"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorPrimary" />
+
+ <TextView
+ android:id="@+id/summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:gravity="start"
+ android:maxLines="10"
+ android:textAlignment="viewStart"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/action_button"
+ style="@style/Widget.Material3.Button.OutlinedButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:layout_marginTop="8dp"
+ android:text="@string/battery_tips_card_action_button"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textStyle="bold"
+ app:strokeColor="?android:attr/colorAccent"
+ app:strokeWidth="1dp" />
+ </LinearLayout>
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"/>
+
+ <LinearLayout
+ android:id="@+id/feedback_card"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/battery_tips_half_rounded_bottom_bg"
+ android:gravity="center_vertical|start"
+ android:orientation="horizontal"
+ android:paddingHorizontal="24dp"
+ android:paddingVertical="16dp"
+ android:visibility="gone">
+
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="0dp"
+ android:layout_marginEnd="20dp"
+ android:layout_weight="1"
+ android:text="@string/battery_tips_card_feedback_info"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textStyle="bold"/>
+
+ <ImageButton
+ android:id="@+id/thumb_up"
+ style="@style/Banner.Dismiss.SettingsLib"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginEnd="20dp"
+ android:src="@drawable/ic_battery_tips_thumb_up" />
+
+ <ImageButton
+ android:id="@+id/thumb_down"
+ style="@style/Banner.Dismiss.SettingsLib"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ android:src="@drawable/ic_battery_tips_thumb_down" />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index f20bce0..8d9d4b9 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -608,6 +608,31 @@
<item>3</item>
</integer-array>
+ <!-- App aspect ratio settings screen, user aspect ratio override options. Must be the same
+ length and order as config_userAspectRatioOverrideValues below. -->
+ <string-array name="config_userAspectRatioOverrideEntries" translatable="false">
+ <item>@string/user_aspect_ratio_app_default</item>
+ <item>@string/user_aspect_ratio_fullscreen</item>
+ <item>@string/user_aspect_ratio_half_screen</item>
+ <item>@string/user_aspect_ratio_device_size</item>
+ <item>@string/user_aspect_ratio_16_9</item>
+ <item>@string/user_aspect_ratio_4_3</item>
+ <item>@string/user_aspect_ratio_3_2</item>
+ </string-array>
+
+ <!-- App aspect ratio settings screen, user aspect ratio override options. Must be the same
+ length and order as config_userAspectRatioOverrideEntries above. The values must
+ correspond to PackageManager.UserMinAspectRatio -->
+ <integer-array name="config_userAspectRatioOverrideValues" translatable="false">
+ <item>0</item> <!-- USER_MIN_ASPECT_RATIO_UNSET -->
+ <item>6</item> <!-- USER_MIN_ASPECT_RATIO_FULLSCREEN -->
+ <item>1</item> <!-- USER_MIN_ASPECT_RATIO_SPLIT_SCREEN -->
+ <item>2</item> <!-- USER_MIN_ASPECT_RATIO_DISPLAY_SIZE -->
+ <item>4</item> <!-- USER_MIN_ASPECT_RATIO_16_9 -->
+ <item>3</item> <!-- USER_MIN_ASPECT_RATIO_4_3 -->
+ <item>5</item> <!-- USER_MIN_ASPECT_RATIO_3_2 -->
+ </integer-array>
+
<!-- The settings/preference description for each settable device state defined in the array
"config_perDeviceStateRotationLockDefaults".
The item in position "i" describes the auto-rotation setting for the device state also in
@@ -664,6 +689,9 @@
<!-- Whether to enable the advanced vpn feature. The default is not to. -->
<bool name="config_advanced_vpn_enabled">false</bool>
+ <!-- Whether to show the VPN options menu. The default is to show it. -->
+ <bool name="config_show_vpn_options">true</bool>
+
<!-- An array of uid name for which packages exempt from Wi-Fi permission check. -->
<string-array name="config_exempt_wifi_permission_uid_name" translatable="false">
<item>@string/config_settingsintelligence_package_name</item>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a7a124a..e272c15 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -373,6 +373,10 @@
<dimen name="chartview_trapezoid_margin_start">1dp</dimen>
<dimen name="chartview_trapezoid_margin_bottom">2dp</dimen>
+ <!-- Battery tips card view component -->
+ <dimen name="battery_tips_card_corner_radius_small">4dp</dimen>
+ <dimen name="battery_tips_card_corner_radius_normal">24dp</dimen>
+
<!-- Dimensions for Dream settings cards -->
<dimen name="dream_item_min_column_width">174dp</dimen>
<dimen name="dream_item_corner_radius">28dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 177a5cb..0863249 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -9686,6 +9686,24 @@
<!-- Preference summary for battery usage list page[CHAR_LIMIT=50]-->
<string name="app_battery_usage_summary">Set battery usage for apps</string>
+ <!-- Label of action button in battery tips card [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_card_action_button" translatable="false">Optimize</string>
+
+ <!-- Feedback card message in battery tips card [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_card_feedback_info" translatable="false">Is this message helpful?</string>
+
+ <!-- Title of battery tips: adaptive brightness [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_adaptive_brightness_title" translatable="false">Turn on adaptive brightness to extend battery life</string>
+
+ <!-- Summary of battery tips: adaptive brightness [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_adaptive_brightness_summary" translatable="false">It will help reduce your daily battery drain by 10%</string>
+
+ <!-- Title of battery tips: reduce screen timeout [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_screen_timeout_title" translatable="false">Reduce screen timeout to extend battery life</string>
+
+ <!-- Summary of battery tips: reduce screen timeout [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_screen_timeout_summary" translatable="false">It will help reduce your daily battery drain by 10%</string>
+
<!-- Filter title for battery unrestricted[CHAR_LIMIT=50]-->
<string name="filter_battery_unrestricted_title">Unrestricted</string>
@@ -11029,6 +11047,8 @@
<string name="sim_action_cancel">Cancel</string>
<!-- Button which will disconnect the user from one mobile network and immediately connect to another. [CHAR LIMIT=30] -->
<string name="sim_switch_button">Switch</string>
+ <!-- Button text to turn off the sim. [CHAR LIMIT=30] -->
+ <string name="sim_action_turn_off">Turn off</string>
<!-- Title of DSDS activation failure dialog [CHAR LIMIT=40] -->
<string name="dsds_activation_failure_title">Can\u2019t activate SIM</string>
<!-- Body text of DSDS activation failure dialog. Users could toggle the selected SIM again or reboot to recover. [CHAR LIMIT=NONE] -->
@@ -12111,6 +12131,33 @@
other {Apps installed more than # months ago}
}</string>
+ <!-- App Aspect Ratio (User Aspect Ratio Override) -->
+ <!-- [CHAR LIMIT=60] Aspect ratio title setting to choose app aspect ratio -->
+ <string name="aspect_ratio_title">Aspect ratio</string>
+ <!-- [CHAR LIMIT=NONE] Aspect ratio setting summary to choose aspect ratio for apps unoptimized for device -->
+ <string name="aspect_ratio_summary">Choose an aspect ratio to view this app if it hasn\'t been designed to fit your <xliff:g id="device_name">%1$s</xliff:g></string>
+ <!-- [CHAR LIMIT=NONE] Aspect ratio suggested apps filter label -->
+ <string name="user_aspect_ratio_suggested_apps_label">Suggested apps</string>
+ <!-- [CHAR LIMIT=NONE] Filter label for apps that have user aspect ratio override applied -->
+ <string name="user_aspect_ratio_overridden_apps_label">Apps you have overridden</string>
+ <!-- [CHAR LIMIT=NONE] App default aspect ratio entry -->
+ <string name="user_aspect_ratio_app_default">App default</string>
+ <!-- [CHAR LIMIT=NONE] Fullscreen aspect ratio entry -->
+ <string name="user_aspect_ratio_fullscreen">Full screen</string>
+ <!-- [CHAR LIMIT=NONE] Half screen aspect ratio entry -->
+ <string name="user_aspect_ratio_half_screen">Half screen</string>
+ <!-- [CHAR LIMIT=NONE] Device display size aspect ratio entry -->
+ <string name="user_aspect_ratio_device_size">Device aspect ratio</string>
+ <!-- [CHAR LIMIT=NONE] 16:9 aspect ratio entry -->
+ <string name="user_aspect_ratio_16_9">16:9</string>
+ <!-- [CHAR LIMIT=NONE] 3:2 aspect ratio entry -->
+ <string name="user_aspect_ratio_3_2">3:2</string>
+ <!-- [CHAR LIMIT=NONE] 4:3 aspect ratio entry -->
+ <string name="user_aspect_ratio_4_3">4:3</string>
+ <!-- [CHAR LIMIT=NONE] Warning description for app info aspect ratio page -->
+ <string name="app_aspect_ratio_footer">The app will restart when you change aspect ratio. You may lose unsaved changes.</string>
+
+
<!-- Accessibility label for fingerprint sensor [CHAR LIMIT=NONE] -->
<string name="accessibility_fingerprint_label">Fingerprint sensor</string>
@@ -12220,4 +12267,9 @@
<!-- Warning text about the visibility of device name. [CHAR LIMIT=NONE] -->
<string name="about_phone_device_name_warning">Your device name is visible to apps you installed. It may also be seen by other people when you connect to Bluetooth devices, connect to a Wi-Fi network or set up a Wi-Fi hotspot.</string>
+
+ <!-- Developer settings: grammatical gender title [CHAR LIMIT=50]-->
+ <string name="grammatical_gender_title">Grammatical gender</string>
+ <!-- Developer settings: select Grammatical gender dialog title [CHAR LIMIT=50]-->
+ <string name="grammatical_gender_dialog_title">Select Grammatical gender</string>
</resources>
diff --git a/res/xml/apps.xml b/res/xml/apps.xml
index 03212c9..73bcbc9 100644
--- a/res/xml/apps.xml
+++ b/res/xml/apps.xml
@@ -80,6 +80,17 @@
android:order="10"/>
<Preference
+ android:key="aspect_ratio_apps"
+ android:title="@string/aspect_ratio_title"
+ android:summary="@string/summary_placeholder"
+ android:order="14"
+ settings:controller="com.android.settings.applications.appcompat.UserAspectRatioAppsPreferenceController"
+ android:fragment="com.android.settings.applications.manageapplications.ManageApplications">
+ <extra android:name="classname"
+ android:value="com.android.settings.Settings$UserAspectRatioAppListActivity"/>
+ </Preference>
+
+ <Preference
android:key="hibernated_apps"
android:title="@string/unused_apps"
android:summary="@string/summary_placeholder"
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index 32acac6..ab1ee41 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -146,6 +146,13 @@
android:key="quick_settings_tiles"
android:title="@string/quick_settings_developer_tiles"
android:fragment="com.android.settings.development.qstile.DevelopmentTileConfigFragment" />
+
+ <ListPreference
+ android:key="grammatical_gender"
+ android:title="@string/grammatical_gender_title"
+ android:dialogTitle="@string/grammatical_gender_dialog_title"
+ android:entries="@array/grammatical_gender_entries"
+ android:entryValues="@array/grammatical_gender_values" />
</PreferenceCategory>
<PreferenceCategory
diff --git a/res/xml/power_usage_advanced.xml b/res/xml/power_usage_advanced.xml
index 2a1a23c..c129453 100644
--- a/res/xml/power_usage_advanced.xml
+++ b/res/xml/power_usage_advanced.xml
@@ -21,6 +21,18 @@
android:title="@string/advanced_battery_title"
settings:keywords="@string/keywords_battery_usage">
+ <PreferenceCategory
+ android:key="battery_tips_category"
+ settings:controller=
+ "com.android.settings.fuelgauge.batteryusage.BatteryTipsController"
+ settings:isPreferenceVisible="false">
+
+ <com.android.settings.fuelgauge.batteryusage.BatteryTipsCardPreference
+ android:key="battery_tips_card"
+ settings:isPreferenceVisible="false" />
+
+ </PreferenceCategory>
+
<com.android.settings.fuelgauge.batteryusage.BatteryHistoryPreference
android:key="battery_chart"
settings:controller=
diff --git a/res/xml/user_aspect_ratio_details.xml b/res/xml/user_aspect_ratio_details.xml
new file mode 100644
index 0000000..fc921dd
--- /dev/null
+++ b/res/xml/user_aspect_ratio_details.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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/aspect_ratio_title">
+
+ <com.android.settingslib.widget.ActionButtonsPreference
+ android:key="header_view" />
+
+ <com.android.settingslib.widget.SelectorWithWidgetPreference
+ android:key="app_default_pref"
+ android:title="@string/user_aspect_ratio_app_default"/>
+
+ <com.android.settingslib.widget.SelectorWithWidgetPreference
+ android:key="fullscreen_pref"
+ android:title="@string/user_aspect_ratio_fullscreen"/>
+
+ <com.android.settingslib.widget.SelectorWithWidgetPreference
+ android:key="half_screen_pref"
+ android:title="@string/user_aspect_ratio_half_screen"/>
+
+ <com.android.settingslib.widget.SelectorWithWidgetPreference
+ android:key="display_size_pref"
+ android:title="@string/user_aspect_ratio_device_size"/>
+
+ <com.android.settingslib.widget.SelectorWithWidgetPreference
+ android:key="16_9_pref"
+ android:title="@string/user_aspect_ratio_16_9"/>
+
+ <com.android.settingslib.widget.SelectorWithWidgetPreference
+ android:key="4_3_pref"
+ android:title="@string/user_aspect_ratio_4_3"/>
+
+ <com.android.settingslib.widget.SelectorWithWidgetPreference
+ android:key="3_2_pref"
+ android:title="@string/user_aspect_ratio_3_2"/>
+
+ <com.android.settingslib.widget.FooterPreference
+ android:title="@string/app_aspect_ratio_footer"
+ android:selectable="false"
+ settings:searchable="false"/>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 405931c..57d91f0 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -51,26 +51,9 @@
public static class MemtagPageActivity extends SettingsActivity { /* empty */}
public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
public static class CreateShortcutActivity extends SettingsActivity { /* empty */ }
- public static class FaceSettingsActivity extends SettingsActivity {
- @Override
- protected void onCreate(Bundle savedState) {
- setTheme(SetupWizardUtils.getTheme(this, getIntent()));
- setTheme(R.style.SettingsPreferenceTheme_SetupWizard);
- ThemeHelper.trySetDynamicColor(this);
- super.onCreate(savedState);
- }
- }
+ public static class FaceSettingsActivity extends SettingsActivity { /* empty */ }
/** Container for {@link FaceSettings} to use with a pre-defined task affinity. */
- public static class FaceSettingsInternalActivity extends SettingsActivity {
- @Override
- protected void onCreate(Bundle savedState) {
- setTheme(SetupWizardUtils.getTheme(this, getIntent()));
- setTheme(R.style.SettingsPreferenceTheme_SetupWizard);
- ThemeHelper.trySetDynamicColor(this);
- super.onCreate(savedState);
- }
- }
-
+ public static class FaceSettingsInternalActivity extends SettingsActivity { /* empty */ }
public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }
public static class FingerprintSettingsActivityV2 extends SettingsActivity { /* empty */ }
public static class CombinedBiometricSettingsActivity extends SettingsActivity { /* empty */ }
@@ -377,6 +360,8 @@
public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
/** Activity to manage Cloned Apps page */
public static class ClonedAppsListActivity extends SettingsActivity { /* empty */ }
+ /** Activity to manage Aspect Ratio app list page */
+ public static class UserAspectRatioAppListActivity extends SettingsActivity { /* empty */ }
public static class NotificationReviewPermissionsActivity extends SettingsActivity { /* empty */ }
public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ChannelNotificationSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 087c181..bc061e3 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -238,7 +238,7 @@
String tag = getMetricsTag();
return new SharedPreferencesLogger(this, tag,
- FeatureFactory.getFactory(this).getMetricsFeatureProvider(),
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(),
lookupMetricsCategory());
}
@@ -292,8 +292,8 @@
protected void createUiFromIntent(Bundle savedState, Intent intent) {
long startTime = System.currentTimeMillis();
- final FeatureFactory factory = FeatureFactory.getFactory(this);
- mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
+ final FeatureFactory factory = FeatureFactory.getFeatureFactory();
+ mDashboardFeatureProvider = factory.getDashboardFeatureProvider();
if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
diff --git a/src/com/android/settings/accessibility/HearingDevicePairingDetail.java b/src/com/android/settings/accessibility/HearingDevicePairingDetail.java
index de86dcf..117a8ed 100644
--- a/src/com/android/settings/accessibility/HearingDevicePairingDetail.java
+++ b/src/com/android/settings/accessibility/HearingDevicePairingDetail.java
@@ -28,7 +28,8 @@
import com.android.settings.bluetooth.BluetoothDevicePairingDetailBase;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import java.util.Collections;
+import java.util.ArrayList;
+import java.util.List;
/**
* HearingDevicePairingDetail is a page to scan hearing devices. This page shows scanning icons and
@@ -42,10 +43,16 @@
public HearingDevicePairingDetail() {
super();
- final ScanFilter filter = new ScanFilter.Builder()
- .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}, new byte[]{0})
- .build();
- setFilter(Collections.singletonList(filter));
+ final List<ScanFilter> filterList = new ArrayList<>();
+ // Filters for ASHA hearing aids
+ filterList.add(new ScanFilter.Builder().setServiceUuid(BluetoothUuid.HEARING_AID).build());
+ filterList.add(new ScanFilter.Builder()
+ .setServiceData(BluetoothUuid.HEARING_AID, new byte[0]).build());
+ // Filters for LE audio hearing aids
+ filterList.add(new ScanFilter.Builder().setServiceUuid(BluetoothUuid.HAS).build());
+ filterList.add(new ScanFilter.Builder()
+ .setServiceData(BluetoothUuid.HAS, new byte[0]).build());
+ setFilter(filterList);
}
@Override
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java b/src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java
new file mode 100644
index 0000000..ff68fb0
--- /dev/null
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.appcompat;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * Preference controller for
+ * {@link com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider}
+ */
+public class UserAspectRatioAppsPreferenceController extends BasePreferenceController {
+
+ public UserAspectRatioAppsPreferenceController(@NonNull Context context,
+ @NonNull String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return UserAspectRatioManager.isFeatureEnabled(mContext)
+ ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mContext.getResources().getString(R.string.aspect_ratio_summary, Build.MODEL);
+ }
+}
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
new file mode 100644
index 0000000..f8406f9
--- /dev/null
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 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.appcompat;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoWithHeader;
+import com.android.settingslib.widget.ActionButtonsPreference;
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * App specific activity to show aspect ratio overrides
+ */
+public class UserAspectRatioDetails extends AppInfoWithHeader implements
+ SelectorWithWidgetPreference.OnClickListener {
+ private static final String TAG = UserAspectRatioDetails.class.getSimpleName();
+
+ private static final String KEY_HEADER_BUTTONS = "header_view";
+ private static final String KEY_PREF_FULLSCREEN = "fullscreen_pref";
+ private static final String KEY_PREF_HALF_SCREEN = "half_screen_pref";
+ private static final String KEY_PREF_DISPLAY_SIZE = "display_size_pref";
+ private static final String KEY_PREF_16_9 = "16_9_pref";
+ private static final String KEY_PREF_4_3 = "4_3_pref";
+ @VisibleForTesting
+ static final String KEY_PREF_DEFAULT = "app_default_pref";
+ @VisibleForTesting
+ static final String KEY_PREF_3_2 = "3_2_pref";
+
+ private final List<SelectorWithWidgetPreference> mAspectRatioPreferences = new ArrayList<>();
+
+ @NonNull private UserAspectRatioManager mUserAspectRatioManager;
+ @NonNull private String mSelectedKey = KEY_PREF_DEFAULT;
+
+ @Override
+ public void onCreate(@NonNull Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mUserAspectRatioManager = new UserAspectRatioManager(getContext());
+ initPreferences();
+ try {
+ final int userAspectRatio = mUserAspectRatioManager
+ .getUserMinAspectRatioValue(mPackageName, mUserId);
+ mSelectedKey = getSelectedKey(userAspectRatio);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to get user min aspect ratio");
+ }
+ refreshUi();
+ }
+
+ @Override
+ public void onRadioButtonClicked(@NonNull SelectorWithWidgetPreference selected) {
+ final String selectedKey = selected.getKey();
+ if (mSelectedKey.equals(selectedKey)) {
+ return;
+ }
+ final int userAspectRatio = getSelectedUserMinAspectRatio(selectedKey);
+ try {
+ getAspectRatioManager().setUserMinAspectRatio(mPackageName, mUserId, userAspectRatio);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to set user min aspect ratio");
+ return;
+ }
+ // Only update to selected aspect ratio if nothing goes wrong
+ mSelectedKey = selectedKey;
+ updateAllPreferences(mSelectedKey);
+ Log.d(TAG, "Killing application process " + mPackageName);
+ try {
+ final IActivityManager am = ActivityManager.getService();
+ am.stopAppForUser(mPackageName, mUserId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to stop application " + mPackageName);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO(b/292566895): add metrics for logging
+ return 0;
+ }
+
+ @Override
+ protected boolean refreshUi() {
+ if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
+ return false;
+ }
+ updateAllPreferences(mSelectedKey);
+ return true;
+ }
+
+ @Override
+ protected AlertDialog createDialog(int id, int errorCode) {
+ return null;
+ }
+
+ private void launchApplication() {
+ Intent launchIntent = mPm.getLaunchIntentForPackage(mPackageName)
+ .addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
+ if (launchIntent != null) {
+ getContext().startActivityAsUser(launchIntent, new UserHandle(mUserId));
+ }
+ }
+
+ @PackageManager.UserMinAspectRatio
+ private int getSelectedUserMinAspectRatio(@NonNull String selectedKey) {
+ switch (selectedKey) {
+ case KEY_PREF_FULLSCREEN:
+ return USER_MIN_ASPECT_RATIO_FULLSCREEN;
+ case KEY_PREF_HALF_SCREEN:
+ return USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+ case KEY_PREF_DISPLAY_SIZE:
+ return USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+ case KEY_PREF_3_2:
+ return USER_MIN_ASPECT_RATIO_3_2;
+ case KEY_PREF_4_3:
+ return USER_MIN_ASPECT_RATIO_4_3;
+ case KEY_PREF_16_9:
+ return USER_MIN_ASPECT_RATIO_16_9;
+ default:
+ return USER_MIN_ASPECT_RATIO_UNSET;
+ }
+ }
+
+ @NonNull
+ private String getSelectedKey(@PackageManager.UserMinAspectRatio int userMinAspectRatio) {
+ switch (userMinAspectRatio) {
+ case USER_MIN_ASPECT_RATIO_FULLSCREEN:
+ return KEY_PREF_FULLSCREEN;
+ case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+ return KEY_PREF_HALF_SCREEN;
+ case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+ return KEY_PREF_DISPLAY_SIZE;
+ case USER_MIN_ASPECT_RATIO_3_2:
+ return KEY_PREF_3_2;
+ case USER_MIN_ASPECT_RATIO_4_3:
+ return KEY_PREF_4_3;
+ case USER_MIN_ASPECT_RATIO_16_9:
+ return KEY_PREF_16_9;
+ default:
+ return KEY_PREF_DEFAULT;
+ }
+ }
+
+ private void initPreferences() {
+ addPreferencesFromResource(R.xml.user_aspect_ratio_details);
+
+ ((ActionButtonsPreference) findPreference(KEY_HEADER_BUTTONS))
+ .setButton1Text(R.string.launch_instant_app)
+ .setButton1Icon(R.drawable.ic_settings_open)
+ .setButton1OnClickListener(v -> launchApplication());
+
+ addPreference(KEY_PREF_DEFAULT, USER_MIN_ASPECT_RATIO_UNSET);
+ addPreference(KEY_PREF_FULLSCREEN, USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ addPreference(KEY_PREF_DISPLAY_SIZE, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
+ addPreference(KEY_PREF_HALF_SCREEN, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
+ addPreference(KEY_PREF_16_9, USER_MIN_ASPECT_RATIO_16_9);
+ addPreference(KEY_PREF_4_3, USER_MIN_ASPECT_RATIO_4_3);
+ addPreference(KEY_PREF_3_2, USER_MIN_ASPECT_RATIO_3_2);
+ }
+
+ private void addPreference(@NonNull String key,
+ @PackageManager.UserMinAspectRatio int aspectRatio) {
+ final SelectorWithWidgetPreference pref = findPreference(key);
+ if (pref == null) {
+ return;
+ }
+ if (!mUserAspectRatioManager.containsAspectRatioOption(aspectRatio)) {
+ pref.setVisible(false);
+ return;
+ }
+ pref.setTitle(mUserAspectRatioManager.getUserMinAspectRatioEntry(aspectRatio));
+ pref.setOnClickListener(this);
+ mAspectRatioPreferences.add(pref);
+ }
+
+ private void updateAllPreferences(@NonNull String selectedKey) {
+ for (SelectorWithWidgetPreference pref : mAspectRatioPreferences) {
+ pref.setChecked(selectedKey.equals(pref.getKey()));
+ }
+ }
+
+ @VisibleForTesting
+ UserAspectRatioManager getAspectRatioManager() {
+ return mUserAspectRatioManager;
+ }
+}
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java b/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java
new file mode 100644
index 0000000..c132fd0
--- /dev/null
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 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.appcompat;
+
+import android.app.AppGlobals;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class for handling app aspect ratio override
+ * {@link PackageManager.UserMinAspectRatio} set by user
+ */
+public class UserAspectRatioManager {
+ private static final Intent LAUNCHER_ENTRY_INTENT =
+ new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);
+
+ // TODO(b/288142656): Enable user aspect ratio settings by default
+ private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS = false;
+ @VisibleForTesting
+ static final String KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS =
+ "enable_app_compat_user_aspect_ratio_settings";
+ static final String KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN =
+ "enable_app_compat_user_aspect_ratio_fullscreen";
+ private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN = true;
+
+ private final Context mContext;
+ private final IPackageManager mIPm;
+ /** Apps that have launcher entry defined in manifest */
+ private final List<ResolveInfo> mInfoHasLauncherEntryList;
+ private final Map<Integer, String> mUserAspectRatioMap;
+
+ public UserAspectRatioManager(@NonNull Context context) {
+ mContext = context;
+ mIPm = AppGlobals.getPackageManager();
+ mInfoHasLauncherEntryList = context.getPackageManager().queryIntentActivities(
+ UserAspectRatioManager.LAUNCHER_ENTRY_INTENT, PackageManager.GET_META_DATA);
+ mUserAspectRatioMap = getUserMinAspectRatioMapping();
+ }
+
+ /**
+ * Whether user aspect ratio settings is enabled for device.
+ */
+ public static boolean isFeatureEnabled(Context context) {
+ final boolean isBuildTimeFlagEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled);
+ return getValueFromDeviceConfig(KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS,
+ DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS) && isBuildTimeFlagEnabled;
+ }
+
+ /**
+ * @return user-specific {@link PackageManager.UserMinAspectRatio} override for an app
+ */
+ @PackageManager.UserMinAspectRatio
+ public int getUserMinAspectRatioValue(@NonNull String packageName, int uid)
+ throws RemoteException {
+ final int aspectRatio = mIPm.getUserMinAspectRatio(packageName, uid);
+ return containsAspectRatioOption(aspectRatio)
+ ? aspectRatio : PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+ }
+
+ /**
+ * @return corresponding string for {@link PackageManager.UserMinAspectRatio} value
+ */
+ @NonNull
+ public String getUserMinAspectRatioEntry(@PackageManager.UserMinAspectRatio int aspectRatio) {
+ if (!containsAspectRatioOption(aspectRatio)) {
+ return mUserAspectRatioMap.get(PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+ }
+ return mUserAspectRatioMap.get(aspectRatio);
+ }
+
+ /**
+ * @return corresponding aspect ratio string for package name and user
+ */
+ @NonNull
+ public String getUserMinAspectRatioEntry(@NonNull String packageName, int uid)
+ throws RemoteException {
+ final int aspectRatio = getUserMinAspectRatioValue(packageName, uid);
+ return getUserMinAspectRatioEntry(aspectRatio);
+ }
+
+ /**
+ * Whether user aspect ratio option is specified in
+ * {@link R.array.config_userAspectRatioOverrideValues}
+ * and is enabled by device config
+ */
+ public boolean containsAspectRatioOption(@PackageManager.UserMinAspectRatio int option) {
+ if (option == PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN
+ && !isFullscreenOptionEnabled()) {
+ return false;
+ }
+ return mUserAspectRatioMap.containsKey(option);
+ }
+
+ /**
+ * Sets user-specified {@link PackageManager.UserMinAspectRatio} override for an app
+ */
+ public void setUserMinAspectRatio(@NonNull String packageName, int uid,
+ @PackageManager.UserMinAspectRatio int aspectRatio) throws RemoteException {
+ mIPm.setUserMinAspectRatio(packageName, uid, aspectRatio);
+ }
+
+ /**
+ * Whether an app's aspect ratio can be overridden by user. Only apps with launcher entry
+ * will be overridable.
+ */
+ public boolean canDisplayAspectRatioUi(@NonNull ApplicationInfo app) {
+ boolean hasLauncherEntry = mInfoHasLauncherEntryList.stream()
+ .anyMatch(info -> info.activityInfo.packageName.equals(app.packageName));
+ return hasLauncherEntry;
+ }
+
+ /**
+ * Whether fullscreen option in per-app user aspect ratio settings is enabled
+ */
+ @VisibleForTesting
+ boolean isFullscreenOptionEnabled() {
+ final boolean isBuildTimeFlagEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_appCompatUserAppAspectRatioFullscreenIsEnabled);
+ return isBuildTimeFlagEnabled && getValueFromDeviceConfig(
+ KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN,
+ DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN);
+ }
+
+ private static boolean getValueFromDeviceConfig(String name, boolean defaultValue) {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, name, defaultValue);
+ }
+
+ @NonNull
+ private Map<Integer, String> getUserMinAspectRatioMapping() {
+ final String[] userMinAspectRatioStrings = mContext.getResources().getStringArray(
+ R.array.config_userAspectRatioOverrideEntries);
+ final int[] userMinAspectRatioValues = mContext.getResources().getIntArray(
+ R.array.config_userAspectRatioOverrideValues);
+ if (userMinAspectRatioStrings.length != userMinAspectRatioValues.length) {
+ throw new RuntimeException(
+ "config_userAspectRatioOverride options cannot be different length");
+ }
+
+ final Map<Integer, String> userMinAspectRatioMap = new ArrayMap<>();
+ for (int i = 0; i < userMinAspectRatioValues.length; i++) {
+ final int aspectRatioVal = userMinAspectRatioValues[i];
+ final String aspectRatioString = getAspectRatioStringOrDefault(
+ userMinAspectRatioStrings[i], aspectRatioVal);
+ switch (aspectRatioVal) {
+ // Only map known values of UserMinAspectRatio and ignore unknown entries
+ case PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN:
+ case PackageManager.USER_MIN_ASPECT_RATIO_UNSET:
+ case PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+ case PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+ case PackageManager.USER_MIN_ASPECT_RATIO_4_3:
+ case PackageManager.USER_MIN_ASPECT_RATIO_16_9:
+ case PackageManager.USER_MIN_ASPECT_RATIO_3_2:
+ userMinAspectRatioMap.put(aspectRatioVal, aspectRatioString);
+ }
+ }
+ if (!userMinAspectRatioMap.containsKey(PackageManager.USER_MIN_ASPECT_RATIO_UNSET)) {
+ throw new RuntimeException("config_userAspectRatioOverrideValues options must have"
+ + " USER_MIN_ASPECT_RATIO_UNSET value");
+ }
+ return userMinAspectRatioMap;
+ }
+
+ @NonNull
+ private String getAspectRatioStringOrDefault(@Nullable String aspectRatioString,
+ @PackageManager.UserMinAspectRatio int aspectRatioVal) {
+ if (aspectRatioString != null) {
+ return aspectRatioString;
+ }
+ // Options are customized per device and if strings are set to @null, use default
+ switch (aspectRatioVal) {
+ case PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN:
+ return mContext.getString(R.string.user_aspect_ratio_fullscreen);
+ case PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+ return mContext.getString(R.string.user_aspect_ratio_half_screen);
+ case PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+ return mContext.getString(R.string.user_aspect_ratio_device_size);
+ case PackageManager.USER_MIN_ASPECT_RATIO_4_3:
+ return mContext.getString(R.string.user_aspect_ratio_4_3);
+ case PackageManager.USER_MIN_ASPECT_RATIO_16_9:
+ return mContext.getString(R.string.user_aspect_ratio_16_9);
+ case PackageManager.USER_MIN_ASPECT_RATIO_3_2:
+ return mContext.getString(R.string.user_aspect_ratio_3_2);
+ default:
+ return mContext.getString(R.string.user_aspect_ratio_app_default);
+ }
+ }
+
+ @VisibleForTesting
+ void addInfoHasLauncherEntry(@NonNull ResolveInfo infoHasLauncherEntry) {
+ mInfoHasLauncherEntryList.add(infoHasLauncherEntry);
+ }
+}
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 548ca55..d734a27 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -269,6 +269,7 @@
public static final int LIST_TYPE_CLONED_APPS = 17;
public static final int LIST_TYPE_NFC_TAG_APPS = 18;
public static final int LIST_TYPE_TURN_SCREEN_ON = 19;
+ public static final int LIST_TYPE_USER_ASPECT_RATIO_APPS = 20;
// List types that should show instant apps.
public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
index 6574f69..216ce47 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
+++ b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
@@ -20,6 +20,7 @@
import android.util.FeatureFlagUtils
import com.android.settings.Settings.AlarmsAndRemindersActivity
import com.android.settings.Settings.AppBatteryUsageActivity
+import com.android.settings.Settings.UserAspectRatioAppListActivity
import com.android.settings.Settings.ChangeNfcTagAppsActivity
import com.android.settings.Settings.ChangeWifiStateActivity
import com.android.settings.Settings.ClonedAppsListActivity
@@ -40,6 +41,7 @@
import com.android.settings.applications.manageapplications.ManageApplications.LIST_MANAGE_EXTERNAL_STORAGE
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_ALARMS_AND_REMINDERS
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_APPS_LOCALE
+import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_USER_ASPECT_RATIO_APPS
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_BATTERY_OPTIMIZATION
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_CLONED_APPS
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_GAMES
@@ -57,6 +59,7 @@
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WIFI_ACCESS
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WRITE_SETTINGS
import com.android.settings.spa.app.AllAppListPageProvider
+import com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
@@ -65,6 +68,7 @@
import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
+import com.android.settings.spa.app.storage.StorageAppListPageProvider
import com.android.settings.spa.notification.AppListNotificationsPageProvider
import com.android.settings.spa.system.AppLanguagesPageProvider
@@ -92,6 +96,7 @@
ClonedAppsListActivity::class to LIST_TYPE_CLONED_APPS,
ChangeNfcTagAppsActivity::class to LIST_TYPE_NFC_TAG_APPS,
TurnScreenOnSettingsActivity::class to LIST_TYPE_TURN_SCREEN_ON,
+ UserAspectRatioAppListActivity::class to LIST_TYPE_USER_ASPECT_RATIO_APPS,
)
@JvmField
@@ -114,6 +119,10 @@
LIST_TYPE_APPS_LOCALE -> AppLanguagesPageProvider.name
LIST_TYPE_MAIN -> AllAppListPageProvider.name
LIST_TYPE_NFC_TAG_APPS -> NfcTagAppsSettingsProvider.getAppListRoute()
+ LIST_TYPE_USER_ASPECT_RATIO_APPS -> UserAspectRatioAppsPageProvider.name
+ // TODO(b/292165031) enable once sorting is supported
+ //LIST_TYPE_STORAGE -> StorageAppListPageProvider.Apps.name
+ //LIST_TYPE_GAMES -> StorageAppListPageProvider.Games.name
else -> null
}
}
diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
index 2a350f4..46f534d 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
@@ -236,6 +236,9 @@
protected void onResume() {
super.onResume();
+ //reset mNextClick to make sure introduction page would be closed correctly
+ mNextClicked = false;
+
final int errorMsg = checkMaxEnrolled();
if (errorMsg == 0) {
mErrorText.setText(null);
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index 8444ad6..3c4eb10 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -480,10 +480,8 @@
R.string.security_settings_fingerprint_enroll_introduction_v3_message,
DeviceHelper.getDeviceName(getActivity()));
column.mLearnMoreClickListener = learnMoreClickListener;
- if (isSfps()) {
- column.mLearnMoreOverrideText = getText(
- R.string.security_settings_fingerprint_settings_footer_learn_more);
- }
+ column.mLearnMoreOverrideText = getText(
+ R.string.security_settings_fingerprint_settings_footer_learn_more);
mFooterColumns.add(column);
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollProgressBarDrawable.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollProgressBarDrawable.java
index aa3f770..75251cf 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollProgressBarDrawable.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollProgressBarDrawable.java
@@ -202,6 +202,7 @@
return;
}
+ mShowingHelp = showingHelp;
if (mShowingHelp) {
if (mVibrator != null && mIsAccessibilityEnabled) {
mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
@@ -228,7 +229,6 @@
}
}
- mShowingHelp = showingHelp;
mRemainingSteps = remainingSteps;
mTotalSteps = totalSteps;
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
index ba2786e..1fd09a3 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
@@ -89,7 +89,7 @@
mManager = manager;
mProfileManager = mManager.getProfileManager();
mCachedDevice = device;
- mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mContext, mCachedDevice);
+ mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice);
lifecycle.addObserver(this);
}
@@ -481,7 +481,7 @@
for (CachedBluetoothDevice item : mAllOfCachedDevices) {
item.unregisterCallback(this);
}
- mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mContext, mCachedDevice);
+ mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice);
for (CachedBluetoothDevice item : mAllOfCachedDevices) {
item.registerCallback(this);
}
diff --git a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java
index e30bbfb..f72494f 100644
--- a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java
@@ -88,6 +88,7 @@
@VisibleForTesting
LayoutPreference mLayoutPreference;
+ LocalBluetoothManager mManager;
private CachedBluetoothDevice mCachedDevice;
private List<CachedBluetoothDevice> mAllOfCachedDevices;
@VisibleForTesting
@@ -152,8 +153,9 @@
public void init(CachedBluetoothDevice cachedBluetoothDevice,
LocalBluetoothManager bluetoothManager) {
mCachedDevice = cachedBluetoothDevice;
+ mManager = bluetoothManager;
mProfileManager = bluetoothManager.getProfileManager();
- mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mContext, mCachedDevice);
+ mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice);
}
@VisibleForTesting
@@ -300,7 +302,7 @@
for (CachedBluetoothDevice item : mAllOfCachedDevices) {
item.unregisterCallback(this);
}
- mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mContext, mCachedDevice);
+ mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice);
for (CachedBluetoothDevice item : mAllOfCachedDevices) {
item.registerCallback(this);
}
diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java
index 79a2de0..f1d6b20 100644
--- a/src/com/android/settings/bluetooth/Utils.java
+++ b/src/com/android/settings/bluetooth/Utils.java
@@ -235,7 +235,8 @@
* @param cachedBluetoothDevice The main cachedBluetoothDevice.
* @return all cachedBluetoothDevices with the same groupId.
*/
- public static List<CachedBluetoothDevice> getAllOfCachedBluetoothDevices(Context context,
+ public static List<CachedBluetoothDevice> getAllOfCachedBluetoothDevices(
+ LocalBluetoothManager localBtMgr,
CachedBluetoothDevice cachedBluetoothDevice) {
List<CachedBluetoothDevice> cachedBluetoothDevices = new ArrayList<>();
if (cachedBluetoothDevice == null) {
@@ -248,7 +249,6 @@
return cachedBluetoothDevices;
}
- final LocalBluetoothManager localBtMgr = Utils.getLocalBtManager(context);
if (localBtMgr == null) {
Log.e(TAG, "getAllOfCachedBluetoothDevices: no LocalBluetoothManager");
return cachedBluetoothDevices;
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index d4acfa1..9d673b1 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -94,8 +94,8 @@
super.onAttach(context);
mSuppressInjectedTileKeys = Arrays.asList(context.getResources().getStringArray(
R.array.config_suppress_injected_tile_keys));
- mDashboardFeatureProvider = FeatureFactory.getFactory(context).
- getDashboardFeatureProvider(context);
+ mDashboardFeatureProvider =
+ FeatureFactory.getFeatureFactory().getDashboardFeatureProvider();
// Load preference controllers from code
final List<AbstractPreferenceController> controllersFromCode =
createPreferenceControllers(context);
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 047b219..01606f2 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -744,6 +744,7 @@
controllers.add(new ContrastPreferenceController(
context, context.getSystemService(UiModeManager.class)));
controllers.add(new ForceEnableNotesRolePreferenceController(context));
+ controllers.add(new GrammaticalGenderPreferenceController(context));
return controllers;
}
diff --git a/src/com/android/settings/development/GrammaticalGenderPreferenceController.java b/src/com/android/settings/development/GrammaticalGenderPreferenceController.java
new file mode 100644
index 0000000..993b318
--- /dev/null
+++ b/src/com/android/settings/development/GrammaticalGenderPreferenceController.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+/**
+ * Preference controller to control Grammatical Gender
+ */
+public class GrammaticalGenderPreferenceController extends DeveloperOptionsPreferenceController
+ implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
+
+ private static final String GRAMMATICAL_GENDER_KEY =
+ "grammatical_gender";
+ @VisibleForTesting
+ static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender";
+ private final String[] mListValues;
+ private final String[] mListSummaries;
+
+ private IActivityManager mActivityManager;
+
+ public GrammaticalGenderPreferenceController(Context context) {
+ this(context, ActivityManager.getService());
+ }
+
+ @VisibleForTesting
+ GrammaticalGenderPreferenceController(Context context,
+ IActivityManager activityManager) {
+ super(context);
+
+ mListValues = context.getResources().getStringArray(R.array.grammatical_gender_values);
+ mListSummaries = context.getResources().getStringArray(R.array.grammatical_gender_entries);
+ mActivityManager = activityManager;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return GRAMMATICAL_GENDER_KEY;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ SystemProperties.set(GRAMMATICAL_GENDER_PROPERTY, newValue.toString());
+ updateState(mPreference);
+ try {
+ Configuration config = mActivityManager.getConfiguration();
+ config.setGrammaticalGender(Integer.parseInt(newValue.toString()));
+ mActivityManager.updatePersistentConfiguration(config);
+ } catch (RemoteException ex) {
+ // intentional no-op
+ }
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final ListPreference listPreference = (ListPreference) preference;
+ final String currentValue = SystemProperties.get(GRAMMATICAL_GENDER_PROPERTY);
+ int index = 0; // Defaults to Not Selected
+ for (int i = 0; i < mListValues.length; i++) {
+ if (TextUtils.equals(currentValue, mListValues[i])) {
+ index = i;
+ break;
+ }
+ }
+ listPreference.setValue(mListValues[index]);
+ listPreference.setSummary(mListSummaries[index]);
+ }
+}
diff --git a/src/com/android/settings/dream/WhenToDreamPicker.java b/src/com/android/settings/dream/WhenToDreamPicker.java
index 13cdadf..3052d20 100644
--- a/src/com/android/settings/dream/WhenToDreamPicker.java
+++ b/src/com/android/settings/dream/WhenToDreamPicker.java
@@ -50,7 +50,7 @@
@Override
public int getMetricsCategory() {
- return SettingsEnums.DREAM;
+ return SettingsEnums.SETTINGS_WHEN_TO_DREAM;
}
@Override
diff --git a/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java b/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java
index 2376c80..0f4634e 100644
--- a/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java
+++ b/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java
@@ -24,6 +24,7 @@
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.DialogInterface;
import android.os.Process;
import android.os.UserHandle;
import android.view.LayoutInflater;
@@ -88,10 +89,11 @@
public AlertDialog.Builder prepareDialogBuilder(String restriction,
EnforcedAdmin enforcedAdmin) {
+ DialogInterface.OnClickListener listener = mActionDisabledByAdminController
+ .getPositiveButtonListener(mActivity, enforcedAdmin);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity)
- .setPositiveButton(R.string.suggestion_button_close,
- mActionDisabledByAdminController
- .getPositiveButtonListener(mActivity, enforcedAdmin))
+ .setPositiveButton(listener == null
+ ? R.string.suggestion_button_close : R.string.okay, listener)
.setView(mDialogView);
prepareDialogBuilder(builder, restriction, enforcedAdmin);
return builder;
diff --git a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
index 4b9e6ef..8697e43 100644
--- a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
+++ b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
@@ -16,8 +16,8 @@
package com.android.settings.fuelgauge;
-import android.content.ContentResolver;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
@@ -25,8 +25,6 @@
import androidx.annotation.VisibleForTesting;
-import com.android.settings.R;
-import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry;
import com.android.settings.fuelgauge.batterysaver.BatterySaverScheduleRadioButtonsController;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
@@ -41,6 +39,7 @@
@Override
public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive: " + intent + " owner: " + BatteryBackupHelper.isOwner());
if (intent != null
&& Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())
&& BatteryBackupHelper.isOwner()) {
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
index c06e7f0..258ded1 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.Intent;
+import android.os.Bundle;
import android.util.ArrayMap;
import android.util.SparseIntArray;
@@ -37,6 +38,16 @@
boolean isBatteryUsageEnabled();
/**
+ * Check whether the battery tips card is enabled in the battery usage page
+ */
+ boolean isBatteryTipsEnabled();
+
+ /**
+ * Check whether the feedback card is enabled in the battery tips card
+ */
+ boolean isBatteryTipsFeedbackEnabled();
+
+ /**
* Returns a threshold (in milliseconds) for the minimal screen on time in battery usage list
*/
double getBatteryUsageListScreenOnTimeThresholdInMs();
@@ -129,9 +140,14 @@
boolean delayHourlyJobWhenBooting();
/**
- * Insert device usage data for anomaly detection
+ * Insert settings configuration data for anomaly detection
*/
- void insertSettingsData(Context context);
+ void insertSettingsData(Context context, double displayDrain);
+
+ /**
+ * Returns {@link Bundle} for settings anomaly detection result
+ */
+ Bundle detectSettingsAnomaly(Context context, double displayDrain);
/**
* Gets an intent for one time bypass charge limited to resume charging.
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
index 89d793a..9b5bb5e 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.os.Bundle;
import android.os.Process;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -75,6 +76,16 @@
}
@Override
+ public boolean isBatteryTipsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isBatteryTipsFeedbackEnabled() {
+ return true;
+ }
+
+ @Override
public double getBatteryUsageListScreenOnTimeThresholdInMs() {
return 0;
}
@@ -161,7 +172,12 @@
}
@Override
- public void insertSettingsData(Context context) {}
+ public void insertSettingsData(Context context, double displayDrain) {}
+
+ @Override
+ public Bundle detectSettingsAnomaly(Context context, double displayDrain) {
+ return null;
+ }
@Override
public Set<Integer> getOthersSystemComponentSet() {
diff --git a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java
index f449723..7287302 100644
--- a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.content.Context;
+import android.os.BatteryManager;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -139,7 +140,10 @@
if (Utils.containsIncompatibleChargers(mContext, TAG)) {
return mContext.getString(R.string.battery_info_status_not_charging);
}
- if (!info.discharging && info.chargeLabel != null) {
+ if (info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
+ // Present status only if no remaining time or status anomalous
+ return info.statusLabel;
+ } else if (!info.discharging && info.chargeLabel != null) {
return info.chargeLabel;
} else if (info.remainingLabel == null) {
return info.batteryPercentString;
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
index 1c5616f..48cfb7a 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
@@ -52,7 +52,7 @@
@Override
public int getIconId() {
- return R.drawable.ic_battery_alert_theme;
+ return R.drawable.ic_battery_charger;
}
@Override
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index 17d9c8a..b7e1885 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -98,6 +98,20 @@
void onScreenOnTimeUpdated(Long screenOnTime, String slotTimestamp);
}
+ /**
+ * A callback listener for the battery tips card is updated.
+ * This happens when battery tips card is ready.
+ */
+ public interface OnBatteryTipsUpdatedListener {
+ /**
+ * The callback function for the battery tips card is updated.
+ * @param title the title of the battery tip card
+ * @param summary the summary of the battery tip card
+ */
+ void onBatteryTipsUpdated(String title, String summary);
+ }
+
+
@VisibleForTesting
Context mPrefContext;
@VisibleForTesting
@@ -119,6 +133,7 @@
private List<BatteryChartViewModel> mHourlyViewModels;
private OnBatteryUsageUpdatedListener mOnBatteryUsageUpdatedListener;
private OnScreenOnTimeUpdatedListener mOnScreenOnTimeUpdatedListener;
+ private OnBatteryTipsUpdatedListener mOnBatteryTipsUpdatedListener;
private final SettingsActivity mActivity;
private final MetricsFeatureProvider mMetricsFeatureProvider;
@@ -209,6 +224,10 @@
mOnScreenOnTimeUpdatedListener = listener;
}
+ void setOnBatteryTipsUpdatedListener(OnBatteryTipsUpdatedListener listener) {
+ mOnBatteryTipsUpdatedListener = listener;
+ }
+
void setBatteryHistoryMap(
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
Log.d(TAG, "setBatteryHistoryMap() " + (batteryHistoryMap == null ? "null"
@@ -344,6 +363,10 @@
}
mOnBatteryUsageUpdatedListener.onBatteryUsageUpdated(
slotUsageData, getSlotInformation(), isBatteryUsageMapNullOrEmpty());
+
+ if (mOnBatteryTipsUpdatedListener != null) {
+ mOnBatteryTipsUpdatedListener.onBatteryTipsUpdated(null, null);
+ }
}
return true;
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java
new file mode 100644
index 0000000..89e2ce9
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+
+import com.google.android.material.button.MaterialButton;
+
+/**
+ * A preference for displaying the battery tips card view.
+ */
+public class BatteryTipsCardPreference extends Preference implements View.OnClickListener {
+
+ private static final String TAG = "BatteryTipsCardPreference";
+
+ private final PowerUsageFeatureProvider mPowerUsageFeatureProvider;
+
+ private MaterialButton mActionButton;
+ private ImageButton mDismissButton;
+ private ImageButton mThumbUpButton;
+ private ImageButton mThumbDownButton;
+ private CharSequence mTitle;
+ private CharSequence mSummary;
+
+ public BatteryTipsCardPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setLayoutResource(R.layout.battery_tips_card);
+ setSelectable(false);
+ mPowerUsageFeatureProvider = FeatureFactory.getFeatureFactory()
+ .getPowerUsageFeatureProvider();
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ notifyChanged();
+ }
+
+ @Override
+ public void setSummary(CharSequence summary) {
+ mSummary = summary;
+ notifyChanged();
+ }
+
+ @Override
+ public void onClick(View view) {
+ // TODO: replace with the settings anomaly obtained from detectSettingsAnomaly();
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder view) {
+ super.onBindViewHolder(view);
+
+ ((TextView) view.findViewById(R.id.title)).setText(mTitle);
+ ((TextView) view.findViewById(R.id.summary)).setText(mSummary);
+
+ mActionButton = (MaterialButton) view.findViewById(R.id.action_button);
+ mActionButton.setOnClickListener(this);
+ mDismissButton = (ImageButton) view.findViewById(R.id.dismiss_button);
+ mDismissButton.setOnClickListener(this);
+
+ if (!mPowerUsageFeatureProvider.isBatteryTipsFeedbackEnabled()) {
+ return;
+ }
+ view.findViewById(R.id.tips_card)
+ .setBackgroundResource(R.drawable.battery_tips_half_rounded_top_bg);
+ view.findViewById(R.id.feedback_card).setVisibility(View.VISIBLE);
+
+ mThumbUpButton = (ImageButton) view.findViewById(R.id.thumb_up);
+ mThumbUpButton.setOnClickListener(this);
+ mThumbDownButton = (ImageButton) view.findViewById(R.id.thumb_down);
+ mThumbDownButton.setOnClickListener(this);
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
new file mode 100644
index 0000000..87c43a1
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import android.content.Context;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+
+/** Controls the update for battery tips card */
+public class BatteryTipsController extends BasePreferenceController {
+
+ private static final String TAG = "BatteryTipsController";
+ private static final String ROOT_PREFERENCE_KEY = "battery_tips_category";
+ private static final String CARD_PREFERENCE_KEY = "battery_tips_card";
+
+ private final PowerUsageFeatureProvider mPowerUsageFeatureProvider;
+
+ private Context mPrefContext;
+ private BatteryTipsCardPreference mCardPreference;
+
+ public BatteryTipsController(Context context) {
+ super(context, ROOT_PREFERENCE_KEY);
+ mPowerUsageFeatureProvider = FeatureFactory.getFeatureFactory()
+ .getPowerUsageFeatureProvider();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPrefContext = screen.getContext();
+ mCardPreference = screen.findPreference(CARD_PREFERENCE_KEY);
+ }
+
+ /**
+ * Update the card visibility and contents.
+ * @param title a string not extend 2 lines.
+ * @param summary a string not extend 10 lines.
+ */
+ // TODO: replace parameters with SettingsAnomaly Data Proto
+ public void handleBatteryTipsCardUpdated(String title, String summary) {
+ if (!mPowerUsageFeatureProvider.isBatteryTipsEnabled()) {
+ mCardPreference.setVisible(false);
+ return;
+ }
+ if (title == null || summary == null) {
+ mCardPreference.setVisible(false);
+ return;
+ }
+ mCardPreference.setTitle(title);
+ mCardPreference.setSummary(summary);
+ mCardPreference.setVisible(true);
+ }
+
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
index 67462b3..ebf1543 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
@@ -83,6 +83,10 @@
recheckIntent.setClass(context, BootBroadcastReceiver.class);
final long delayedTime = getRescheduleTimeForBootAction(context);
mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent), delayedTime);
+
+ // Refreshes the usage source from UsageStatsManager when booting.
+ DatabaseUtils.removeUsageSource(context);
+
BatteryUsageLogUtils.writeLog(context, Action.RECHECK_JOB, "delay:" + delayedTime);
} else if (ACTION_SETUP_WIZARD_FINISHED.equals(action)) {
ElapsedTimeUtils.storeSuwFinishedTimestamp(context, System.currentTimeMillis());
diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
index 2c98c4b..3fc44cc 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
@@ -17,7 +17,6 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManager;
import android.content.ContentValues;
@@ -27,7 +26,6 @@
import android.os.BatteryUsageStats;
import android.os.Build;
import android.os.LocaleList;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.text.format.DateFormat;
@@ -67,6 +65,12 @@
public static final int CONSUMER_TYPE_USER_BATTERY = 2;
public static final int CONSUMER_TYPE_SYSTEM_BATTERY = 3;
+ public static final int DEFAULT_USAGE_SOURCE = UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+ public static final int EMPTY_USAGE_SOURCE = -1;
+
+ @VisibleForTesting
+ static int sUsageSource = EMPTY_USAGE_SOURCE;
+
private ConvertUtils() {
}
@@ -181,8 +185,7 @@
/** Converts to {@link AppUsageEvent} from {@link Event} */
@Nullable
public static AppUsageEvent convertToAppUsageEvent(
- Context context, final IUsageStatsManager usageStatsManager, final Event event,
- final long userId) {
+ Context context, final Event event, final long userId) {
final String packageName = event.getPackageName();
if (packageName == null) {
// See b/190609174: Event package names should never be null, but sometimes they are.
@@ -207,7 +210,7 @@
}
final String effectivePackageName =
- getEffectivePackageName(usageStatsManager, packageName, taskRootPackageName);
+ getEffectivePackageName(context, packageName, taskRootPackageName);
try {
final long uid = context
.getPackageManager()
@@ -323,9 +326,8 @@
*/
@VisibleForTesting
static String getEffectivePackageName(
- final IUsageStatsManager usageStatsManager, final String packageName,
- final String taskRootPackageName) {
- int usageSource = getUsageSource(usageStatsManager);
+ Context context, final String packageName, final String taskRootPackageName) {
+ final int usageSource = getUsageSource(context);
switch (usageSource) {
case UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY:
return !TextUtils.isEmpty(taskRootPackageName)
@@ -370,18 +372,11 @@
}
}
- /**
- * Returns what App Usage Observers will consider the source of usage for an activity.
- *
- * @see UsageStatsManager#getUsageSource()
- */
- private static int getUsageSource(final IUsageStatsManager usageStatsManager) {
- try {
- return usageStatsManager.getUsageSource();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to getUsageSource", e);
- return UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+ private static int getUsageSource(Context context) {
+ if (sUsageSource == EMPTY_USAGE_SOURCE) {
+ sUsageSource = DatabaseUtils.getUsageSource(context);
}
+ return sUsageSource;
}
private static AppUsageEventType getAppUsageEventType(final int eventType) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
index 0f67e6a..1c851fd 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
@@ -397,8 +397,8 @@
}
// Generates the indexed AppUsagePeriod list data for each corresponding time slot for
// further use.
- mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap(mRawStartTimestamp,
- mHourlyBatteryLevelsPerDay, mAppUsageEventList, mBatteryEventList);
+ mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap(
+ mContext, mHourlyBatteryLevelsPerDay, mAppUsageEventList, mBatteryEventList);
}
private void tryToGenerateFinalDataAndApplyCallback() {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index 6de3f59..f1a8063 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -32,7 +32,6 @@
import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UidBatteryConsumer;
import android.os.UserBatteryConsumer;
import android.os.UserHandle;
@@ -78,8 +77,6 @@
private static final int MIN_DAILY_DATA_SIZE = 2;
private static final int MIN_TIMESTAMP_DATA_SIZE = 2;
private static final int MAX_DIFF_SECONDS_OF_UPPER_TIMESTAMP = 5;
- // Maximum total time value for each hourly slot cumulative data at most 2 hours.
- private static final float TOTAL_HOURLY_TIME_THRESHOLD = DateUtils.HOUR_IN_MILLIS * 2;
private static final long MIN_TIME_SLOT = DateUtils.HOUR_IN_MILLIS * 2;
private static final String MEDIASERVER_PACKAGE_NAME = "mediaserver";
private static final String ANDROID_CORE_APPS_SHARED_USER_ID = "android.uid.shared";
@@ -111,11 +108,6 @@
@VisibleForTesting
static Set<String> sTestSystemAppsPackageNames;
- @VisibleForTesting
- static IUsageStatsManager sUsageStatsManager =
- IUsageStatsManager.Stub.asInterface(
- ServiceManager.getService(Context.USAGE_STATS_SERVICE));
-
public static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER =
"CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER";
@@ -271,7 +263,7 @@
@Nullable
public static Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
generateAppUsagePeriodMap(
- final long rawStartTimestamp,
+ Context context,
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
final List<AppUsageEvent> appUsageEventList,
final List<BatteryEvent> batteryEventList) {
@@ -305,7 +297,7 @@
// The value could be null when there is no data in the hourly slot.
dailyMap.put(
hourlyIndex,
- buildAppUsagePeriodList(hourlyAppUsageEventList, batteryEventList,
+ buildAppUsagePeriodList(context, hourlyAppUsageEventList, batteryEventList,
startTimestamp, endTimestamp));
}
}
@@ -346,8 +338,7 @@
break;
}
final AppUsageEvent appUsageEvent =
- ConvertUtils.convertToAppUsageEvent(
- context, sUsageStatsManager, event, userId);
+ ConvertUtils.convertToAppUsageEvent(context, event, userId);
if (appUsageEvent != null) {
numEventsFetched++;
appUsageEventList.add(appUsageEvent);
@@ -661,8 +652,8 @@
@VisibleForTesting
@Nullable
static Map<Long, Map<String, List<AppUsagePeriod>>> buildAppUsagePeriodList(
- final List<AppUsageEvent> appUsageEvents, final List<BatteryEvent> batteryEventList,
- final long startTime, final long endTime) {
+ Context context, final List<AppUsageEvent> appUsageEvents,
+ final List<BatteryEvent> batteryEventList, final long startTime, final long endTime) {
if (appUsageEvents.isEmpty()) {
return null;
}
@@ -702,7 +693,7 @@
final AppUsageEvent firstEvent = usageEvents.get(0);
final long eventUserId = firstEvent.getUserId();
final String packageName = getEffectivePackageName(
- sUsageStatsManager,
+ context,
firstEvent.getPackageName(),
firstEvent.getTaskRootPackageName());
usageEvents.addAll(deviceEvents);
@@ -975,7 +966,7 @@
final long startTime = DatabaseUtils.getAppUsageStartTimestampOfUser(
context, userID, earliestTimestamp);
return loadAppUsageEventsForUserFromService(
- sUsageStatsManager, startTime, now, userID, callingPackage);
+ DatabaseUtils.sUsageStatsManager, startTime, now, userID, callingPackage);
}
@Nullable
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index 8d1a2f9..ea1f3ed 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -15,6 +15,8 @@
*/
package com.android.settings.fuelgauge.batteryusage;
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageStatsManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -28,6 +30,8 @@
import android.os.BatteryUsageStats;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserManager;
import android.util.Log;
@@ -63,6 +67,7 @@
static final int DATA_RETENTION_INTERVAL_DAY = 9;
static final String KEY_LAST_LOAD_FULL_CHARGE_TIME = "last_load_full_charge_time";
static final String KEY_LAST_UPLOAD_FULL_CHARGE_TIME = "last_upload_full_charge_time";
+ static final String KEY_LAST_USAGE_SOURCE = "last_usage_source";
/** An authority name of the battery content provider. */
public static final String AUTHORITY = "com.android.settings.battery.usage.provider";
@@ -74,8 +79,6 @@
public static final String BATTERY_STATE_TABLE = "BatteryState";
/** A path name for app usage latest timestamp query. */
public static final String APP_USAGE_LATEST_TIMESTAMP_PATH = "appUsageLatestTimestamp";
- /** A class name for battery usage data provider. */
- public static final String SETTINGS_PACKAGE_PATH = "com.android.settings";
/** Key for query parameter timestamp used in BATTERY_CONTENT_URI **/
public static final String QUERY_KEY_TIMESTAMP = "timestamp";
/** Key for query parameter userid used in APP_USAGE_EVENT_URI **/
@@ -114,6 +117,11 @@
@VisibleForTesting
static Supplier<Cursor> sFakeSupplier;
+ @VisibleForTesting
+ static IUsageStatsManager sUsageStatsManager =
+ IUsageStatsManager.Stub.asInterface(
+ ServiceManager.getService(Context.USAGE_STATS_SERVICE));
+
private DatabaseUtils() {
}
@@ -468,6 +476,37 @@
SHARED_PREFS_FILE, Context.MODE_PRIVATE);
}
+ static void removeUsageSource(Context context) {
+ final SharedPreferences sharedPreferences = getSharedPreferences(context);
+ if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) {
+ sharedPreferences.edit().remove(KEY_LAST_USAGE_SOURCE).apply();
+ }
+ }
+
+ /**
+ * Returns what App Usage Observers will consider the source of usage for an activity.
+ *
+ * @see UsageStatsManager#getUsageSource()
+ */
+ static int getUsageSource(Context context) {
+ final SharedPreferences sharedPreferences = getSharedPreferences(context);
+ if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) {
+ return sharedPreferences
+ .getInt(KEY_LAST_USAGE_SOURCE, ConvertUtils.DEFAULT_USAGE_SOURCE);
+ }
+ int usageSource = ConvertUtils.DEFAULT_USAGE_SOURCE;
+
+ try {
+ usageSource = sUsageStatsManager.getUsageSource();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to getUsageSource", e);
+ }
+ if (sharedPreferences != null) {
+ sharedPreferences.edit().putInt(KEY_LAST_USAGE_SOURCE, usageSource).apply();
+ }
+ return usageSource;
+ }
+
static void recordDateTime(Context context, String preferenceKey) {
final SharedPreferences sharedPreferences = getSharedPreferences(context);
if (sharedPreferences != null) {
@@ -564,7 +603,7 @@
private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
Context context, Uri batteryStateUri) {
- context = DatabaseUtils.getParentContext(context);
+ context = getParentContext(context);
if (context == null) {
return null;
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
index 7c4478e..5a96fb4 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
@@ -34,6 +34,8 @@
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.fuelgauge.BatteryBroadcastReceiver;
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
@@ -143,6 +145,16 @@
controllers.add(screenOnTimeController);
controllers.add(batteryUsageBreakdownController);
setBatteryChartPreferenceController();
+
+ final PowerUsageFeatureProvider powerUsageFeatureProvider =
+ FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider();
+ if (powerUsageFeatureProvider.isBatteryTipsEnabled()) {
+ BatteryTipsController batteryTipsController = new BatteryTipsController(context);
+ mBatteryChartPreferenceController.setOnBatteryTipsUpdatedListener(
+ batteryTipsController::handleBatteryTipsCardUpdated);
+ controllers.add(batteryTipsController);
+ }
+
return controllers;
}
diff --git a/src/com/android/settings/fuelgauge/protos/Android.bp b/src/com/android/settings/fuelgauge/protos/Android.bp
index 3af2aef..1f3cdd9 100644
--- a/src/com/android/settings/fuelgauge/protos/Android.bp
+++ b/src/com/android/settings/fuelgauge/protos/Android.bp
@@ -30,3 +30,11 @@
},
srcs: ["fuelgauge_usage_state.proto"],
}
+
+java_library {
+ name: "power-anomaly-event-protos-lite",
+ proto: {
+ type: "lite",
+ },
+ srcs: ["power_anomaly_event.proto"],
+}
diff --git a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
new file mode 100644
index 0000000..b4277c4
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
@@ -0,0 +1,37 @@
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "com.android.settings.fuelgauge.batteryusage";
+option java_outer_classname = "PowerAnomalyEventProto";
+
+message PowerAnomalyEvent {
+ optional int64 timestamp = 1;
+ optional string type = 2; // e.g. settings, apps
+ optional string key = 3; // e.g. brightness, significant_increase
+ optional float score = 4;
+ oneof info {
+ WarningBannerInfo warning_banner_info = 5;
+ WarningItemInfo warning_item_info = 6;
+ }
+}
+
+message WarningBannerInfo {
+ optional string title_string = 1;
+ optional string description_string = 2;
+ optional string main_button_string = 3;
+ optional string main_button_action = 4;
+ optional string cancel_button_string = 5;
+ optional string cancel_button_action = 6;
+}
+
+message WarningItemInfo {
+ optional int64 start_timestamp = 1;
+ optional int64 end_timestamp = 2;
+ optional string top_card_string = 3;
+ optional string title_string = 4;
+ optional string description_string = 5;
+ optional string main_button_string = 6;
+ optional string main_button_action = 7;
+ optional string cancel_button_string = 8;
+ optional string cancel_button_action = 9;
+}
diff --git a/src/com/android/settings/media/MediaDeviceUpdateWorker.java b/src/com/android/settings/media/MediaDeviceUpdateWorker.java
index 6fa512e..d4aef47 100644
--- a/src/com/android/settings/media/MediaDeviceUpdateWorker.java
+++ b/src/com/android/settings/media/MediaDeviceUpdateWorker.java
@@ -40,7 +40,6 @@
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -224,18 +223,8 @@
return mLocalMediaManager.getSessionName();
}
- List<RoutingSessionInfo> getActiveRemoteMediaDevice() {
- final List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
- for (RoutingSessionInfo info : mLocalMediaManager.getActiveMediaSession()) {
- if (!info.isSystemSession()) {
- if (DEBUG) {
- Log.d(TAG, "getActiveRemoteMediaDevice() info : " + info.toString()
- + ", package name : " + info.getClientPackageName());
- }
- sessionInfos.add(info);
- }
- }
- return sessionInfos;
+ List<RoutingSessionInfo> getActiveRemoteMediaDevices() {
+ return mLocalMediaManager.getRemoteRoutingSessions();
}
/**
diff --git a/src/com/android/settings/media/RemoteMediaSlice.java b/src/com/android/settings/media/RemoteMediaSlice.java
index 193a6c7..a9980e8 100644
--- a/src/com/android/settings/media/RemoteMediaSlice.java
+++ b/src/com/android/settings/media/RemoteMediaSlice.java
@@ -100,8 +100,9 @@
Log.e(TAG, "Unable to get the slice worker.");
return listBuilder.build();
}
+
// Only displaying remote devices
- final List<RoutingSessionInfo> infos = getWorker().getActiveRemoteMediaDevice();
+ final List<RoutingSessionInfo> infos = getWorker().getActiveRemoteMediaDevices();
if (infos.isEmpty()) {
Log.d(TAG, "No active remote media device");
return listBuilder.build();
diff --git a/src/com/android/settings/network/apn/ApnEditor.java b/src/com/android/settings/network/apn/ApnEditor.java
index 82584f6..5ff4b35 100644
--- a/src/com/android/settings/network/apn/ApnEditor.java
+++ b/src/com/android/settings/network/apn/ApnEditor.java
@@ -435,15 +435,20 @@
return false;
}
- if (hasAllApns(apnTypesArray1) || TextUtils.isEmpty(apnTypes2)) {
+ final String[] apnTypesArray1LowerCase = new String[apnTypesArray1.length];
+ for (int i = 0; i < apnTypesArray1.length; i++) {
+ apnTypesArray1LowerCase[i] = apnTypesArray1[i].toLowerCase();
+ }
+
+ if (hasAllApns(apnTypesArray1LowerCase) || TextUtils.isEmpty(apnTypes2)) {
return true;
}
- final List apnTypesList1 = Arrays.asList(apnTypesArray1);
+ final List apnTypesList1 = Arrays.asList(apnTypesArray1LowerCase);
final String[] apnTypesArray2 = apnTypes2.split(",");
for (String apn : apnTypesArray2) {
- if (apnTypesList1.contains(apn.trim())) {
+ if (apnTypesList1.contains(apn.trim().toLowerCase())) {
Log.d(TAG, "apnTypesMatch: true because match found for " + apn.trim());
return true;
}
diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
index 4160299..6f4d3c3 100644
--- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
+++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
@@ -379,7 +379,7 @@
DIALOG_TAG_DISABLE_SIM_CONFIRMATION,
title,
null,
- getString(R.string.condition_turn_off),
+ getString(R.string.sim_action_turn_off),
getString(R.string.sim_action_cancel));
}
diff --git a/src/com/android/settings/notification/RemoteVolumeGroupController.java b/src/com/android/settings/notification/RemoteVolumeGroupController.java
index a943d5f..e2faf02 100644
--- a/src/com/android/settings/notification/RemoteVolumeGroupController.java
+++ b/src/com/android/settings/notification/RemoteVolumeGroupController.java
@@ -54,7 +54,7 @@
static final String SWITCHER_PREFIX = "OUTPUT_SWITCHER";
private PreferenceCategory mPreferenceCategory;
- private List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>();
+ private final List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>();
@VisibleForTesting
LocalMediaManager mLocalMediaManager;
@@ -89,11 +89,7 @@
private void initRemoteMediaSession() {
mRoutingSessionInfos.clear();
- for (RoutingSessionInfo info : mLocalMediaManager.getActiveMediaSession()) {
- if (!info.isSystemSession()) {
- mRoutingSessionInfos.add(info);
- }
- }
+ mRoutingSessionInfos.addAll(mLocalMediaManager.getRemoteRoutingSessions());
}
@Override
diff --git a/src/com/android/settings/notification/SeekBarVolumizerFactory.java b/src/com/android/settings/notification/SeekBarVolumizerFactory.java
new file mode 100644
index 0000000..6fac2c1
--- /dev/null
+++ b/src/com/android/settings/notification/SeekBarVolumizerFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.notification;
+
+import android.content.Context;
+import android.net.Uri;
+import android.preference.SeekBarVolumizer;
+
+/**
+ * Testable wrapper around {@link SeekBarVolumizer} constructor.
+ */
+public class SeekBarVolumizerFactory {
+ private final Context mContext;
+
+ public SeekBarVolumizerFactory(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Creates a new SeekBarVolumizer.
+ *
+ * @param streamType of the audio manager.
+ * @param defaultUri of the volume.
+ * @param sbvc callback of the seekbar volumizer.
+ * @return a SeekBarVolumizer.
+ */
+ public SeekBarVolumizer create(int streamType, Uri defaultUri, SeekBarVolumizer.Callback sbvc) {
+ return new SeekBarVolumizer(mContext, streamType, defaultUri, sbvc);
+ }
+}
diff --git a/src/com/android/settings/notification/VolumeSeekBarPreference.java b/src/com/android/settings/notification/VolumeSeekBarPreference.java
index dd9df44..2156c05 100644
--- a/src/com/android/settings/notification/VolumeSeekBarPreference.java
+++ b/src/com/android/settings/notification/VolumeSeekBarPreference.java
@@ -37,6 +37,8 @@
import com.android.settings.R;
import com.android.settings.widget.SeekBarPreference;
+import java.text.NumberFormat;
+import java.util.Locale;
import java.util.Objects;
/** A slider preference that directly controls an audio stream volume (no dialog) **/
@@ -47,8 +49,9 @@
protected SeekBar mSeekBar;
private int mStream;
+ private SeekBarVolumizer mVolumizer;
@VisibleForTesting
- SeekBarVolumizer mVolumizer;
+ SeekBarVolumizerFactory mSeekBarVolumizerFactory;
private Callback mCallback;
private Listener mListener;
private ImageView mIconView;
@@ -62,30 +65,36 @@
private boolean mStopped;
@VisibleForTesting
AudioManager mAudioManager;
+ private Locale mLocale;
+ private NumberFormat mNumberFormat;
public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.preference_volume_slider);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mSeekBarVolumizerFactory = new SeekBarVolumizerFactory(context);
}
public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayoutResource(R.layout.preference_volume_slider);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mSeekBarVolumizerFactory = new SeekBarVolumizerFactory(context);
}
public VolumeSeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.preference_volume_slider);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mSeekBarVolumizerFactory = new SeekBarVolumizerFactory(context);
}
public VolumeSeekBarPreference(Context context) {
super(context);
setLayoutResource(R.layout.preference_volume_slider);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mSeekBarVolumizerFactory = new SeekBarVolumizerFactory(context);
}
public void setStream(int stream) {
@@ -148,6 +157,7 @@
if (mCallback != null) {
mCallback.onStreamValueChanged(mStream, progress);
}
+ overrideSeekBarStateDescription(formatStateDescription(progress));
}
@Override
public void onMuted(boolean muted, boolean zenMuted) {
@@ -175,7 +185,7 @@
};
final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
if (mVolumizer == null) {
- mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc);
+ mVolumizer = mSeekBarVolumizerFactory.create(mStream, sampleUri, sbvc);
}
mVolumizer.start();
mVolumizer.setSeekBar(mSeekBar);
@@ -217,6 +227,33 @@
+ "/" + R.raw.media_volume);
}
+ @VisibleForTesting
+ CharSequence formatStateDescription(int progress) {
+ // This code follows the same approach in ProgressBar.java, but it rounds down the percent
+ // to match it with what the talkback feature says after any progress change. (b/285458191)
+ // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed
+ // non-null, so the first time this is called we will always get the appropriate
+ // NumberFormat, then never regenerate it unless the locale changes on the fly.
+ Locale curLocale = getContext().getResources().getConfiguration().getLocales().get(0);
+ if (mLocale == null || !mLocale.equals(curLocale)) {
+ mLocale = curLocale;
+ mNumberFormat = NumberFormat.getPercentInstance(mLocale);
+ }
+ return mNumberFormat.format(getPercent(progress));
+ }
+
+ @VisibleForTesting
+ double getPercent(float progress) {
+ final float maxProgress = getMax();
+ final float minProgress = getMin();
+ final float diffProgress = maxProgress - minProgress;
+ if (diffProgress <= 0.0f) {
+ return 0.0f;
+ }
+ final float percent = (progress - minProgress) / diffProgress;
+ return Math.floor(Math.max(0.0f, Math.min(1.0f, percent)) * 100) / 100;
+ }
+
public void setSuppressionText(String text) {
if (Objects.equals(text, mSuppressionText)) return;
mSuppressionText = text;
diff --git a/src/com/android/settings/overlay/FeatureFactory.kt b/src/com/android/settings/overlay/FeatureFactory.kt
index f38b5da..5f456e0 100644
--- a/src/com/android/settings/overlay/FeatureFactory.kt
+++ b/src/com/android/settings/overlay/FeatureFactory.kt
@@ -81,7 +81,7 @@
*/
abstract val batterySettingsFeatureProvider: BatterySettingsFeatureProvider
- abstract fun getDashboardFeatureProvider(context: Context): DashboardFeatureProvider
+ abstract val dashboardFeatureProvider: DashboardFeatureProvider
abstract val dockUpdaterFeatureProvider: DockUpdaterFeatureProvider
abstract val applicationFeatureProvider: ApplicationFeatureProvider
abstract val localeFeatureProvider: LocaleFeatureProvider
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.kt b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
index b6dd221..0da5871 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.kt
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
@@ -67,8 +67,6 @@
* [FeatureFactory] implementation for AOSP Settings.
*/
open class FeatureFactoryImpl : FeatureFactory() {
- private val dashboardFeatureProvider by lazy { DashboardFeatureProviderImpl(appContext) }
-
private val enterprisePrivacyFeatureProvider by lazy {
EnterprisePrivacyFeatureProviderImpl(
appContext,
@@ -100,7 +98,7 @@
override val batterySettingsFeatureProvider by lazy { BatterySettingsFeatureProviderImpl() }
- override fun getDashboardFeatureProvider(context: Context) = dashboardFeatureProvider
+ override val dashboardFeatureProvider by lazy { DashboardFeatureProviderImpl(appContext) }
override val dockUpdaterFeatureProvider: DockUpdaterFeatureProvider by lazy {
DockUpdaterFeatureProviderImpl()
diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java
index 6066efb..12709fe 100644
--- a/src/com/android/settings/password/ChooseLockPassword.java
+++ b/src/com/android/settings/password/ChooseLockPassword.java
@@ -523,6 +523,7 @@
setupPasswordRequirementsView(headerLayout);
mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
+ mPasswordRestrictionView.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
mPasswordEntry = view.findViewById(R.id.password_entry);
mPasswordEntry.setOnEditorActionListener(this);
mPasswordEntry.addTextChangedListener(this);
diff --git a/src/com/android/settings/password/ConfirmLockPassword.java b/src/com/android/settings/password/ConfirmLockPassword.java
index 8d0ff14..b203015 100644
--- a/src/com/android/settings/password/ConfirmLockPassword.java
+++ b/src/com/android/settings/password/ConfirmLockPassword.java
@@ -18,12 +18,8 @@
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PASSWORD;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PIN;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PASSWORD_REQUIRED;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PIN_REQUIRED;
import static android.app.admin.DevicePolicyResources.UNDEFINED;
import static com.android.settings.biometrics.GatekeeperPasswordProvider.containsGatekeeperPasswordHandle;
@@ -75,27 +71,12 @@
public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
- // The index of the array is isStrongAuth << 2 + isManagedProfile << 1 + isAlpha.
+ // The index of the array is isStrongAuth << 1 + isAlpha.
private static final int[] DETAIL_TEXTS = new int[] {
R.string.lockpassword_confirm_your_pin_generic,
R.string.lockpassword_confirm_your_password_generic,
- R.string.lockpassword_confirm_your_pin_generic_profile,
- R.string.lockpassword_confirm_your_password_generic_profile,
R.string.lockpassword_strong_auth_required_device_pin,
R.string.lockpassword_strong_auth_required_device_password,
- R.string.lockpassword_strong_auth_required_work_pin,
- R.string.lockpassword_strong_auth_required_work_password
- };
-
- private static final String[] DETAIL_TEXT_OVERRIDES = new String[] {
- UNDEFINED,
- UNDEFINED,
- WORK_PROFILE_CONFIRM_PIN,
- WORK_PROFILE_CONFIRM_PASSWORD,
- UNDEFINED,
- UNDEFINED,
- WORK_PROFILE_PIN_REQUIRED,
- WORK_PROFILE_PASSWORD_REQUIRED
};
public static class InternalActivity extends ConfirmLockPassword {
@@ -200,7 +181,12 @@
detailsMessage = getDefaultDetails();
}
mGlifLayout.setHeaderText(headerMessage);
- mGlifLayout.setDescriptionText(detailsMessage);
+
+ if (mIsManagedProfile) {
+ mGlifLayout.getDescriptionTextView().setVisibility(View.GONE);
+ } else {
+ mGlifLayout.setDescriptionText(detailsMessage);
+ }
mCheckBoxLabel = intent.getCharSequenceExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL);
}
int currentType = mPasswordEntry.getInputType();
@@ -323,11 +309,9 @@
: R.string.lockpassword_remote_validation_pin_details);
}
boolean isStrongAuthRequired = isStrongAuthRequired();
- // Map boolean flags to an index by isStrongAuth << 2 + isManagedProfile << 1 + isAlpha.
- int index = ((isStrongAuthRequired ? 1 : 0) << 2) + ((mIsManagedProfile ? 1 : 0) << 1)
- + (mIsAlpha ? 1 : 0);
- return mDevicePolicyManager.getResources().getString(
- DETAIL_TEXT_OVERRIDES[index], () -> getString(DETAIL_TEXTS[index]));
+ // Map boolean flags to an index by isStrongAuth << 1 + isAlpha.
+ int index = ((isStrongAuthRequired ? 1 : 0) << 1) + (mIsAlpha ? 1 : 0);
+ return getString(DETAIL_TEXTS[index]);
}
private String getDefaultCheckboxLabel() {
diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java
index ffd7c64..7160d64 100644
--- a/src/com/android/settings/password/ConfirmLockPattern.java
+++ b/src/com/android/settings/password/ConfirmLockPattern.java
@@ -316,12 +316,9 @@
R.string.lockpassword_remote_validation_pattern_details);
}
final boolean isStrongAuthRequired = isStrongAuthRequired();
- if (!mIsManagedProfile) {
- return isStrongAuthRequired
- ? getString(R.string.lockpassword_strong_auth_required_device_pattern)
- : getString(R.string.lockpassword_confirm_your_pattern_generic);
- }
- return null;
+ return isStrongAuthRequired
+ ? getString(R.string.lockpassword_strong_auth_required_device_pattern)
+ : getString(R.string.lockpassword_confirm_your_pattern_generic);
}
private Object[][] getActiveViews() {
@@ -371,7 +368,10 @@
CharSequence detailsText =
mDetailsText == null ? getDefaultDetails() : mDetailsText;
- if (detailsText != null) {
+
+ if (mIsManagedProfile) {
+ mGlifLayout.getDescriptionTextView().setVisibility(View.GONE);
+ } else {
mGlifLayout.setDescriptionText(detailsText);
}
diff --git a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
index d6635a1..3b10c09 100644
--- a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
+++ b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
@@ -173,7 +173,7 @@
public Cursor queryDynamicRawData(String[] projection) {
final Context context = getContext();
final List<SearchIndexableRaw> rawList = new ArrayList<>();
- final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context)
+ final Collection<SearchIndexableData> bundles = FeatureFactory.getFeatureFactory()
.getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
for (SearchIndexableData bundle : bundles) {
@@ -200,8 +200,8 @@
final MatrixCursor cursor = new MatrixCursor(SITE_MAP_COLUMNS);
final Context context = getContext();
// Loop through all IA categories and pages and build additional SiteMapPairs
- final List<DashboardCategory> categories = FeatureFactory.getFactory(context)
- .getDashboardFeatureProvider(context).getAllCategories();
+ final List<DashboardCategory> categories = FeatureFactory.getFeatureFactory()
+ .getDashboardFeatureProvider().getAllCategories();
for (DashboardCategory category : categories) {
// Use the category key to look up parent (which page hosts this key)
final String parentClass = CATEGORY_KEY_TO_PARENT_MAP.get(category.key);
@@ -271,7 +271,7 @@
}
private List<String> getNonIndexableKeysFromProvider(Context context) {
- final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context)
+ final Collection<SearchIndexableData> bundles = FeatureFactory.getFeatureFactory()
.getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
final List<String> nonIndexableKeys = new ArrayList<>();
@@ -322,7 +322,7 @@
}
private List<SearchIndexableResource> getSearchIndexableResourcesFromProvider(Context context) {
- final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context)
+ final Collection<SearchIndexableData> bundles = FeatureFactory.getFeatureFactory()
.getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
List<SearchIndexableResource> resourceList = new ArrayList<>();
@@ -348,7 +348,7 @@
}
private List<SearchIndexableRaw> getSearchIndexableRawFromProvider(Context context) {
- final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context)
+ final Collection<SearchIndexableData> bundles = FeatureFactory.getFeatureFactory()
.getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
final List<SearchIndexableRaw> rawList = new ArrayList<>();
@@ -392,7 +392,7 @@
@VisibleForTesting
List<SearchIndexableRaw> getInjectionIndexableRawData(Context context) {
final DashboardFeatureProvider dashboardFeatureProvider =
- FeatureFactory.getFactory(context).getDashboardFeatureProvider(context);
+ FeatureFactory.getFeatureFactory().getDashboardFeatureProvider();
final List<SearchIndexableRaw> rawList = new ArrayList<>();
final String currentPackageName = context.getPackageName();
for (DashboardCategory category : dashboardFeatureProvider.getAllCategories()) {
diff --git a/src/com/android/settings/sim/SimDialogActivity.java b/src/com/android/settings/sim/SimDialogActivity.java
index 7d39938..e7b0185 100644
--- a/src/com/android/settings/sim/SimDialogActivity.java
+++ b/src/com/android/settings/sim/SimDialogActivity.java
@@ -280,8 +280,20 @@
public void showEnableAutoDataSwitchDialog() {
final FragmentManager fragmentManager = getSupportFragmentManager();
SimDialogFragment fragment = createFragment(ENABLE_AUTO_DATA_SWITCH);
- fragment.show(fragmentManager, Integer.toString(ENABLE_AUTO_DATA_SWITCH));
+ if (fragmentManager.isStateSaved()) {
+ Log.w(TAG, "Failed to show EnableAutoDataSwitchDialog. The fragmentManager "
+ + "is StateSaved.");
+ forceClose();
+ return;
+ }
+ try {
+ fragment.show(fragmentManager, Integer.toString(ENABLE_AUTO_DATA_SWITCH));
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to show EnableAutoDataSwitchDialog.", e);
+ forceClose();
+ return;
+ }
if (getResources().getBoolean(
R.bool.config_auto_data_switch_enables_cross_sim_calling)) {
// If auto data switch is already enabled on the non-DDS, the dialog for enabling it
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index 7b450c9..f08a2de 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -21,6 +21,7 @@
import com.android.settings.spa.about.AboutPhonePageProvider
import com.android.settings.spa.app.AllAppListPageProvider
import com.android.settings.spa.app.AppsMainPageProvider
+import com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
import com.android.settings.spa.app.appinfo.CloneAppInfoSettingsProvider
import com.android.settings.spa.app.backgroundinstall.BackgroundInstalledAppsPageProvider
@@ -35,6 +36,7 @@
import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider
+import com.android.settings.spa.app.storage.StorageAppListPageProvider
import com.android.settings.spa.core.instrumentation.SpaLogProvider
import com.android.settings.spa.development.UsageStatsPageProvider
import com.android.settings.spa.development.compat.PlatformCompatAppListPageProvider
@@ -87,9 +89,12 @@
UsageStatsPageProvider,
PlatformCompatAppListPageProvider,
BackgroundInstalledAppsPageProvider,
+ UserAspectRatioAppsPageProvider,
CloneAppInfoSettingsProvider,
NetworkAndInternetPageProvider,
AboutPhonePageProvider,
+ StorageAppListPageProvider.Apps,
+ StorageAppListPageProvider.Games,
) + togglePermissionAppListTemplate.createPageProviders(),
rootPages = listOf(
HomePageProvider.createSettingsPage()
diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt
new file mode 100644
index 0000000..3680715
--- /dev/null
+++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 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.appcompat
+
+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.appcompat.UserAspectRatioDetails
+import com.android.settings.applications.appcompat.UserAspectRatioManager
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
+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 UserAspectRatioAppPreference(app: ApplicationInfo) {
+ val context = LocalContext.current
+ val presenter = remember { UserAspectRatioAppPresenter(context, app) }
+ if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.aspect_ratio_title)
+ override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder),
+ )
+ override val onClick = presenter::startActivity
+ })
+}
+
+class UserAspectRatioAppPresenter(
+ private val context: Context,
+ private val app: ApplicationInfo,
+) {
+ private val manager = UserAspectRatioManager(context)
+
+ val isAvailableFlow = flow {
+ emit(UserAspectRatioManager.isFeatureEnabled(context)
+ && manager.canDisplayAspectRatioUi(app))
+ }.flowOn(Dispatchers.IO)
+
+ fun startActivity() =
+ navigateToAppAspectRatioSettings(context, app)
+
+ val summaryFlow = flow {
+ emit(manager.getUserMinAspectRatioEntry(app.packageName, context.userId))
+ }.flowOn(Dispatchers.IO)
+}
+
+fun navigateToAppAspectRatioSettings(context: Context, app: ApplicationInfo) {
+ AppInfoDashboardFragment.startAppInfoFragment(
+ UserAspectRatioDetails::class.java,
+ app,
+ context,
+ AppInfoSettingsProvider.METRICS_CATEGORY,
+ )
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt
new file mode 100644
index 0000000..ff90492
--- /dev/null
+++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2023 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.appcompat
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.GET_ACTIVITIES
+import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+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.appcompat.UserAspectRatioManager
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.util.asyncMap
+import com.android.settingslib.spa.framework.util.filterItem
+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.spa.widget.ui.SpinnerOption
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.userId
+import com.android.settingslib.spaprivileged.template.app.AppList
+import com.android.settingslib.spaprivileged.template.app.AppListInput
+import com.android.settingslib.spaprivileged.template.app.AppListItem
+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.flow
+import kotlinx.coroutines.flow.flowOn
+
+object UserAspectRatioAppsPageProvider : SettingsPageProvider {
+ override val name = "UserAspectRatioAppsPage"
+ private val owner = createSettingsPage()
+
+ override fun isEnabled(arguments: Bundle?): Boolean =
+ UserAspectRatioManager.isFeatureEnabled(SpaEnvironmentFactory.instance.appContext)
+
+ @Composable
+ override fun Page(arguments: Bundle?) =
+ UserAspectRatioAppList()
+
+ @Composable
+ @VisibleForTesting
+ fun EntryItem() =
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.aspect_ratio_title)
+ override val summary = getSummary().toState()
+ override val onClick = navigator(name)
+ })
+
+ @VisibleForTesting
+ fun buildInjectEntry() = SettingsEntryBuilder
+ .createInject(owner)
+ .setSearchDataFn { null }
+ .setUiLayoutFn { EntryItem() }
+
+ @Composable
+ @VisibleForTesting
+ fun getSummary(): String = stringResource(R.string.aspect_ratio_summary, Build.MODEL)
+}
+
+@Composable
+fun UserAspectRatioAppList(
+ appList: @Composable AppListInput<UserAspectRatioAppListItemModel>.() -> Unit
+ = { AppList() },
+) {
+ AppListPage(
+ title = stringResource(R.string.aspect_ratio_title),
+ listModel = rememberContext(::UserAspectRatioAppListModel),
+ appList = appList,
+ header = {
+ Box(Modifier.padding(SettingsDimension.itemPadding)) {
+ SettingsBody(UserAspectRatioAppsPageProvider.getSummary())
+ }
+ }
+ )
+}
+
+data class UserAspectRatioAppListItemModel(
+ override val app: ApplicationInfo,
+ val override: Int,
+ val suggested: Boolean,
+ val canDisplay: Boolean,
+) : AppRecord
+
+class UserAspectRatioAppListModel(private val context: Context)
+ : AppListModel<UserAspectRatioAppListItemModel> {
+
+ private val packageManager = context.packageManager
+ private val userAspectRatioManager = UserAspectRatioManager(context)
+
+ override fun getSpinnerOptions(
+ recordList: List<UserAspectRatioAppListItemModel>
+ ): List<SpinnerOption> {
+ val hasSuggested = recordList.any { it.suggested }
+ val hasOverride = recordList.any { it.override != USER_MIN_ASPECT_RATIO_UNSET }
+ val options = mutableListOf(SpinnerItem.All)
+ // Add suggested filter first as default
+ if (hasSuggested) options.add(0, SpinnerItem.Suggested)
+ if (hasOverride) options += SpinnerItem.Overridden
+ return options.map {
+ SpinnerOption(
+ id = it.ordinal,
+ text = context.getString(it.stringResId),
+ )
+ }
+ }
+
+ @Composable
+ override fun AppListItemModel<UserAspectRatioAppListItemModel>.AppItem() {
+ val app = record.app
+ AppListItem(
+ onClick = { navigateToAppAspectRatioSettings(context, app) }
+ )
+ }
+
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ userIdFlow.combine(appListFlow) { uid, appList ->
+ appList.asyncMap { app ->
+ UserAspectRatioAppListItemModel(
+ app = app,
+ suggested = !app.isSystemApp && getPackageAndActivityInfo(
+ app)?.isFixedOrientationOrAspectRatio() == true,
+ override = userAspectRatioManager.getUserMinAspectRatioValue(
+ app.packageName, uid),
+ canDisplay = userAspectRatioManager.canDisplayAspectRatioUi(app),
+ )
+ }
+ }
+
+ override fun filter(
+ userIdFlow: Flow<Int>,
+ option: Int,
+ recordListFlow: Flow<List<UserAspectRatioAppListItemModel>>
+ ): Flow<List<UserAspectRatioAppListItemModel>> = recordListFlow.filterItem(
+ when (SpinnerItem.values().getOrNull(option)) {
+ SpinnerItem.Suggested -> ({ it.canDisplay && it.suggested })
+ SpinnerItem.Overridden -> ({ it.override != USER_MIN_ASPECT_RATIO_UNSET })
+ else -> ({ it.canDisplay })
+ }
+ )
+
+ @OptIn(ExperimentalLifecycleComposeApi::class)
+ @Composable
+ override fun getSummary(option: Int, record: UserAspectRatioAppListItemModel) : State<String> =
+ remember(record.override) {
+ flow {
+ emit(userAspectRatioManager.getUserMinAspectRatioEntry(record.override))
+ }.flowOn(Dispatchers.IO)
+ }.collectAsStateWithLifecycle(initialValue = stringResource(R.string.summary_placeholder))
+
+ private fun getPackageAndActivityInfo(app: ApplicationInfo): PackageInfo? = try {
+ packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId)
+ } catch (e: Exception) {
+ // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause
+ // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture
+ // packages, we need to catch the exception to alleviate the impact before PackageManager
+ // fixing this issue or provide a better api.
+ Log.e(TAG, "Exception while getPackageInfoAsUser", e)
+ null
+ }
+
+ companion object {
+ private const val TAG = "AspectRatioAppsListModel"
+ private fun PackageInfo.isFixedOrientationOrAspectRatio() =
+ activities?.any { a -> a.isFixedOrientation || a.hasFixedAspectRatio() } ?: false
+ private val GET_ACTIVITIES_FLAGS =
+ PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong())
+ }
+}
+
+private enum class SpinnerItem(val stringResId: Int) {
+ Suggested(R.string.user_aspect_ratio_suggested_apps_label),
+ All(R.string.filter_all_apps),
+ Overridden(R.string.user_aspect_ratio_overridden_apps_label)
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index d59a4f7..e6df933 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -35,6 +35,7 @@
import com.android.settings.applications.AppInfoBase
import com.android.settings.applications.appinfo.AppInfoDashboardFragment
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
+import com.android.settings.spa.app.appcompat.UserAspectRatioAppPreference
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
@@ -150,6 +151,7 @@
}
Category(title = stringResource(R.string.advanced_apps)) {
+ UserAspectRatioAppPreference(app)
DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
ModifySystemSettingsAppListProvider.InfoPageEntryItem(app)
PictureInPictureListProvider.InfoPageEntryItem(app)
diff --git a/src/com/android/settings/spa/app/storage/StorageAppList.kt b/src/com/android/settings/spa/app/storage/StorageAppList.kt
new file mode 100644
index 0000000..8fc3eb5
--- /dev/null
+++ b/src/com/android/settings/spa/app/storage/StorageAppList.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 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.storage
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.os.Bundle
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.remember
+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.SettingsPageProvider
+import com.android.settingslib.spa.framework.util.filterItem
+import com.android.settingslib.spa.framework.util.mapItem
+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.AppListInput
+import com.android.settingslib.spaprivileged.template.app.AppListItem
+import com.android.settingslib.spaprivileged.template.app.AppListItemModel
+import com.android.settingslib.spaprivileged.template.app.AppListPage
+import com.android.settingslib.spaprivileged.template.app.calculateSizeBytes
+import com.android.settingslib.spaprivileged.template.app.getStorageSize
+import kotlinx.coroutines.flow.Flow
+
+sealed class StorageAppListPageProvider(private val type: StorageType) : SettingsPageProvider {
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ StorageAppListPage(type)
+ }
+
+ object Apps : StorageAppListPageProvider(StorageType.Apps) {
+ override val name = "StorageAppList"
+ }
+
+ object Games : StorageAppListPageProvider(StorageType.Games) {
+ override val name = "GameStorageAppList"
+ }
+}
+
+sealed class StorageType(
+ @StringRes val titleResource: Int,
+ val filter: (AppRecordWithSize) -> Boolean
+) {
+ object Apps : StorageType(
+ titleResource = R.string.apps_storage,
+ filter = {
+ (it.app.flags and ApplicationInfo.FLAG_IS_GAME) == 0 &&
+ it.app.category != ApplicationInfo.CATEGORY_GAME
+ }
+ )
+ object Games : StorageType(
+ titleResource = R.string.game_storage_settings,
+ filter = {
+ (it.app.flags and ApplicationInfo.FLAG_IS_GAME) != 0 ||
+ it.app.category == ApplicationInfo.CATEGORY_GAME
+ }
+ )
+}
+
+@Composable
+fun StorageAppListPage(
+ type: StorageType,
+ appList: @Composable AppListInput<AppRecordWithSize>.() -> Unit = { AppList() }
+) {
+ val context = LocalContext.current
+ AppListPage(
+ title = stringResource(type.titleResource),
+ listModel = when (type) {
+ StorageType.Apps -> remember(context) { StorageAppListModel(context, type) }
+ StorageType.Games -> remember(context) { StorageAppListModel(context, type) }
+ },
+ showInstantApps = true,
+ matchAnyUserForAdmin = true,
+ appList = appList,
+ moreOptions = { }, // TODO(b/292165031) Sorting in Options not yet supported
+ )
+}
+
+data class AppRecordWithSize(
+ override val app: ApplicationInfo,
+ val size: Long
+) : AppRecord
+
+class StorageAppListModel(
+ private val context: Context,
+ private val type: StorageType,
+ private val getStorageSummary: @Composable ApplicationInfo.() -> State<String> = {
+ getStorageSize()
+ }
+) : AppListModel<AppRecordWithSize> {
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ appListFlow.mapItem {
+ AppRecordWithSize(it, it.calculateSizeBytes(context) ?: 0L)
+ }
+
+ override fun filter(
+ userIdFlow: Flow<Int>,
+ option: Int,
+ recordListFlow: Flow<List<AppRecordWithSize>>
+ ): Flow<List<AppRecordWithSize>> = recordListFlow.filterItem { type.filter(it) }
+
+ @Composable
+ override fun getSummary(option: Int, record: AppRecordWithSize): State<String> {
+ val storageSummary = record.app.getStorageSummary()
+ return remember {
+ derivedStateOf {
+ storageSummary.value
+ }
+ }
+ }
+
+ @Composable
+ override fun AppListItemModel<AppRecordWithSize>.AppItem() {
+ AppListItem(onClick = AppInfoSettingsProvider.navigator(app = record.app))
+ }
+
+ override fun getComparator(option: Int) = compareByDescending<AppEntry<AppRecordWithSize>> {
+ it.record.size
+ }.then(super.getComparator(option))
+}
diff --git a/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt b/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt
index 600a2e6..8659bf5 100644
--- a/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt
+++ b/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt
@@ -72,10 +72,10 @@
}
val sessionSource = extraData.getString(LOG_DATA_SESSION_NAME)
return when(sessionSource) {
- SESSION_BROWSE -> SettingsEnums.BROWSE
- SESSION_SEARCH -> SettingsEnums.SEARCH
- SESSION_SLICE -> SettingsEnums.SLICE_TYPE
- SESSION_EXTERNAL -> SettingsEnums.EXTERNAL
+ SESSION_BROWSE -> SettingsEnums.SESSION_BROWSE
+ SESSION_SEARCH -> SettingsEnums.SESSION_SEARCH
+ SESSION_SLICE -> SettingsEnums.SESSION_SLICE_TYPE
+ SESSION_EXTERNAL -> SettingsEnums.SESSION_EXTERNAL
else -> SettingsEnums.SESSION_UNKNOWN
}
}
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index 93b7c78..b0816fd 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -1628,7 +1628,7 @@
mRemovingUserId = -1;
updateUserList();
if (mCreateUserDialogController.isActive()) {
- mCreateUserDialogController.clear();
+ mCreateUserDialogController.finish();
}
}
}
diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java
index 8cec2f4..0389ecd 100644
--- a/src/com/android/settings/vpn2/VpnSettings.java
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -141,6 +141,11 @@
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
+
+ if (!getContext().getResources().getBoolean(R.bool.config_show_vpn_options)) {
+ return;
+ }
+
// Although FEATURE_IPSEC_TUNNELS should always be present in android S and beyond,
// keep this check here just to be safe.
if (!getContext().getPackageManager().hasSystemFeature(
diff --git a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java
index 5639047..93d88e9 100644
--- a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java
+++ b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java
@@ -18,8 +18,6 @@
import static com.android.wifitrackerlib.Utils.getSecurityTypesFromScanResult;
-import static java.util.stream.Collectors.toList;
-
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -273,19 +271,31 @@
@VisibleForTesting
void updateWifiEntries() {
final List<WifiEntry> wifiEntries = new ArrayList<>();
- if (mWifiPickerTracker.getConnectedWifiEntry() != null) {
- wifiEntries.add(mWifiPickerTracker.getConnectedWifiEntry());
+ WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
+ String connectedSsid;
+ if (connectedWifiEntry != null) {
+ connectedSsid = connectedWifiEntry.getSsid();
+ wifiEntries.add(connectedWifiEntry);
+ } else {
+ connectedSsid = null;
}
wifiEntries.addAll(mWifiPickerTracker.getWifiEntries());
mFilteredWifiEntries.clear();
mFilteredWifiEntries.addAll(wifiEntries.stream()
- .filter(entry -> isMatchedWifiEntry(entry))
+ .filter(entry -> isMatchedWifiEntry(entry, connectedSsid))
.limit(mShowLimitedItem ? MAX_NUMBER_LIST_ITEM : Long.MAX_VALUE)
- .collect(toList()));
+ .toList());
}
- private boolean isMatchedWifiEntry(WifiEntry entry) {
+ private boolean isMatchedWifiEntry(WifiEntry entry, String connectedSsid) {
+ if (entry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED
+ && TextUtils.equals(entry.getSsid(), connectedSsid)) {
+ // WifiPickerTracker may return a duplicate unsaved network that is separate from
+ // the connecting app-requested network, so make sure we only show the connected
+ // app-requested one.
+ return false;
+ }
for (MatchWifi wifi : mMatchWifis) {
if (!TextUtils.equals(entry.getSsid(), wifi.mSsid)) {
continue;
diff --git a/src/com/android/settings/wifi/WifiEntryPreference.java b/src/com/android/settings/wifi/WifiEntryPreference.java
index 5b44887..7206666 100644
--- a/src/com/android/settings/wifi/WifiEntryPreference.java
+++ b/src/com/android/settings/wifi/WifiEntryPreference.java
@@ -15,6 +15,8 @@
*/
package com.android.settings.wifi;
+import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
+
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
@@ -37,6 +39,7 @@
import com.android.settingslib.Utils;
import com.android.settingslib.wifi.WifiUtils;
import com.android.wifitrackerlib.BaseWifiTracker;
+import com.android.wifitrackerlib.HotspotNetworkEntry;
import com.android.wifitrackerlib.WifiEntry;
/**
@@ -145,13 +148,17 @@
*/
public void refresh() {
setTitle(mWifiEntry.getTitle());
- final int level = mWifiEntry.getLevel();
- final boolean showX = mWifiEntry.shouldShowXLevelIcon();
- if (level != mLevel || showX != mShowX) {
- mLevel = level;
- mShowX = showX;
- updateIcon(mShowX, mLevel);
- notifyChanged();
+ if (mWifiEntry instanceof HotspotNetworkEntry) {
+ updateHotspotIcon(((HotspotNetworkEntry) mWifiEntry).getDeviceType());
+ } else {
+ int level = mWifiEntry.getLevel();
+ boolean showX = mWifiEntry.shouldShowXLevelIcon();
+
+ if (level != mLevel || showX != mShowX) {
+ mLevel = level;
+ mShowX = showX;
+ updateIcon(mShowX, mLevel);
+ }
}
setSummary(mWifiEntry.getSummary(false /* concise */));
@@ -201,14 +208,7 @@
return accent ? android.R.attr.colorAccent : android.R.attr.colorControlNormal;
}
- @VisibleForTesting
- void updateIcon(boolean showX, int level) {
- if (level == -1) {
- setIcon(null);
- return;
- }
-
- final Drawable drawable = mIconInjector.getIcon(showX, level);
+ private void setIconWithTint(Drawable drawable) {
if (drawable != null) {
// Must use Drawable#setTintList() instead of Drawable#setTint() to show the grey
// icon when the preference is disabled.
@@ -219,6 +219,20 @@
}
}
+ @VisibleForTesting
+ void updateIcon(boolean showX, int level) {
+ if (level == -1) {
+ setIcon(null);
+ return;
+ }
+ setIconWithTint(mIconInjector.getIcon(showX, level));
+ }
+
+ @VisibleForTesting
+ void updateHotspotIcon(int deviceType) {
+ setIconWithTint(getContext().getDrawable(getHotspotIconResource(deviceType)));
+ }
+
@Nullable
private StateListDrawable getFrictionStateListDrawable() {
TypedArray frictionSld;
diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java
index d3a4be7..4fc036b 100644
--- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java
+++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java
@@ -27,6 +27,7 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.provider.Settings;
+import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@@ -118,6 +119,8 @@
final String password = wifiNetworkConfig.getPreSharedKey();
TextView passwordView = view.findViewById(R.id.password);
+ passwordView.setInputType(
+ InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
if (TextUtils.isEmpty(password)) {
mSummary.setText(getString(
R.string.wifi_dpp_scan_open_network_qr_code_with_another_device,
diff --git a/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java
new file mode 100644
index 0000000..31ff76c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 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.appcompat;
+
+import static com.android.settings.applications.appcompat.UserAspectRatioDetails.KEY_PREF_3_2;
+import static com.android.settings.applications.appcompat.UserAspectRatioDetails.KEY_PREF_DEFAULT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.shadow.ShadowActivityManager;
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+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.annotation.Config;
+
+/**
+ * To run test: atest SettingsRoboTests:UserAspectRatioDetailsTest
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowActivityManager.class})
+public class UserAspectRatioDetailsTest {
+
+ @Mock
+ private UserAspectRatioManager mUserAspectRatioManager;
+ @Mock
+ private IActivityManager mAm;
+
+ private SelectorWithWidgetPreference mRadioButtonPref;
+ private Context mContext;
+ private UserAspectRatioDetails mFragment;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ mFragment = spy(new UserAspectRatioDetails());
+ when(mFragment.getContext()).thenReturn(mContext);
+ when(mFragment.getAspectRatioManager()).thenReturn(mUserAspectRatioManager);
+ ShadowActivityManager.setService(mAm);
+ mRadioButtonPref = new SelectorWithWidgetPreference(mContext);
+ }
+
+ @Test
+ public void onRadioButtonClicked_prefChange_shouldStopActivity() throws RemoteException {
+ // Default was already selected
+ mRadioButtonPref.setKey(KEY_PREF_DEFAULT);
+ mFragment.onRadioButtonClicked(mRadioButtonPref);
+ // Preference changed
+ mRadioButtonPref.setKey(KEY_PREF_3_2);
+ mFragment.onRadioButtonClicked(mRadioButtonPref);
+ // Only triggered once when preference change
+ verify(mAm).stopAppForUser(any(), anyInt());
+ }
+
+ @Test
+ public void onRadioButtonClicked_prefChange_shouldSetAspectRatio() throws RemoteException {
+ // Default was already selected
+ mRadioButtonPref.setKey(KEY_PREF_DEFAULT);
+ mFragment.onRadioButtonClicked(mRadioButtonPref);
+ // Preference changed
+ mRadioButtonPref.setKey(KEY_PREF_3_2);
+ mFragment.onRadioButtonClicked(mRadioButtonPref);
+ // Only triggered once when preference changes
+ verify(mUserAspectRatioManager).setUserMinAspectRatio(
+ any(), anyInt(), anyInt());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsActivityTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsActivityTest.java
deleted file mode 100644
index 2a95f4c..0000000
--- a/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsActivityTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.settings.biometrics.face;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.spy;
-
-import android.content.res.Resources;
-import android.os.Bundle;
-
-import com.android.settings.Settings;
-import com.android.settings.testutils.FakeFeatureFactory;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.android.controller.ActivityController;
-
-@RunWith(RobolectricTestRunner.class)
-public class FaceSettingsActivityTest {
-
- private static final String APPLIED_SETUP_WIZARD_THEME = "SettingsPreferenceTheme.SetupWizard";
-
- private Settings.FaceSettingsActivity mActivity;
- private Resources.Theme mTheme;
-
- @Before
- public void setUp() {
- FakeFeatureFactory.setupForTest();
- mActivity = spy(Settings.FaceSettingsActivity.class);
- }
-
- @Test
- public void verifyFaceSettingsActivity_shouldAppliedSetupWizardTheme() {
- createActivity();
-
- assertThat(isThemeApplied(APPLIED_SETUP_WIZARD_THEME)).isTrue();
- }
-
- private boolean isThemeApplied(String themeName) {
- final String [] appliedThemes = mTheme.getTheme();
- for (String theme : appliedThemes) {
- if (theme.contains(themeName)) {
- return true;
- }
- }
- return false;
- }
-
- private void createActivity() {
- ActivityController.of(mActivity).create(new Bundle());
- mTheme = mActivity.getTheme();
- }
-}
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java
index 84826e1..006c699 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java
@@ -137,6 +137,23 @@
}
@Test
+ public void fingerprintUdfpsEnrollInitStage_afterOnEnrollmentHelp_shouldVibrate() {
+ initializeActivityFor(TYPE_UDFPS_OPTICAL);
+
+ assertThat(getLayout().getDescriptionText()).isNotEqualTo("");
+
+ mActivity.configureEnrollmentStage(0 /* lottie */);
+ mActivity.onEnrollmentHelp(1/* FINGERPRINT_ACQUIRED_PARTIAL */, mContext.getString(
+ com.android.internal.R.string.fingerprint_acquired_partial));
+
+ verify(mVibrator, never()).vibrate(anyInt(), anyString(), any(), anyString(), any());
+
+ mActivity.onEnrollmentProgressChange(1, 1);
+ verify(mVibrator).vibrate(anyInt(), anyString(), any(), anyString(), any());
+
+ }
+
+ @Test
public void fingerprintUdfpsOverlayEnrollment_gainFocus_shouldNotCancel() {
initializeActivityFor(TYPE_UDFPS_OPTICAL);
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java
index 69f10d6..3eba91c 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java
@@ -85,6 +85,7 @@
private Context mContext;
private TestFingerprintEnrollIntroduction mFingerprintEnrollIntroduction;
+ private ActivityController<TestFingerprintEnrollIntroduction> mController;
private static final int MAX_ENROLLMENTS = 5;
private static final byte[] EXPECTED_TOKEN = new byte[] { 10, 20, 30, 40 };
@@ -121,9 +122,8 @@
void setupFingerprintEnrollIntroWith(@NonNull Intent intent) {
- final ActivityController<TestFingerprintEnrollIntroduction> controller =
- Robolectric.buildActivity(TestFingerprintEnrollIntroduction.class, intent);
- mFingerprintEnrollIntroduction = controller.get();
+ mController = Robolectric.buildActivity(TestFingerprintEnrollIntroduction.class, intent);
+ mFingerprintEnrollIntroduction = mController.get();
mFingerprintEnrollIntroduction.mMockedFingerprintManager = mFingerprintManager;
mFingerprintEnrollIntroduction.mMockedGatekeeperPasswordProvider =
mGatekeeperPasswordProvider;
@@ -137,7 +137,7 @@
when(mLockPatternUtils.getActivePasswordQuality(userId))
.thenReturn(PASSWORD_QUALITY_SOMETHING);
- controller.create();
+ mController.create();
}
void setFingerprintManagerToHave(int numEnrollments) {
@@ -277,6 +277,18 @@
}
}
+ @Test
+ public void clickNext_onActivityResult_pause_shouldFinish() {
+ setupFingerprintEnrollIntroWith(newTokenOnlyIntent());
+ mController.resume();
+ mFingerprintEnrollIntroduction.clickNextBtn();
+ mController.pause().stop();
+ assertThat(mFingerprintEnrollIntroduction.shouldFinishWhenBackgrounded()).isEqualTo(false);
+
+ mController.resume().pause().stop();
+ assertThat(mFingerprintEnrollIntroduction.shouldFinishWhenBackgrounded()).isEqualTo(true);
+ }
+
private Intent newTokenOnlyIntent() {
return new Intent()
.putExtra(EXTRA_KEY_CHALLENGE_TOKEN, new byte[] { 1 });
@@ -362,5 +374,16 @@
protected void getChallenge(GenerateChallengeCallback callback) {
callback.onChallengeGenerated(mNewSensorId, mUserId, mNewChallenge);
}
+
+ @Override
+ protected boolean shouldFinishWhenBackgrounded() {
+ return super.shouldFinishWhenBackgrounded();
+ }
+
+ //mock click next btn
+ public void clickNextBtn() {
+ super.onNextButtonClick(null);
+ }
+
}
}
diff --git a/tests/robotests/src/com/android/settings/development/GrammaticalGenderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/GrammaticalGenderPreferenceControllerTest.java
new file mode 100644
index 0000000..005e6a8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/GrammaticalGenderPreferenceControllerTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.development;
+
+
+import static com.android.settings.development.GrammaticalGenderPreferenceController.GRAMMATICAL_GENDER_PROPERTY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+
+import androidx.preference.ListPreference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+
+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 GrammaticalGenderPreferenceControllerTest {
+ @Mock
+ private ListPreference mPreference;
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+ @Mock
+ private IActivityManager mActivityManager;
+ private Configuration mConfiguration;
+ private Context mContext;
+ private GrammaticalGenderPreferenceController mController;
+ private String[] mListValues;
+ private String[] mListSummaries;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ final Resources resources = mContext.getResources();
+ mListValues = resources.getStringArray(R.array.grammatical_gender_values);
+ mListSummaries = resources.getStringArray(R.array.grammatical_gender_entries);
+ mConfiguration = new Configuration();
+ mController = new GrammaticalGenderPreferenceController(mContext, mActivityManager);
+ when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
+ .thenReturn(mPreference);
+ mController.displayPreference(mPreferenceScreen);
+ doReturn(mConfiguration).when(mActivityManager).getConfiguration();
+ }
+
+ @Test
+ public void onPreferenceChange_setNeuter_shouldEnableNeuter() throws RemoteException {
+ mController.onPreferenceChange(mPreference, mListValues[1]);
+ final String currentValue = SystemProperties.get(GRAMMATICAL_GENDER_PROPERTY);
+ assertThat(currentValue).isEqualTo(mListValues[1]);
+ verify(mActivityManager).updatePersistentConfiguration(mConfiguration);
+ assertThat(mConfiguration.getGrammaticalGender())
+ .isEqualTo(Integer.parseInt(mListValues[1]));
+ }
+
+ @Test
+ public void updateState_setNeuter_shouldSetPreferenceToNeuter() {
+ SystemProperties.set(GRAMMATICAL_GENDER_PROPERTY, mListValues[1]);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setValue(mListValues[1]);
+ verify(mPreference).setSummary(mListSummaries[1]);
+ }
+
+ @Test
+ public void onPreferenceChange_setFeminine_shouldEnableFeminine() throws RemoteException {
+ mController.onPreferenceChange(mPreference, mListValues[2]);
+ final String currentValue = SystemProperties.get(GRAMMATICAL_GENDER_PROPERTY);
+ assertThat(currentValue).isEqualTo(mListValues[2]);
+ verify(mActivityManager).updatePersistentConfiguration(mConfiguration);
+ assertThat(mConfiguration.getGrammaticalGender())
+ .isEqualTo(Integer.parseInt(mListValues[2]));
+ }
+
+ @Test
+ public void updateState_setFeminine_shouldSetPreferenceToFeminine() {
+ SystemProperties.set(GRAMMATICAL_GENDER_PROPERTY, mListValues[2]);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setValue(mListValues[2]);
+ verify(mPreference).setSummary(mListSummaries[2]);
+ }
+
+ @Test
+ public void onPreferenceChange_setMasculine_shouldEnableMasculine() throws RemoteException {
+ mController.onPreferenceChange(mPreference, mListValues[3]);
+ final String currentValue = SystemProperties.get(GRAMMATICAL_GENDER_PROPERTY);
+ assertThat(currentValue).isEqualTo(mListValues[3]);
+ verify(mActivityManager).updatePersistentConfiguration(mConfiguration);
+ assertThat(mConfiguration.getGrammaticalGender())
+ .isEqualTo(Integer.parseInt(mListValues[3]));
+ }
+
+ @Test
+ public void updateState_setMasculine_shouldSetPreferenceToMasculine() {
+ SystemProperties.set(GRAMMATICAL_GENDER_PROPERTY, mListValues[3]);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setValue(mListValues[3]);
+ verify(mPreference).setSummary(mListSummaries[3]);
+ }
+
+ @Test
+ public void updateState_noValueSet_shouldSetDefaultToNotSpecified() {
+ mController.updateState(mPreference);
+
+ verify(mPreference).setValue(mListValues[0]);
+ verify(mPreference).setSummary(mListSummaries[0]);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java
index 1a43dbb..c9591a5 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java
@@ -68,6 +68,15 @@
}
@Test
+ public void testIsBatteryTipsEnabled_returnFalse() {
+ assertThat(mPowerFeatureProvider.isBatteryTipsEnabled()).isFalse();
+ }
+
+ @Test
+ public void testIsBatteryTipsFeedbackEnabled_returnTrue() {
+ assertThat(mPowerFeatureProvider.isBatteryTipsFeedbackEnabled()).isTrue();
+ }
+ @Test
public void testGetBatteryUsageListConsumePowerThreshold_return0() {
assertThat(mPowerFeatureProvider.getBatteryUsageListConsumePowerThreshold()).isEqualTo(0.0);
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java
index b444309..f6bc297 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java
@@ -31,6 +31,7 @@
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
+import android.os.BatteryManager;
import androidx.preference.Preference;
import androidx.test.core.app.ApplicationProvider;
@@ -146,6 +147,17 @@
}
@Test
+ public void getDashboardLabel_notChargingState_returnsCorrectLabel() {
+ mController.mPreference = new Preference(mContext);
+ BatteryInfo info = new BatteryInfo();
+ info.batteryStatus = BatteryManager.BATTERY_STATUS_NOT_CHARGING;
+ info.statusLabel = "expected returned label";
+
+ assertThat(mController.getDashboardLabel(mContext, info, true))
+ .isEqualTo(info.statusLabel);
+ }
+
+ @Test
public void getSummary_batteryNotPresent_shouldShowWarningMessage() {
mController.mIsBatteryPresent = false;
assertThat(mController.getSummary())
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
index a5f1ab3..9f6e4e3 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
@@ -85,7 +85,7 @@
@Test
public void getIcon_showIcon() {
assertThat(mIncompatibleChargerTip.getIconId())
- .isEqualTo(R.drawable.ic_battery_alert_theme);
+ .isEqualTo(R.drawable.ic_battery_charger);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
new file mode 100644
index 0000000..6f9a474
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryTipsCardPreferenceTest {
+
+ private Context mContext;
+ private BatteryTipsCardPreference mBatteryTipsCardPreference;
+
+ @Before
+ public void setUp() {
+ mContext = spy(RuntimeEnvironment.application);
+ mBatteryTipsCardPreference = new BatteryTipsCardPreference(mContext, /*attrs=*/ null);
+ }
+
+ @Test
+ public void constructor_returnExpectedResult() {
+ assertThat(mBatteryTipsCardPreference.getLayoutResource()).isEqualTo(
+ R.layout.battery_tips_card);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
index aa1ebd7..566df52 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
@@ -22,8 +22,10 @@
import android.app.AlarmManager;
import android.app.Application;
+import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import androidx.test.core.app.ApplicationProvider;
@@ -40,7 +42,6 @@
import org.robolectric.shadows.ShadowAlarmManager;
import java.time.Clock;
-import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -65,10 +66,12 @@
BatteryTestUtils.insertDataToBatteryStateTable(
mContext, Clock.systemUTC().millis(), "com.android.systemui");
mDao = database.batteryStateDao();
+ clearSharedPreferences();
}
@After
public void tearDown() {
+ clearSharedPreferences();
mPeriodicJobManager.reset();
}
@@ -82,8 +85,21 @@
@Test
public void onReceive_withBootCompletedIntent_refreshesJob() {
+ final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext);
+ sharedPreferences
+ .edit()
+ .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE,
+ UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY)
+ .apply();
+
mReceiver.onReceive(mContext, new Intent(Intent.ACTION_BOOT_COMPLETED));
+
assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull();
+ assertThat(
+ DatabaseUtils
+ .getSharedPreferences(mContext)
+ .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE))
+ .isFalse();
}
@Test
@@ -133,15 +149,7 @@
BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK);
}
- private void insertExpiredData(int shiftDay) {
- final long expiredTimeInMs =
- Clock.systemUTC().millis() - Duration.ofDays(shiftDay).toMillis();
- BatteryTestUtils.insertDataToBatteryStateTable(
- mContext, expiredTimeInMs - 1, "com.android.systemui");
- BatteryTestUtils.insertDataToBatteryStateTable(
- mContext, expiredTimeInMs, "com.android.systemui");
- // Ensures the testing environment is correct.
- assertThat(mDao.getAllAfter(0)).hasSize(3);
+ private void clearSharedPreferences() {
+ DatabaseUtils.getSharedPreferences(mContext).edit().clear().apply();
}
-
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
index 6b8073b..3cbe8a4 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
@@ -25,7 +25,6 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
import android.content.ContentValues;
@@ -35,7 +34,6 @@
import android.os.BatteryManager;
import android.os.BatteryUsageStats;
import android.os.LocaleList;
-import android.os.RemoteException;
import android.os.UserHandle;
import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
@@ -62,14 +60,13 @@
@Mock
private BatteryUsageStats mBatteryUsageStats;
@Mock
- private IUsageStatsManager mUsageStatsManager;
- @Mock
private BatteryEntry mMockBatteryEntry;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
+ ConvertUtils.sUsageSource = ConvertUtils.EMPTY_USAGE_SOURCE;
when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
}
@@ -302,7 +299,7 @@
final long userId = 2;
final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
- mContext, mUsageStatsManager, event, userId);
+ mContext, event, userId);
assertThat(appUsageEvent.getTimestamp()).isEqualTo(101L);
assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.ACTIVITY_RESUMED);
assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1");
@@ -322,8 +319,8 @@
when(mMockPackageManager.getPackageUidAsUser(any(), anyInt())).thenReturn(1001);
final long userId = 1;
- final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
- mContext, mUsageStatsManager, event, userId);
+ final AppUsageEvent appUsageEvent =
+ ConvertUtils.convertToAppUsageEvent(mContext, event, userId);
assertThat(appUsageEvent.getTimestamp()).isEqualTo(101L);
assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.DEVICE_SHUTDOWN);
assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1");
@@ -338,8 +335,8 @@
final Event event = new Event();
event.mPackage = null;
- final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
- mContext, mUsageStatsManager, event, /*userId=*/ 0);
+ final AppUsageEvent appUsageEvent =
+ ConvertUtils.convertToAppUsageEvent(mContext, event, /*userId=*/ 0);
assertThat(appUsageEvent).isNull();
}
@@ -354,8 +351,8 @@
.thenThrow(new PackageManager.NameNotFoundException());
final long userId = 1;
- final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
- mContext, mUsageStatsManager, event, userId);
+ final AppUsageEvent appUsageEvent =
+ ConvertUtils.convertToAppUsageEvent(mContext, event, userId);
assertThat(appUsageEvent).isNull();
}
@@ -450,51 +447,47 @@
}
@Test
- public void getEffectivePackageName_currentActivity_returnPackageName() throws RemoteException {
- when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_CURRENT_ACTIVITY);
+ public void getEffectivePackageName_currentActivity_returnPackageName() {
+ ConvertUtils.sUsageSource = USAGE_SOURCE_CURRENT_ACTIVITY;
final String packageName = "com.android.settings1";
final String taskRootPackageName = "com.android.settings2";
assertThat(ConvertUtils.getEffectivePackageName(
- mUsageStatsManager, packageName, taskRootPackageName))
+ mContext, packageName, taskRootPackageName))
.isEqualTo(packageName);
}
@Test
- public void getEffectivePackageName_usageSourceThrowException_returnPackageName()
- throws RemoteException {
- when(mUsageStatsManager.getUsageSource()).thenThrow(new RemoteException());
+ public void getEffectivePackageName_emptyUsageSource_returnPackageName() {
final String packageName = "com.android.settings1";
final String taskRootPackageName = "com.android.settings2";
assertThat(ConvertUtils.getEffectivePackageName(
- mUsageStatsManager, packageName, taskRootPackageName))
+ mContext, packageName, taskRootPackageName))
.isEqualTo(packageName);
}
@Test
- public void getEffectivePackageName_rootActivity_returnTaskRootPackageName()
- throws RemoteException {
- when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+ public void getEffectivePackageName_rootActivity_returnTaskRootPackageName() {
+ ConvertUtils.sUsageSource = USAGE_SOURCE_TASK_ROOT_ACTIVITY;
final String packageName = "com.android.settings1";
final String taskRootPackageName = "com.android.settings2";
assertThat(ConvertUtils.getEffectivePackageName(
- mUsageStatsManager, packageName, taskRootPackageName))
+ mContext, packageName, taskRootPackageName))
.isEqualTo(taskRootPackageName);
}
@Test
- public void getEffectivePackageName_nullOrEmptyTaskRoot_returnPackageName()
- throws RemoteException {
- when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+ public void getEffectivePackageName_nullOrEmptyTaskRoot_returnPackageName() {
+ ConvertUtils.sUsageSource = USAGE_SOURCE_TASK_ROOT_ACTIVITY;
final String packageName = "com.android.settings1";
assertThat(ConvertUtils.getEffectivePackageName(
- mUsageStatsManager, packageName, /*taskRootPackageName=*/ null))
+ mContext, packageName, /*taskRootPackageName=*/ null))
.isEqualTo(packageName);
assertThat(ConvertUtils.getEffectivePackageName(
- mUsageStatsManager, packageName, /*taskRootPackageName=*/ ""))
+ mContext, packageName, /*taskRootPackageName=*/ ""))
.isEqualTo(packageName);
}
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
index b610cfb..7f7fe43 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
@@ -72,7 +72,7 @@
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
- DataProcessor.sUsageStatsManager = mUsageStatsManager;
+ DatabaseUtils.sUsageStatsManager = mUsageStatsManager;
doReturn(mContext).when(mContext).getApplicationContext();
doReturn(mUserManager)
.when(mContext)
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
index e2274e2..8bed054 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
@@ -93,7 +93,7 @@
mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider;
DataProcessor.sTestSystemAppsPackageNames = Set.of();
- DataProcessor.sUsageStatsManager = mUsageStatsManager;
+ DatabaseUtils.sUsageStatsManager = mUsageStatsManager;
doReturn(mIntent).when(mContext).registerReceiver(
isA(BroadcastReceiver.class), isA(IntentFilter.class));
doReturn(100).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_SCALE), anyInt());
@@ -249,7 +249,7 @@
final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> periodMap =
DataProcessor.generateAppUsagePeriodMap(
- 14400000L, hourlyBatteryLevelsPerDay, appUsageEventList, new ArrayList<>());
+ mContext, hourlyBatteryLevelsPerDay, appUsageEventList, new ArrayList<>());
assertThat(periodMap).hasSize(3);
// Day 1
@@ -287,7 +287,8 @@
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
assertThat(DataProcessor.generateAppUsagePeriodMap(
- 0L, hourlyBatteryLevelsPerDay, new ArrayList<>(), new ArrayList<>())).isNull();
+ mContext, hourlyBatteryLevelsPerDay, new ArrayList<>(), new ArrayList<>()))
+ .isNull();
}
@Test
@@ -1644,7 +1645,7 @@
final Map<Long, Map<String, List<AppUsagePeriod>>> appUsagePeriodMap =
DataProcessor.buildAppUsagePeriodList(
- appUsageEvents, new ArrayList<>(), 0, 5);
+ mContext, appUsageEvents, new ArrayList<>(), 0, 5);
assertThat(appUsagePeriodMap).hasSize(2);
final Map<String, List<AppUsagePeriod>> userMap1 = appUsagePeriodMap.get(1L);
@@ -1668,7 +1669,7 @@
@Test
public void buildAppUsagePeriodList_emptyEventList_returnNull() {
assertThat(DataProcessor.buildAppUsagePeriodList(
- new ArrayList<>(), new ArrayList<>(), 0, 1)).isNull();
+ mContext, new ArrayList<>(), new ArrayList<>(), 0, 1)).isNull();
}
@Test
@@ -1680,7 +1681,7 @@
AppUsageEventType.DEVICE_SHUTDOWN, /*timestamp=*/ 2));
assertThat(DataProcessor.buildAppUsagePeriodList(
- appUsageEvents, new ArrayList<>(), 0, 3)).isNull();
+ mContext, appUsageEvents, new ArrayList<>(), 0, 3)).isNull();
}
@Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
index efce44e..24be769 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
@@ -16,6 +16,9 @@
package com.android.settings.fuelgauge.batteryusage;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -23,15 +26,19 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import android.app.usage.IUsageStatsManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.MatrixCursor;
import android.os.BatteryManager;
import android.os.BatteryUsageStats;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -67,6 +74,7 @@
@Mock private BatteryEntry mMockBatteryEntry2;
@Mock private BatteryEntry mMockBatteryEntry3;
@Mock private Context mMockContext;
+ @Mock private IUsageStatsManager mUsageStatsManager;
@Before
public void setUp() {
@@ -77,6 +85,7 @@
doReturn(mPackageManager).when(mMockContext).getPackageManager();
doReturn(mPackageManager).when(mContext).getPackageManager();
DatabaseUtils.getSharedPreferences(mContext).edit().clear().apply();
+ DatabaseUtils.sUsageStatsManager = mUsageStatsManager;
}
@Test
@@ -423,6 +432,71 @@
}
@Test
+ public void removeUsageSource_hasNoData() {
+ DatabaseUtils.removeUsageSource(mContext);
+ assertThat(
+ DatabaseUtils
+ .getSharedPreferences(mContext)
+ .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE))
+ .isFalse();
+ }
+
+ @Test
+ public void removeUsageSource_hasData_deleteUsageSource() {
+ final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext);
+ sharedPreferences
+ .edit()
+ .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY)
+ .apply();
+
+ DatabaseUtils.removeUsageSource(mContext);
+
+ assertThat(
+ DatabaseUtils
+ .getSharedPreferences(mContext)
+ .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE))
+ .isFalse();
+ }
+
+ @Test
+ public void getUsageSource_hasData() {
+ final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext);
+ sharedPreferences
+ .edit()
+ .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY)
+ .apply();
+
+ assertThat(DatabaseUtils.getUsageSource(mContext))
+ .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+ }
+
+ @Test
+ public void getUsageSource_notHasData_writeLoadedData() throws RemoteException {
+ when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+
+ assertThat(DatabaseUtils.getUsageSource(mContext))
+ .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+ assertThat(
+ DatabaseUtils
+ .getSharedPreferences(mContext)
+ .getInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_CURRENT_ACTIVITY))
+ .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+ }
+
+ @Test
+ public void getUsageSource_throwException_writeDefaultData() throws RemoteException {
+ when(mUsageStatsManager.getUsageSource()).thenThrow(new RemoteException());
+
+ assertThat(DatabaseUtils.getUsageSource(mContext))
+ .isEqualTo(USAGE_SOURCE_CURRENT_ACTIVITY);
+ assertThat(
+ DatabaseUtils
+ .getSharedPreferences(mContext)
+ .getInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_CURRENT_ACTIVITY))
+ .isEqualTo(USAGE_SOURCE_CURRENT_ACTIVITY);
+ }
+
+ @Test
public void recordDateTime_writeDataIntoSharedPreferences() {
final String preferenceKey = "test_preference_key";
DatabaseUtils.recordDateTime(mContext, preferenceKey);
diff --git a/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
index 73b5ab0..e0d76ce 100644
--- a/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
@@ -218,16 +218,13 @@
mMediaDeviceUpdateWorker.mLocalMediaManager = mock(LocalMediaManager.class);
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo remoteSessionInfo = mock(RoutingSessionInfo.class);
- final RoutingSessionInfo localSessionInfo = mock(RoutingSessionInfo.class);
when(remoteSessionInfo.isSystemSession()).thenReturn(false);
- when(localSessionInfo.isSystemSession()).thenReturn(true);
routingSessionInfos.add(remoteSessionInfo);
- routingSessionInfos.add(localSessionInfo);
- when(mMediaDeviceUpdateWorker.mLocalMediaManager.getActiveMediaSession()).thenReturn(
- routingSessionInfos);
+ when(mMediaDeviceUpdateWorker.mLocalMediaManager.getRemoteRoutingSessions())
+ .thenReturn(routingSessionInfos);
- assertThat(mMediaDeviceUpdateWorker.getActiveRemoteMediaDevice()).containsExactly(
- remoteSessionInfo);
+ assertThat(mMediaDeviceUpdateWorker.getActiveRemoteMediaDevices())
+ .containsExactly(remoteSessionInfo);
}
@Test
@@ -246,6 +243,7 @@
TEST_DEVICE_PACKAGE_NAME1);
mMediaDeviceUpdateWorker = new MediaDeviceUpdateWorker(mContext, URI2);
+ mMediaDeviceUpdateWorker.mManager = mock(MediaRouter2Manager.class);
mMediaDeviceUpdateWorker.mLocalMediaManager = mock(LocalMediaManager.class);
when(mMediaDeviceUpdateWorker.mLocalMediaManager.getPackageName())
.thenReturn(TEST_DEVICE_PACKAGE_NAME2);
diff --git a/tests/robotests/src/com/android/settings/media/RemoteMediaSliceTest.java b/tests/robotests/src/com/android/settings/media/RemoteMediaSliceTest.java
index d995793..5188ad4 100644
--- a/tests/robotests/src/com/android/settings/media/RemoteMediaSliceTest.java
+++ b/tests/robotests/src/com/android/settings/media/RemoteMediaSliceTest.java
@@ -100,8 +100,8 @@
when(remoteSessionInfo.getVolume()).thenReturn(10);
when(remoteSessionInfo.isSystemSession()).thenReturn(false);
mRoutingSessionInfos.add(remoteSessionInfo);
- when(sMediaDeviceUpdateWorker.getActiveRemoteMediaDevice()).thenReturn(
- mRoutingSessionInfos);
+ when(sMediaDeviceUpdateWorker.getActiveRemoteMediaDevices())
+ .thenReturn(mRoutingSessionInfos);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java b/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java
index e713963..1e42e18 100644
--- a/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java
@@ -31,6 +31,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageStats;
+import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
import android.media.session.MediaSessionManager;
@@ -83,6 +84,8 @@
private SharedPreferences mSharedPreferences;
@Mock
private MediaSessionManager mMediaSessionManager;
+ @Mock
+ private MediaRouter2Manager mRouterManager;
private final List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>();
@@ -102,7 +105,7 @@
Context.MEDIA_SESSION_SERVICE);
mController = new RemoteVolumeGroupController(mContext, KEY_REMOTE_VOLUME_GROUP);
mController.mLocalMediaManager = mLocalMediaManager;
- mController.mRouterManager = mock(MediaRouter2Manager.class);
+ mController.mRouterManager = mRouterManager;
mPreferenceCategory = spy(new PreferenceCategory(mContext));
mPreferenceCategory.setKey(mController.getPreferenceKey());
@@ -118,7 +121,7 @@
when(remoteSessionInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
when(remoteSessionInfo.isSystemSession()).thenReturn(false);
mRoutingSessionInfos.add(remoteSessionInfo);
- when(mLocalMediaManager.getActiveMediaSession()).thenReturn(mRoutingSessionInfos);
+ when(mLocalMediaManager.getRemoteRoutingSessions()).thenReturn(mRoutingSessionInfos);
}
@Test
@@ -178,6 +181,10 @@
@Test
public void displayPreference_withActiveSession_checkSwitcherPreferenceTitle() {
+ // Preference title needs media output to be enabled.
+ when(mRouterManager.getTransferableRoutes(TEST_PACKAGE_NAME)).thenReturn(List.of(mock(
+ MediaRoute2Info.class)));
+
initPackage();
mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
mController.displayPreference(mScreen);
diff --git a/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java
index ad374cd..6abfe69 100644
--- a/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java
@@ -17,62 +17,81 @@
package com.android.settings.notification;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.media.AudioManager;
+import android.os.LocaleList;
import android.preference.SeekBarVolumizer;
import android.widget.SeekBar;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
+import java.util.Locale;
+
@RunWith(RobolectricTestRunner.class)
public class VolumeSeekBarPreferenceTest {
private static final CharSequence CONTENT_DESCRIPTION = "TEST";
+ private static final int STREAM = 5;
@Mock
private AudioManager mAudioManager;
@Mock
private VolumeSeekBarPreference mPreference;
@Mock
private Context mContext;
+
+ @Mock
+ private Resources mRes;
+ @Mock
+ private Configuration mConfig;
@Mock
private SeekBar mSeekBar;
+ @Captor
+ private ArgumentCaptor<SeekBarVolumizer.Callback> mSbvc;
@Mock
private SeekBarVolumizer mVolumizer;
+ @Mock
+ private SeekBarVolumizerFactory mSeekBarVolumizerFactory;
private VolumeSeekBarPreference.Listener mListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+ when(mSeekBarVolumizerFactory.create(eq(STREAM), eq(null), mSbvc.capture()))
+ .thenReturn(mVolumizer);
+ doCallRealMethod().when(mPreference).setStream(anyInt());
doCallRealMethod().when(mPreference).updateContentDescription(CONTENT_DESCRIPTION);
mPreference.mSeekBar = mSeekBar;
mPreference.mAudioManager = mAudioManager;
- mPreference.mVolumizer = mVolumizer;
+ mPreference.mSeekBarVolumizerFactory = mSeekBarVolumizerFactory;
mListener = () -> mPreference.updateContentDescription(CONTENT_DESCRIPTION);
}
@Test
public void setStream_shouldSetMinMaxAndProgress() {
- final int stream = 5;
final int max = 17;
final int min = 1;
final int progress = 4;
- when(mAudioManager.getStreamMaxVolume(stream)).thenReturn(max);
- when(mAudioManager.getStreamMinVolumeInt(stream)).thenReturn(min);
- when(mAudioManager.getStreamVolume(stream)).thenReturn(progress);
- doCallRealMethod().when(mPreference).setStream(anyInt());
+ when(mAudioManager.getStreamMaxVolume(STREAM)).thenReturn(max);
+ when(mAudioManager.getStreamMinVolumeInt(STREAM)).thenReturn(min);
+ when(mAudioManager.getStreamVolume(STREAM)).thenReturn(progress);
- mPreference.setStream(stream);
+ mPreference.setStream(STREAM);
verify(mPreference).setMax(max);
verify(mPreference).setMin(min);
@@ -85,6 +104,7 @@
doCallRealMethod().when(mPreference).setListener(mListener);
doCallRealMethod().when(mPreference).init();
+ mPreference.setStream(STREAM);
mPreference.setListener(mListener);
mPreference.init();
@@ -107,8 +127,69 @@
doCallRealMethod().when(mPreference).setListener(mListener);
doCallRealMethod().when(mPreference).init();
+ mPreference.setStream(STREAM);
mPreference.init();
verify(mPreference, never()).updateContentDescription(CONTENT_DESCRIPTION);
}
+
+ @Test
+ public void init_changeProgress_overrideStateDescriptionCalled() {
+ final int progress = 4;
+ when(mPreference.formatStateDescription(progress)).thenReturn(CONTENT_DESCRIPTION);
+ doCallRealMethod().when(mPreference).init();
+
+ mPreference.setStream(STREAM);
+ mPreference.init();
+
+ verify(mSeekBarVolumizerFactory).create(eq(STREAM), eq(null), mSbvc.capture());
+
+ mSbvc.getValue().onProgressChanged(mSeekBar, 4, true);
+
+ verify(mPreference).overrideSeekBarStateDescription(CONTENT_DESCRIPTION);
+ }
+
+ @Test
+ public void init_changeProgress_stateDescriptionValueUpdated() {
+ final int max = 17;
+ final int min = 1;
+ int progress = 4;
+ when(mAudioManager.getStreamMaxVolume(STREAM)).thenReturn(max);
+ when(mAudioManager.getStreamMinVolumeInt(STREAM)).thenReturn(min);
+ when(mAudioManager.getStreamVolume(STREAM)).thenReturn(progress);
+ when(mPreference.getMin()).thenReturn(min);
+ when(mPreference.getMax()).thenReturn(max);
+ when(mPreference.getContext()).thenReturn(mContext);
+ when(mContext.getResources()).thenReturn(mRes);
+ when(mRes.getConfiguration()).thenReturn(mConfig);
+ when(mConfig.getLocales()).thenReturn(new LocaleList(Locale.US));
+ doCallRealMethod().when(mPreference).init();
+
+ mPreference.setStream(STREAM);
+ mPreference.init();
+
+ // On progress change, Round down the percent to match it with what the talkback says.
+ // (b/285458191)
+ // when progress is 4, the percent is 0.187. The state description should be set to 18%.
+ testFormatStateDescription(progress, "18%");
+
+ progress = 6;
+
+ // when progress is 6, the percent is 0.3125. The state description should be set to 31%.
+ testFormatStateDescription(progress, "31%");
+
+ progress = 7;
+
+ // when progress is 7, the percent is 0.375. The state description should be set to 37%.
+ testFormatStateDescription(progress, "37%");
+ }
+
+ private void testFormatStateDescription(int progress, String expected) {
+ doCallRealMethod().when(mPreference).formatStateDescription(progress);
+ doCallRealMethod().when(mPreference).getPercent(progress);
+
+ mSbvc.getValue().onProgressChanged(mSeekBar, progress, true);
+
+ verify(mPreference).overrideSeekBarStateDescription(expected);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
index 12e4b8d..dea936d 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
@@ -27,10 +27,8 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-import static com.android.internal.widget.LockPatternUtils.FLAG_ENABLE_AUTO_PIN_CONFIRMATION;
import static com.android.internal.widget.LockPatternUtils.PASSWORD_TYPE_KEY;
import static com.android.settings.password.ChooseLockGeneric.CONFIRM_CREDENTIALS;
@@ -45,7 +43,6 @@
import android.app.admin.PasswordPolicy;
import android.content.Intent;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
@@ -55,7 +52,6 @@
import com.android.settings.password.ChooseLockPassword.ChooseLockPasswordFragment;
import com.android.settings.password.ChooseLockPassword.IntentBuilder;
import com.android.settings.testutils.shadow.SettingsShadowResources;
-import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import com.android.settings.testutils.shadow.ShadowUtils;
@@ -79,7 +75,6 @@
ShadowLockPatternUtils.class,
ShadowUtils.class,
ShadowDevicePolicyManager.class,
- ShadowDeviceConfig.class,
})
public class ChooseLockPasswordTest {
@Before
@@ -453,8 +448,6 @@
@Test
public void autoPinConfirmOption_featureEnabledAndUntouchedByUser_changeStateAsPerRules() {
- DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
- /* value= */ "true", /* makeDefault= */ false);
ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
@@ -491,8 +484,6 @@
@Test
public void autoPinConfirmOption_featureEnabledAndModifiedByUser_shouldChangeStateAsPerRules() {
- DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
- /* value= */ "true", /* makeDefault= */ false);
ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
diff --git a/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java
index 715913c..86c1244 100644
--- a/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java
@@ -16,22 +16,16 @@
package com.android.settings.security.screenlock;
-import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
-
-import static com.android.internal.widget.LockPatternUtils.FLAG_ENABLE_AUTO_PIN_CONFIRMATION;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.provider.DeviceConfig;
import androidx.preference.SwitchPreference;
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils;
-import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
import org.junit.Before;
@@ -40,10 +34,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowDeviceConfig.class})
public class AutoPinConfirmPreferenceControllerTest {
private static final Integer TEST_USER_ID = 1;
@Mock
@@ -65,8 +57,6 @@
@Test
public void isAvailable_featureEnabledAndLockSetToNone_shouldReturnFalse() {
- DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
- "true", /* makeDefault */ false);
when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
assertThat(mController.isAvailable()).isFalse();
@@ -74,8 +64,6 @@
@Test
public void isAvailable_featureEnabledAndLockSetToPassword_shouldReturnFalse() {
- DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
- "true", /* makeDefault */ false);
when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
.thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
@@ -85,8 +73,6 @@
@Test
public void isAvailable_featureEnabledAndLockSetToPIN_lengthLessThanSix_shouldReturnFalse() {
- DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
- "true", /* makeDefault */ false);
when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
.thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PIN);
when(mLockPatternUtils.getPinLength(TEST_USER_ID)).thenReturn(5);
@@ -96,8 +82,6 @@
@Test
public void isAvailable_featureEnabledAndLockSetToPIN_lengthMoreThanEqSix_shouldReturnTrue() {
- DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
- "true", /* makeDefault */ false);
when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
.thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PIN);
@@ -107,20 +91,7 @@
}
@Test
- public void isAvailable_featureDisabledAndLockSetToPIN_shouldReturnFalse() {
- DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
- "false", /* makeDefault */ false);
- when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
- when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
- .thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PIN);
-
- assertThat(mController.isAvailable()).isFalse();
- }
-
- @Test
public void updateState_ChangingSettingState_shouldSetPreferenceToAppropriateCheckedState() {
- DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
- "true", /* makeDefault */ false);
// When auto_pin_confirm setting is disabled, switchPreference is unchecked
when(mLockPatternUtils.isAutoPinConfirmEnabled(TEST_USER_ID)).thenReturn(false);
mController.updateState(mPreference);
diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
index 90985dd..cd9b081 100644
--- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -171,8 +171,9 @@
return powerUsageFeatureProvider;
}
+ @NotNull
@Override
- public DashboardFeatureProvider getDashboardFeatureProvider(Context context) {
+ public DashboardFeatureProvider getDashboardFeatureProvider() {
return dashboardFeatureProvider;
}
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java b/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java
index a60b531..316beb3 100644
--- a/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java
@@ -18,11 +18,15 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
@@ -31,6 +35,7 @@
import com.android.settingslib.R;
import com.android.settingslib.wifi.WifiUtils;
+import com.android.wifitrackerlib.HotspotNetworkEntry;
import com.android.wifitrackerlib.WifiEntry;
import org.junit.Before;
@@ -52,6 +57,8 @@
@Mock
private WifiEntry mMockWifiEntry;
@Mock
+ private HotspotNetworkEntry mHotspotNetworkEntry;
+ @Mock
private WifiUtils.InternetIconInjector mMockIconInjector;
@Mock
@@ -256,4 +263,26 @@
public void getSecondTargetResId_shouldNotReturnZero() {
assertThat(mPref.getSecondTargetResId()).isNotEqualTo(0);
}
+
+ @Test
+ public void refresh_itsHotspotNetworkEntry_shouldUpdateHotspotIcon() {
+ int deviceType = NetworkProviderInfo.DEVICE_TYPE_PHONE;
+ when(mHotspotNetworkEntry.getDeviceType()).thenReturn(deviceType);
+ WifiEntryPreference pref = spy(
+ new WifiEntryPreference(mContext, mHotspotNetworkEntry, mMockIconInjector));
+
+ pref.refresh();
+
+ verify(pref).updateHotspotIcon(deviceType);
+ }
+
+ @Test
+ public void refresh_notHotspotNetworkEntry_shouldNotUpdateHotspotIcon() {
+ WifiEntryPreference pref = spy(
+ new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector));
+
+ pref.refresh();
+
+ verify(pref, never()).updateHotspotIcon(anyInt());
+ }
}
diff --git a/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java b/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
index 9d57d4a..54a546a 100644
--- a/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
@@ -26,19 +26,25 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.net.wifi.WifiManager;
+
import androidx.lifecycle.Lifecycle;
+import androidx.test.core.app.ApplicationProvider;
import com.android.settings.wifi.WifiPickerTrackerHelper;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import java.util.Arrays;
@@ -47,17 +53,24 @@
private static final int SUB_ID = 2;
- private WifiScanWorker mWifiScanWorker;
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Spy
+ Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock
+ WifiManager mWifiManager;
@Mock
WifiPickerTracker mWifiPickerTracker;
@Mock
WifiPickerTrackerHelper mWifiPickerTrackerHelper;
+ private WifiScanWorker mWifiScanWorker;
+
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager);
- mWifiScanWorker = new WifiScanWorker(RuntimeEnvironment.application, WIFI_SLICE_URI);
+ mWifiScanWorker = new WifiScanWorker(mContext, WIFI_SLICE_URI);
mWifiScanWorker.mWifiPickerTracker = mWifiPickerTracker;
mWifiScanWorker.mWifiPickerTrackerHelper = mWifiPickerTrackerHelper;
when(mWifiPickerTrackerHelper.isCarrierNetworkProvisionEnabled(SUB_ID)).thenReturn(false);
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreferenceTest.kt
new file mode 100644
index 0000000..342405a
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreferenceTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2023 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.appcompat
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.os.Build
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.hasTextExactly
+import androidx.compose.ui.test.junit4.createComposeRule
+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 android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER
+import com.android.settings.R
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.applications.appcompat.UserAspectRatioDetails
+import com.android.settings.applications.appcompat.UserAspectRatioManager
+import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
+import com.android.settings.testutils.TestDeviceConfig
+import com.android.settingslib.spa.testutils.delay
+import com.android.settingslib.spa.testutils.waitUntilExists
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.MockitoSession
+import org.mockito.Spy
+import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * To run this test: atest SettingsSpaUnitTests:UserAspectRatioAppPreferenceTest
+ */
+@RunWith(AndroidJUnit4::class)
+class UserAspectRatioAppPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private lateinit var mockSession: MockitoSession
+
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Spy
+ private val resources = context.resources
+
+ private val aspectRatioEnabledConfig =
+ TestDeviceConfig(NAMESPACE_WINDOW_MANAGER, "enable_app_compat_user_aspect_ratio_settings")
+
+ private lateinit var userAspectRatioManager: UserAspectRatioManager
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ @Before
+ fun setUp() {
+ mockSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .mockStatic(UserAspectRatioDetails::class.java)
+ .mockStatic(AppInfoDashboardFragment::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ whenever(context.resources).thenReturn(resources)
+ whenever(context.packageManager).thenReturn(packageManager)
+ userAspectRatioManager = mock(UserAspectRatioManager::class.java)
+ }
+
+ @After
+ fun tearDown() {
+ aspectRatioEnabledConfig.reset()
+ mockSession.finishMocking()
+ }
+
+ @Test
+ fun whenConfigIsFalse_notDisplayed() {
+ setConfig(false)
+
+ setContent()
+
+ composeTestRule.onRoot().assertIsNotDisplayed()
+ }
+
+ @Test
+ fun whenCannotDisplayAspectRatioUi_notDisplayed() {
+ setContent()
+
+ composeTestRule.onRoot().assertIsNotDisplayed()
+ }
+
+ @Test
+ fun whenCanDisplayAspectRatioUiAndConfigFalse_notDisplayed() {
+ setConfig(false)
+ whenever(packageManager.queryIntentActivities(any(), anyInt()))
+ .thenReturn(listOf(RESOLVE_INFO))
+
+ setContent()
+
+ composeTestRule.onRoot().assertIsNotDisplayed()
+ }
+
+ @Test
+ fun whenCannotDisplayAspectRatioUiAndConfigTrue_notDisplayed() {
+ setConfig(true)
+
+ setContent()
+
+ composeTestRule.onRoot().assertIsNotDisplayed()
+ }
+
+ @Test
+ fun whenCanDisplayAspectRatioUiAndConfigTrue_Displayed() {
+ setConfig(true)
+ whenever(packageManager.queryIntentActivities(any(), anyInt()))
+ .thenReturn(listOf(RESOLVE_INFO))
+
+ setContent()
+
+ composeTestRule.onNode(
+ hasTextExactly(
+ context.getString(R.string.aspect_ratio_title),
+ context.getString(R.string.user_aspect_ratio_app_default)
+ ),
+ ).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun onClick_startActivity() {
+ setConfig(true)
+ whenever(packageManager.queryIntentActivities(any(), anyInt()))
+ .thenReturn(listOf(RESOLVE_INFO))
+
+ setContent()
+ composeTestRule.onRoot().performClick()
+
+ ExtendedMockito.verify {
+ AppInfoDashboardFragment.startAppInfoFragment(
+ UserAspectRatioDetails::class.java,
+ APP,
+ context,
+ AppInfoSettingsProvider.METRICS_CATEGORY,
+ )
+ }
+ }
+
+ private fun setConfig(enabled: Boolean) {
+ whenever(resources.getBoolean(
+ com.android.internal.R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled
+ )).thenReturn(enabled)
+ aspectRatioEnabledConfig.override(enabled)
+ }
+
+ private fun setContent() {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ UserAspectRatioAppPreference(APP)
+ }
+ }
+ composeTestRule.delay()
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ const val UID = 123
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = UID
+ }
+ private val RESOLVE_INFO = ResolveInfo().apply {
+ activityInfo = ActivityInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt
new file mode 100644
index 0000000..0d2869c
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2023 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.appcompat
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN
+import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+import android.os.Build
+import androidx.compose.runtime.State
+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.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.template.app.AppListItemModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * To run this test: atest SettingsSpaUnitTests:UserAspectRatioAppsPageProviderTest
+ */
+@RunWith(AndroidJUnit4::class)
+class UserAspectRatioAppsPageProviderTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val fakeNavControllerWrapper = FakeNavControllerWrapper()
+
+ @Test
+ fun aspectRatioAppsPageProvider_name() {
+ assertThat(UserAspectRatioAppsPageProvider.name).isEqualTo(EXPECTED_PROVIDER_NAME)
+ }
+
+ @Test
+ fun injectEntry_title() {
+ setInjectEntry()
+ composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_title))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun injectEntry_summary() {
+ setInjectEntry()
+ composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_summary, Build.MODEL))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun injectEntry_onClick_navigate() {
+ setInjectEntry()
+ composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_title)).performClick()
+ assertThat(fakeNavControllerWrapper.navigateCalledWith).isEqualTo("UserAspectRatioAppsPage")
+ }
+
+ private fun setInjectEntry() {
+ composeTestRule.setContent {
+ fakeNavControllerWrapper.Wrapper {
+ UserAspectRatioAppsPageProvider.buildInjectEntry().build().UiLayout()
+ }
+ }
+ }
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ UserAspectRatioAppList {}
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_title))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun item_labelDisplayed() {
+ setItemContent()
+
+ composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun aspectRatioAppListModel_transform() = runTest {
+ val listModel = UserAspectRatioAppListModel(context)
+ val recordListFlow = listModel.transform(flowOf(USER_ID), flowOf(listOf(APP)))
+ val recordList = recordListFlow.firstWithTimeoutOrNull()!!
+
+ assertThat(recordList).hasSize(1)
+ assertThat(recordList[0].app).isSameInstanceAs(APP)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun aspectRatioAppListModel_filter() = runTest {
+ val listModel = UserAspectRatioAppListModel(context)
+
+ val recordListFlow = listModel.filter(flowOf(USER_ID), 0,
+ flowOf(listOf(APP_RECORD_NOT_DISPLAYED, APP_RECORD_SUGGESTED)))
+
+ val recordList = checkNotNull(recordListFlow.firstWithTimeoutOrNull())
+ assertThat(recordList).containsExactly(APP_RECORD_SUGGESTED)
+ }
+
+ private fun setItemContent() {
+ composeTestRule.setContent {
+ fakeNavControllerWrapper.Wrapper {
+ with(UserAspectRatioAppListModel(context)) {
+ AppListItemModel(
+ record = APP_RECORD_SUGGESTED,
+ label = LABEL,
+ summary = stateOf(SUMMARY)
+ ).AppItem()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun aspectRatioAppListModel_getSummaryDefault() {
+ val summaryState = setSummaryState(USER_MIN_ASPECT_RATIO_UNSET)
+ assertThat(summaryState.value)
+ .isEqualTo(context.getString(R.string.user_aspect_ratio_app_default))
+ }
+
+ @Test
+ fun aspectRatioAppListModel_getSummaryWhenSplitScreen() {
+ val summaryState = setSummaryState(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN)
+ assertThat(summaryState.value)
+ .isEqualTo(context.getString(R.string.user_aspect_ratio_half_screen))
+ }
+
+ private fun setSummaryState(override: Int): State<String> {
+ val listModel = UserAspectRatioAppListModel(context)
+ lateinit var summaryState: State<String>
+ composeTestRule.setContent {
+ summaryState = listModel.getSummary(option = 0,
+ record = UserAspectRatioAppListItemModel(
+ app = APP,
+ override = override,
+ suggested = false,
+ canDisplay = true,
+ ))
+ }
+ return summaryState
+ }
+
+
+ private companion object {
+ private const val EXPECTED_PROVIDER_NAME = "UserAspectRatioAppsPage"
+ private const val PACKAGE_NAME = "package.name"
+ private const val USER_ID = 0
+ private const val LABEL = "Label"
+ private const val SUMMARY = "Summary"
+
+ private val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ private val APP_RECORD_SUGGESTED = UserAspectRatioAppListItemModel(
+ APP,
+ override = USER_MIN_ASPECT_RATIO_UNSET,
+ suggested = true,
+ canDisplay = true
+ )
+ private val APP_RECORD_NOT_DISPLAYED = UserAspectRatioAppListItemModel(
+ APP,
+ override = USER_MIN_ASPECT_RATIO_UNSET,
+ suggested = true,
+ canDisplay = false
+ )
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/storage/StorageAppListTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/storage/StorageAppListTest.kt
new file mode 100644
index 0000000..836bf09
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/storage/StorageAppListTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 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.storage
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.icu.text.CollationKey
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.model.app.AppEntry
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class StorageAppListTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun storageAppListPageProvider_apps_name() {
+ assertThat(StorageAppListPageProvider.Apps.name).isEqualTo("StorageAppList")
+ }
+
+ @Test
+ fun storageAppListPageProvider_games_name() {
+ assertThat(StorageAppListPageProvider.Games.name).isEqualTo("GameStorageAppList")
+ }
+
+ @Test
+ fun transform_containsSize() = runTest {
+ val listModel = StorageAppListModel(context, StorageType.Apps)
+ val recordListFlow = listModel.transform(flowOf(0), flowOf(listOf(APP)))
+ val recordList = recordListFlow.firstWithTimeoutOrNull()!!
+ assertThat(recordList).hasSize(1)
+ assertThat(recordList.first().app).isSameInstanceAs(APP)
+ assertThat(recordList.first().size).isEqualTo(0L)
+ }
+
+ @Test
+ fun filter_apps_appWithoutGame() = runTest {
+ val listModel = StorageAppListModel(context, StorageType.Apps)
+ val recordListFlow = listModel.filter(
+ flowOf(0),
+ 0,
+ flowOf(
+ listOf(
+ AppRecordWithSize(APP, 1L),
+ AppRecordWithSize(APP2, 1L),
+ AppRecordWithSize(GAME, 1L)
+ )
+ )
+ )
+ val recordList = recordListFlow.firstWithTimeoutOrNull()!!
+ assertThat(recordList).hasSize(2)
+ assertThat(recordList.none { it.app === GAME }).isTrue()
+ }
+
+ @Test
+ fun filter_games_gameWithoutApp() = runTest {
+ val listModel = StorageAppListModel(context, StorageType.Games)
+ val recordListFlow = listModel.filter(
+ flowOf(0),
+ 0,
+ flowOf(
+ listOf(
+ AppRecordWithSize(APP, 1L),
+ AppRecordWithSize(APP2, 1L),
+ AppRecordWithSize(GAME, 1L)
+ )
+ )
+ )
+ val recordList = recordListFlow.firstWithTimeoutOrNull()!!
+ assertThat(recordList).hasSize(1)
+ assertThat(recordList.all { it.app === GAME }).isTrue()
+ }
+
+ @Test
+ fun getComparator_sortsByDescendingSize() {
+ val listModel = StorageAppListModel(context, StorageType.Apps)
+ val source = listOf(
+ AppEntry(
+ AppRecordWithSize(app = APP, size = 1L),
+ "app1",
+ CollationKey("first", byteArrayOf(0))
+ ),
+ AppEntry(
+ AppRecordWithSize(app = APP2, size = 3L),
+ "app2",
+ CollationKey("second", byteArrayOf(0))
+ ),
+ AppEntry(
+ AppRecordWithSize(app = APP3, size = 3L),
+ "app3",
+ CollationKey("third", byteArrayOf(0))
+ )
+ )
+
+ val result = source.sortedWith(listModel.getComparator(0))
+
+ assertThat(result[0].record.app).isSameInstanceAs(APP2)
+ assertThat(result[1].record.app).isSameInstanceAs(APP3)
+ assertThat(result[2].record.app).isSameInstanceAs(APP)
+ }
+
+ private companion object {
+ const val APP_PACKAGE_NAME = "app.package.name"
+ const val APP_PACKAGE_NAME2 = "app.package.name2"
+ const val APP_PACKAGE_NAME3 = "app.package.name3"
+ const val GAME_PACKAGE_NAME = "game.package.name"
+ val APP = ApplicationInfo().apply {
+ packageName = APP_PACKAGE_NAME
+ flags = ApplicationInfo.FLAG_INSTALLED
+ }
+ val APP2 = ApplicationInfo().apply {
+ packageName = APP_PACKAGE_NAME2
+ flags = ApplicationInfo.FLAG_INSTALLED
+ }
+ val APP3 = ApplicationInfo().apply {
+ packageName = APP_PACKAGE_NAME3
+ flags = ApplicationInfo.FLAG_INSTALLED
+ }
+ val GAME = ApplicationInfo().apply {
+ packageName = GAME_PACKAGE_NAME
+ flags = ApplicationInfo.FLAG_INSTALLED or ApplicationInfo.FLAG_IS_GAME
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt b/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt
index 19be10e..162f8ea 100644
--- a/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt
@@ -53,7 +53,7 @@
bundle.putString(LOG_DATA_SESSION_NAME, SESSION_BROWSE)
val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel)
- assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.BROWSE)
+ assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SESSION_BROWSE)
}
@Test
@@ -61,7 +61,7 @@
bundle.putString(LOG_DATA_SESSION_NAME, SESSION_SEARCH)
val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel)
- assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SEARCH)
+ assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SESSION_SEARCH)
}
@Test
diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
index 54934a7..0fe634a 100644
--- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
+++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
@@ -77,9 +77,8 @@
override val batterySettingsFeatureProvider: BatterySettingsFeatureProvider
get() = TODO("Not yet implemented")
- override fun getDashboardFeatureProvider(context: Context): DashboardFeatureProvider {
- TODO("Not yet implemented")
- }
+ override val dashboardFeatureProvider: DashboardFeatureProvider
+ get() = TODO("Not yet implemented")
override val dockUpdaterFeatureProvider: DockUpdaterFeatureProvider
get() = TODO("Not yet implemented")
diff --git a/tests/unit/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceControllerTest.java b/tests/unit/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceControllerTest.java
index e3d2408..9a6bf70 100644
--- a/tests/unit/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceControllerTest.java
@@ -27,6 +27,7 @@
import com.android.settings.core.BasePreferenceController;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,6 +52,11 @@
mContext, "font_weight_adjustment");
}
+ @After
+ public void teardown() {
+ Settings.Secure.resetToDefaults(mContext.getContentResolver(), /* tag= */ null);
+ }
+
@Test
public void getAvailabilityStatus_byDefault_shouldReturnAvailable() {
assertThat(mController.getAvailabilityStatus()).isEqualTo(
diff --git a/tests/unit/src/com/android/settings/applications/appcompat/UserAspectRatioManagerTest.java b/tests/unit/src/com/android/settings/applications/appcompat/UserAspectRatioManagerTest.java
new file mode 100644
index 0000000..f4dcaf8
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/appcompat/UserAspectRatioManagerTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 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.appcompat;
+
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+
+import static com.android.settings.applications.appcompat.UserAspectRatioManager.KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN;
+import static com.android.settings.applications.appcompat.UserAspectRatioManager.KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.provider.DeviceConfig;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.settings.testutils.ResourcesUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * To run this test: atest SettingsUnitTests:UserAspectRatioManagerTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class UserAspectRatioManagerTest {
+
+ private Context mContext;
+ private Resources mResources;
+ private UserAspectRatioManager mUtils;
+ private String mOriginalSettingsFlag;
+ private String mOriginalFullscreenFlag;
+
+ @Before
+ public void setUp() {
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ mResources = spy(mContext.getResources());
+ mUtils = spy(new UserAspectRatioManager(mContext));
+
+ when(mContext.getResources()).thenReturn(mResources);
+
+ mOriginalSettingsFlag = DeviceConfig.getProperty(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS);
+ setAspectRatioSettingsBuildTimeFlagEnabled(true);
+ setAspectRatioSettingsDeviceConfigEnabled("true" /* enabled */, false /* makeDefault */);
+
+ mOriginalFullscreenFlag = DeviceConfig.getProperty(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN);
+ setAspectRatioFullscreenBuildTimeFlagEnabled(true);
+ setAspectRatioFullscreenDeviceConfigEnabled("true" /* enabled */, false /* makeDefault */);
+ }
+
+ @After
+ public void tearDown() {
+ setAspectRatioSettingsDeviceConfigEnabled(mOriginalSettingsFlag, true /* makeDefault */);
+ setAspectRatioFullscreenDeviceConfigEnabled(mOriginalFullscreenFlag,
+ true /* makeDefault */);
+ }
+
+ @Test
+ public void testCanDisplayAspectRatioUi() {
+ final ApplicationInfo canDisplay = new ApplicationInfo();
+ canDisplay.packageName = "com.app.candisplay";
+ addResolveInfoLauncherEntry(canDisplay.packageName);
+
+ assertTrue(mUtils.canDisplayAspectRatioUi(canDisplay));
+
+ final ApplicationInfo noLauncherEntry = new ApplicationInfo();
+ noLauncherEntry.packageName = "com.app.nolauncherentry";
+
+ assertFalse(mUtils.canDisplayAspectRatioUi(noLauncherEntry));
+ }
+
+ @Test
+ public void testIsFeatureEnabled() {
+ assertTrue(UserAspectRatioManager.isFeatureEnabled(mContext));
+ }
+
+ @Test
+ public void testIsFeatureEnabled_disabledBuildTimeFlag_returnFalse() {
+ setAspectRatioSettingsBuildTimeFlagEnabled(false);
+ assertFalse(UserAspectRatioManager.isFeatureEnabled(mContext));
+ }
+
+ @Test
+ public void testIsFeatureEnabled_disabledRuntimeFlag_returnFalse() {
+ setAspectRatioSettingsDeviceConfigEnabled("false" /* enabled */, false /* makeDefault */);
+ assertFalse(UserAspectRatioManager.isFeatureEnabled(mContext));
+ }
+
+ @Test
+ public void testIsFullscreenOptionEnabled() {
+ assertTrue(mUtils.isFullscreenOptionEnabled());
+ }
+
+ @Test
+ public void testIsFullscreenOptionEnabled_settingsDisabled_returnFalse() {
+ setAspectRatioFullscreenBuildTimeFlagEnabled(false);
+ assertFalse(mUtils.isFullscreenOptionEnabled());
+ }
+
+ @Test
+ public void testIsFullscreenOptionEnabled_disabledBuildTimeFlag_returnFalse() {
+ setAspectRatioFullscreenBuildTimeFlagEnabled(false);
+ assertFalse(mUtils.isFullscreenOptionEnabled());
+ }
+
+ @Test
+ public void testIsFullscreenOptionEnabled_disabledRuntimeFlag_returnFalse() {
+ setAspectRatioFullscreenDeviceConfigEnabled("false" /* enabled */, false /*makeDefault */);
+ assertFalse(mUtils.isFullscreenOptionEnabled());
+ }
+
+ @Test
+ public void containsAspectRatioOption_fullscreen() {
+ assertTrue(mUtils.containsAspectRatioOption(USER_MIN_ASPECT_RATIO_FULLSCREEN));
+
+ when(mUtils.isFullscreenOptionEnabled()).thenReturn(false);
+ assertFalse(mUtils.containsAspectRatioOption(USER_MIN_ASPECT_RATIO_FULLSCREEN));
+ }
+
+ @Test
+ public void testGetUserMinAspectRatioEntry() {
+ // R.string.user_aspect_ratio_app_default
+ final String appDefault = ResourcesUtils.getResourcesString(mContext,
+ "user_aspect_ratio_app_default");
+ assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_UNSET))
+ .isEqualTo(appDefault);
+ // should always return default if value does not correspond to anything
+ assertThat(mUtils.getUserMinAspectRatioEntry(-1))
+ .isEqualTo(appDefault);
+ // R.string.user_aspect_ratio_half_screen
+ assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN))
+ .isEqualTo(ResourcesUtils.getResourcesString(mContext,
+ "user_aspect_ratio_half_screen"));
+ // R.string.user_aspect_ratio_3_2
+ assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_3_2))
+ .isEqualTo(ResourcesUtils.getResourcesString(mContext, "user_aspect_ratio_3_2"));
+ // R,string.user_aspect_ratio_4_3
+ assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_4_3))
+ .isEqualTo(ResourcesUtils.getResourcesString(mContext, "user_aspect_ratio_4_3"));
+ // R.string.user_aspect_ratio_16_9
+ assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_16_9))
+ .isEqualTo(ResourcesUtils.getResourcesString(mContext, "user_aspect_ratio_16_9"));
+ // R.string.user_aspect_ratio_fullscreen
+ assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_FULLSCREEN))
+ .isEqualTo(ResourcesUtils.getResourcesString(mContext,
+ "user_aspect_ratio_fullscreen"));
+ }
+
+ @Test
+ public void testGetUserMinAspectRatioEntry_fullscreenDisabled_shouldReturnDefault() {
+ setAspectRatioFullscreenBuildTimeFlagEnabled(false);
+ assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_FULLSCREEN))
+ .isEqualTo(ResourcesUtils.getResourcesString(mContext,
+ "user_aspect_ratio_app_default"));
+ }
+
+ private void setAspectRatioSettingsBuildTimeFlagEnabled(boolean enabled) {
+ when(mResources.getBoolean(R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled))
+ .thenReturn(enabled);
+ }
+
+ private void setAspectRatioSettingsDeviceConfigEnabled(String enabled, boolean makeDefault) {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS, enabled, makeDefault);
+ }
+
+ private void setAspectRatioFullscreenBuildTimeFlagEnabled(boolean enabled) {
+ when(mResources.getBoolean(R.bool.config_appCompatUserAppAspectRatioFullscreenIsEnabled))
+ .thenReturn(enabled);
+ }
+
+ private void setAspectRatioFullscreenDeviceConfigEnabled(String enabled, boolean makeDefault) {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN, enabled, makeDefault);
+ }
+
+ private void addResolveInfoLauncherEntry(String packageName) {
+ final ResolveInfo resolveInfo = mock(ResolveInfo.class);
+ final ActivityInfo activityInfo = mock(ActivityInfo.class);
+ activityInfo.packageName = packageName;
+ resolveInfo.activityInfo = activityInfo;
+ mUtils.addInfoHasLauncherEntry(resolveInfo);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollErrorDialogViewModelTest.kt b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollErrorDialogViewModelTest.kt
index ae829b9..3679dd2 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollErrorDialogViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollErrorDialogViewModelTest.kt
@@ -24,6 +24,9 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -56,79 +59,69 @@
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testNewDialog() = runTest {
- backgroundScope.launch {
- mutableListOf<Any>().let { list ->
- viewModel.newDialogFlow.toList(list)
- assertThat(list.size).isEqualTo(0)
- }
-
- mutableListOf<FingerprintErrorDialogSetResultAction>().let { list ->
- val testErrorMsgId = 3456
- viewModel.newDialog(testErrorMsgId)
-
- assertThat(viewModel.isDialogShown).isTrue()
- viewModel.setResultFlow.toList(list)
- assertThat(list.size).isEqualTo(1)
- assertThat(list[0]).isEqualTo(testErrorMsgId)
+ val newDialogs: List<Int> = mutableListOf<Int>().also {
+ backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
+ viewModel.newDialogFlow.toList(it)
}
}
+
+ runCurrent()
+
+ // Default values
+ assertThat(viewModel.isDialogShown).isFalse()
+ assertThat(newDialogs.size).isEqualTo(0)
+
+ val testErrorMsgId = 3456
+ viewModel.newDialog(testErrorMsgId)
+ runCurrent()
+
+ // verify after emit
+ assertThat(viewModel.isDialogShown).isTrue()
+ assertThat(newDialogs.size).isEqualTo(1)
+ assertThat(newDialogs[0]).isEqualTo(testErrorMsgId)
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testTriggerRetry() = runTest {
- backgroundScope.launch {
- // triggerRetryFlow shall be empty on begin
- mutableListOf<Any>().let { list ->
- viewModel.triggerRetryFlow.toList(list)
- assertThat(list.size).isEqualTo(0)
- }
-
- // emit newDialog
- mutableListOf<FingerprintErrorDialogSetResultAction>().let { list ->
- viewModel.newDialog(0)
- viewModel.triggerRetry()
-
- assertThat(viewModel.isDialogShown).isFalse()
- viewModel.setResultFlow.toList(list)
- assertThat(list.size).isEqualTo(1)
+ val triggerRetries: List<Any> = mutableListOf<Any>().also {
+ backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
+ viewModel.triggerRetryFlow.toList(it)
}
}
+
+ runCurrent()
+
+ // Default values
+ assertThat(triggerRetries.size).isEqualTo(0)
+
+ viewModel.triggerRetry()
+ runCurrent()
+
+ // verify after emit
+ assertThat(triggerRetries.size).isEqualTo(1)
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testSetResultFinish() = runTest {
- backgroundScope.launch {
- // setResultFlow shall be empty on begin
- mutableListOf<FingerprintErrorDialogSetResultAction>().let { list ->
- viewModel.setResultFlow.toList(list)
- assertThat(list.size).isEqualTo(0)
+ val setResults: List<FingerprintErrorDialogSetResultAction> =
+ mutableListOf<FingerprintErrorDialogSetResultAction>().also {
+ backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
+ viewModel.setResultFlow.toList(it)
+ }
}
- // emit FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
- viewModel = FingerprintEnrollErrorDialogViewModel(application, false)
- mutableListOf<FingerprintErrorDialogSetResultAction>().let { list ->
- viewModel.newDialog(0)
- viewModel.setResultAndFinish(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH)
+ runCurrent()
- assertThat(viewModel.isDialogShown).isFalse()
- viewModel.setResultFlow.toList(list)
- assertThat(list.size).isEqualTo(1)
- assertThat(list[0]).isEqualTo(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH)
- }
+ // Default values
+ assertThat(setResults.size).isEqualTo(0)
- // emit FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
- viewModel = FingerprintEnrollErrorDialogViewModel(application, false)
- mutableListOf<FingerprintErrorDialogSetResultAction>().let { list ->
- viewModel.newDialog(0)
- viewModel.setResultAndFinish(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT)
+ viewModel.setResultAndFinish(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH)
+ runCurrent()
- assertThat(viewModel.isDialogShown).isFalse()
- viewModel.setResultFlow.toList(list)
- assertThat(list.size).isEqualTo(1)
- assertThat(list[0]).isEqualTo(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH)
- }
- }
+ // verify after emit
+ assertThat(setResults.size).isEqualTo(1)
+ assertThat(setResults[0]).isEqualTo(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH)
}
}
diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
index 79804e3..0c3bc8c 100644
--- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -170,8 +170,9 @@
return powerUsageFeatureProvider;
}
+ @NotNull
@Override
- public DashboardFeatureProvider getDashboardFeatureProvider(Context context) {
+ public DashboardFeatureProvider getDashboardFeatureProvider() {
return dashboardFeatureProvider;
}