Snap for 12829911 from f2f9874122 to 25Q2-release

Change-Id: I57ad032a7adfdc5427f643e82533893b0fe69274
This commit is contained in:
Android Build Coastguard Worker
2024-12-19 16:18:10 -08:00
34 changed files with 736 additions and 1300 deletions

View File

@@ -4459,6 +4459,19 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".security.ActionDisabledByAdvancedProtectionDialog"
android:theme="@style/Theme.AlertDialog"
android:taskAffinity="com.android.settings.security"
android:excludeFromRecents="true"
android:exported="false"
android:launchMode="singleTop"
android:featureFlag="android.security.aapm_api">
<intent-filter android:priority="1">
<action android:name="android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity <activity
android:name="Settings$ManageExternalStorageActivity" android:name="Settings$ManageExternalStorageActivity"
android:knownActivityEmbeddingCerts="@array/config_known_host_certs" android:knownActivityEmbeddingCerts="@array/config_known_host_certs"
@@ -5440,7 +5453,7 @@
</service> </service>
<receiver android:name="com.android.settings.connecteddevice.audiosharing.AudioSharingReceiver" <receiver android:name="com.android.settings.connecteddevice.audiosharing.AudioSharingReceiver"
android:exported="false"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE" /> <action android:name="com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE" />
<action android:name="com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STOP" /> <action android:name="com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STOP" />

View File

@@ -14,15 +14,217 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<inset xmlns:android="http://schemas.android.com/apk/res/android" <selector xmlns:android="http://schemas.android.com/apk/res/android"
android:insetBottom="@dimen/pointer_fill_style_circle_offset" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" >
android:insetLeft="@dimen/pointer_fill_style_circle_offset" <item
android:insetRight="@dimen/pointer_fill_style_circle_offset" android:state_hovered="true"
android:insetTop="@dimen/pointer_fill_style_circle_offset"> android:state_selected="true">
<shape android:shape="oval"> <layer-list>
<size <item
android:width="@dimen/pointer_fill_style_circle_inner_diameter" android:top="@dimen/pointer_fill_style_circle_background_outline_offset"
android:height="@dimen/pointer_fill_style_circle_inner_diameter" /> android:left="@dimen/pointer_fill_style_circle_background_outline_offset"
<solid android:color="@android:color/white" /> android:bottom="@dimen/pointer_fill_style_circle_background_outline_offset"
</shape> android:right="@dimen/pointer_fill_style_circle_background_outline_offset">
</inset> <shape android:shape="rectangle">
<corners
android:radius="@dimen/pointer_fill_style_circle_background_corner_radius" />
<size
android:width="@dimen/pointer_fill_style_circle_background_outer_diameter_less_stroke"
android:height="@dimen/pointer_fill_style_circle_background_outer_diameter_less_stroke" />
<stroke
android:width="@dimen/pointer_fill_style_circle_background_selected_outline_stroke"
android:color="@androidprv:color/materialColorSecondary" />
</shape>
</item>
<item
android:top="@dimen/pointer_fill_style_circle_background_offset"
android:left="@dimen/pointer_fill_style_circle_background_offset"
android:bottom="@dimen/pointer_fill_style_circle_background_offset"
android:right="@dimen/pointer_fill_style_circle_background_offset">
<shape android:shape="rectangle">
<corners android:radius="@dimen/pointer_fill_style_circle_background_corner_inner_radius" />
<size
android:width="@dimen/pointer_fill_style_circle_background_outer_diameter_selected"
android:height="@dimen/pointer_fill_style_circle_background_outer_diameter_selected" />
<solid
android:color="@color/pointer_fill_hovered_color" />
</shape>
</item>
<item
android:id="@+id/tintableCircleHoveredSelected"
android:top="@dimen/pointer_fill_style_circle_offset_selected"
android:left="@dimen/pointer_fill_style_circle_offset_selected"
android:bottom="@dimen/pointer_fill_style_circle_offset_selected"
android:right="@dimen/pointer_fill_style_circle_offset_selected">
<shape android:shape="oval">
<size
android:width="@dimen/pointer_fill_style_circle_diameter"
android:height="@dimen/pointer_fill_style_circle_diameter" />
<solid android:color="@android:color/white" />
</shape>
</item>
<item
android:top="@dimen/pointer_fill_style_circle_outline_offset_selected"
android:left="@dimen/pointer_fill_style_circle_outline_offset_selected"
android:bottom="@dimen/pointer_fill_style_circle_outline_offset_selected"
android:right="@dimen/pointer_fill_style_circle_outline_offset_selected">
<shape android:shape="oval">
<size
android:width="@dimen/pointer_fill_style_circle_outline_diameter"
android:height="@dimen/pointer_fill_style_circle_outline_diameter" />
<stroke
android:width="@dimen/pointer_fill_style_circle_outline_stroke"
android:color="@android:color/white" />
</shape>
</item>
<item android:gravity="center"
android:width="@dimen/pointer_fill_style_checkmark_size"
android:height="@dimen/pointer_fill_style_checkmark_size"
android:drawable="@drawable/pointer_fill_check_24dp" />
</layer-list>
</item>
<item android:state_selected="true">
<layer-list>
<item
android:top="@dimen/pointer_fill_style_circle_background_outline_offset"
android:left="@dimen/pointer_fill_style_circle_background_outline_offset"
android:bottom="@dimen/pointer_fill_style_circle_background_outline_offset"
android:right="@dimen/pointer_fill_style_circle_background_outline_offset">
<shape android:shape="rectangle">
<corners android:radius="@dimen/pointer_fill_style_circle_background_corner_radius" />
<size
android:width="@dimen/pointer_fill_style_circle_background_outer_diameter_less_stroke"
android:height="@dimen/pointer_fill_style_circle_background_outer_diameter_less_stroke" />
<stroke
android:width="@dimen/pointer_fill_style_circle_background_selected_outline_stroke"
android:color="@androidprv:color/materialColorSecondary" />
</shape>
</item>
<item
android:top="@dimen/pointer_fill_style_circle_background_offset"
android:left="@dimen/pointer_fill_style_circle_background_offset"
android:bottom="@dimen/pointer_fill_style_circle_background_offset"
android:right="@dimen/pointer_fill_style_circle_background_offset">
<shape android:shape="rectangle">
<corners android:radius="@dimen/pointer_fill_style_circle_background_corner_inner_radius" />
<size
android:width="@dimen/pointer_fill_style_circle_background_outer_diameter_selected"
android:height="@dimen/pointer_fill_style_circle_background_outer_diameter_selected" />
<solid android:color="@androidprv:color/materialColorSurfaceBright" />
</shape>
</item>
<item
android:id="@+id/tintableCircleSelected"
android:top="@dimen/pointer_fill_style_circle_offset_selected"
android:left="@dimen/pointer_fill_style_circle_offset_selected"
android:bottom="@dimen/pointer_fill_style_circle_offset_selected"
android:right="@dimen/pointer_fill_style_circle_offset_selected">
<shape android:shape="oval">
<size
android:width="@dimen/pointer_fill_style_circle_diameter"
android:height="@dimen/pointer_fill_style_circle_diameter" />
<solid android:color="@android:color/white" />
</shape>
</item>
<item
android:top="@dimen/pointer_fill_style_circle_outline_offset_selected"
android:left="@dimen/pointer_fill_style_circle_outline_offset_selected"
android:bottom="@dimen/pointer_fill_style_circle_outline_offset_selected"
android:right="@dimen/pointer_fill_style_circle_outline_offset_selected">
<shape android:shape="oval">
<size
android:width="@dimen/pointer_fill_style_circle_outline_diameter"
android:height="@dimen/pointer_fill_style_circle_outline_diameter" />
<stroke
android:width="@dimen/pointer_fill_style_circle_outline_stroke"
android:color="@android:color/white" />
</shape>
</item>
<item
android:gravity="center"
android:width="@dimen/pointer_fill_style_checkmark_size"
android:height="@dimen/pointer_fill_style_checkmark_size"
android:drawable="@drawable/pointer_fill_check_24dp" />
</layer-list>
</item>
<item android:state_hovered="true">
<layer-list>
<item>
<shape android:shape="rectangle">
<corners android:radius="@dimen/pointer_fill_style_circle_background_corner_radius" />
<size
android:width="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:height="@dimen/pointer_fill_style_circle_background_outer_diameter" />
<solid android:color="@color/pointer_fill_hovered_color" />
</shape>
</item>
<item
android:id="@+id/tintableCircleHovered"
android:top="@dimen/pointer_fill_style_circle_offset"
android:left="@dimen/pointer_fill_style_circle_offset"
android:bottom="@dimen/pointer_fill_style_circle_offset"
android:right="@dimen/pointer_fill_style_circle_offset">
<shape android:shape="oval">
<size
android:width="@dimen/pointer_fill_style_circle_diameter"
android:height="@dimen/pointer_fill_style_circle_diameter" />
<solid android:color="@android:color/white" />
</shape>
</item>
<item
android:top="@dimen/pointer_fill_style_circle_outline_offset"
android:left="@dimen/pointer_fill_style_circle_outline_offset"
android:bottom="@dimen/pointer_fill_style_circle_outline_offset"
android:right="@dimen/pointer_fill_style_circle_outline_offset">
<shape android:shape="oval">
<size
android:width="@dimen/pointer_fill_style_circle_outline_diameter"
android:height="@dimen/pointer_fill_style_circle_outline_diameter" />
<stroke
android:width="@dimen/pointer_fill_style_circle_outline_stroke"
android:color="@android:color/white" />
</shape>
</item>
</layer-list>
</item>
<item><!-- default state -->
<layer-list>
<item>
<shape android:shape="rectangle">
<corners android:radius="@dimen/pointer_fill_style_circle_background_corner_radius" />
<size
android:width="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:height="@dimen/pointer_fill_style_circle_background_outer_diameter" />
<solid android:color="@androidprv:color/materialColorSurfaceBright" />
</shape>
</item>
<item
android:id="@+id/tintableCircleDefault"
android:top="@dimen/pointer_fill_style_circle_offset"
android:left="@dimen/pointer_fill_style_circle_offset"
android:bottom="@dimen/pointer_fill_style_circle_offset"
android:right="@dimen/pointer_fill_style_circle_offset">
<shape android:shape="oval">
<size
android:width="@dimen/pointer_fill_style_circle_diameter"
android:height="@dimen/pointer_fill_style_circle_diameter" />
<solid android:color="@android:color/white" />
</shape>
</item>
<item
android:top="@dimen/pointer_fill_style_circle_outline_offset"
android:left="@dimen/pointer_fill_style_circle_outline_offset"
android:bottom="@dimen/pointer_fill_style_circle_outline_offset"
android:right="@dimen/pointer_fill_style_circle_outline_offset">
<shape android:shape="oval">
<size
android:width="@dimen/pointer_fill_style_circle_outline_diameter"
android:height="@dimen/pointer_fill_style_circle_outline_diameter" />
<stroke
android:width="@dimen/pointer_fill_style_circle_outline_stroke"
android:color="@android:color/white" />
</shape>
</item>
</layer-list>
</item>
</selector>

View File

@@ -1,67 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:state_hovered="true">
<layer-list>
<item>
<shape android:shape="oval">
<size android:width="@dimen/pointer_fill_style_circle_hover_selected_diameter"
android:height="@dimen/pointer_fill_style_circle_hover_selected_diameter" />
<stroke android:width="@dimen/pointer_fill_style_shape_hovered_stroke"
android:color="@color/pointer_fill_outline_color" />
</shape>
</item>
<item
android:top="@dimen/pointer_fill_style_checkmark_hovered_padding"
android:left="@dimen/pointer_fill_style_checkmark_hovered_padding"
android:bottom="@dimen/pointer_fill_style_checkmark_hovered_padding"
android:right="@dimen/pointer_fill_style_checkmark_hovered_padding"
android:drawable="@drawable/pointer_fill_check_24dp" />
</layer-list>
</item>
<item android:state_selected="true">
<layer-list>
<item>
<inset android:insetTop="@dimen/pointer_fill_style_circle_selected_offset"
android:insetLeft="@dimen/pointer_fill_style_circle_selected_offset"
android:insetBottom="@dimen/pointer_fill_style_circle_selected_offset"
android:insetRight="@dimen/pointer_fill_style_circle_selected_offset">
<shape android:shape="oval">
<size android:width="@dimen/pointer_fill_style_circle_selected_diameter"
android:height="@dimen/pointer_fill_style_circle_selected_diameter" />
<stroke android:width="@dimen/pointer_fill_style_shape_selected_stroke"
android:color="@color/pointer_fill_outline_color" />
</shape>
</inset>
</item>
<item
android:top="@dimen/pointer_fill_style_checkmark_selected_padding"
android:left="@dimen/pointer_fill_style_checkmark_selected_padding"
android:bottom="@dimen/pointer_fill_style_checkmark_selected_padding"
android:right="@dimen/pointer_fill_style_checkmark_selected_padding"
android:drawable="@drawable/pointer_fill_check_24dp" />
</layer-list>
</item>
<item android:state_hovered="true">
<shape android:shape="oval">
<size android:width="@dimen/pointer_fill_style_circle_hover_diameter"
android:height="@dimen/pointer_fill_style_circle_hover_diameter" />
<stroke android:width="@dimen/pointer_fill_style_shape_hovered_stroke"
android:color="@color/pointer_fill_outline_color" />
</shape>
</item>
</selector>

View File

@@ -53,6 +53,7 @@
android:layout_height="@dimen/chartview_layout_height" android:layout_height="@dimen/chartview_layout_height"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:visibility="gone" android:visibility="gone"
android:alpha="0"
android:contentDescription="@string/hourly_battery_usage_chart" android:contentDescription="@string/hourly_battery_usage_chart"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary" /> settings:textColor="?android:attr/textColorSecondary" />

View File

@@ -42,7 +42,6 @@
android:layout_height="@dimen/pointer_fill_container_height" android:layout_height="@dimen/pointer_fill_container_height"
android:layout_marginBottom="@dimen/pointer_fill_style_circle_padding" android:layout_marginBottom="@dimen/pointer_fill_style_circle_padding"
android:layout_marginTop="@dimen/pointer_fill_style_circle_padding" android:layout_marginTop="@dimen/pointer_fill_style_circle_padding"
android:background="@drawable/pointer_icon_fill_container_background"
android:gravity="center" android:gravity="center"
android:paddingTop="@dimen/pointer_fill_style_container_padding" android:paddingTop="@dimen/pointer_fill_style_container_padding"
android:paddingBottom="@dimen/pointer_fill_style_container_padding"> android:paddingBottom="@dimen/pointer_fill_style_container_padding">
@@ -53,19 +52,18 @@
android:focusable="false" android:focusable="false"
android:clickable="false" android:clickable="false"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:layout_weight="4" /> android:layout_weight="1" />
<ImageView <ImageView
android:id="@+id/button_black" android:id="@+id/button_black"
android:layout_width="@dimen/pointer_fill_style_circle_diameter" android:layout_width="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:layout_weight="0" android:layout_weight="0"
android:layout_height="@dimen/pointer_fill_style_circle_diameter" android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:maxWidth="@dimen/pointer_fill_style_circle_diameter" android:maxWidth="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:contentDescription="@string/pointer_fill_style_black_button" android:contentDescription="@string/pointer_fill_style_black_button"
android:scaleType="center" android:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background" android:src="@drawable/pointer_icon_fill_color_background" />
android:src="@drawable/pointer_icon_fill_color_foreground" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
@@ -73,19 +71,18 @@
android:focusable="false" android:focusable="false"
android:clickable="false" android:clickable="false"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:layout_weight="3" /> android:layout_weight="1" />
<ImageView <ImageView
android:id="@+id/button_green" android:id="@+id/button_green"
android:layout_width="@dimen/pointer_fill_style_circle_diameter" android:layout_width="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:layout_weight="0" android:layout_weight="0"
android:layout_height="@dimen/pointer_fill_style_circle_diameter" android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:maxWidth="@dimen/pointer_fill_style_circle_diameter" android:maxWidth="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:contentDescription="@string/pointer_fill_style_green_button" android:contentDescription="@string/pointer_fill_style_green_button"
android:scaleType="center" android:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background" android:src="@drawable/pointer_icon_fill_color_background" />
android:src="@drawable/pointer_icon_fill_color_foreground" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
@@ -93,19 +90,18 @@
android:focusable="false" android:focusable="false"
android:clickable="false" android:clickable="false"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:layout_weight="3" /> android:layout_weight="1" />
<ImageView <ImageView
android:id="@+id/button_red" android:id="@+id/button_red"
android:layout_width="@dimen/pointer_fill_style_circle_diameter" android:layout_width="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:layout_weight="0" android:layout_weight="0"
android:layout_height="@dimen/pointer_fill_style_circle_diameter" android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:maxWidth="@dimen/pointer_fill_style_circle_diameter" android:maxWidth="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:contentDescription="@string/pointer_fill_style_red_button" android:contentDescription="@string/pointer_fill_style_red_button"
android:scaleType="center" android:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background" android:src="@drawable/pointer_icon_fill_color_background" />
android:src="@drawable/pointer_icon_fill_color_foreground" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
@@ -113,19 +109,18 @@
android:focusable="false" android:focusable="false"
android:clickable="false" android:clickable="false"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:layout_weight="3" /> android:layout_weight="1" />
<ImageView <ImageView
android:id="@+id/button_pink" android:id="@+id/button_pink"
android:layout_width="@dimen/pointer_fill_style_circle_diameter" android:layout_width="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:layout_weight="0" android:layout_weight="0"
android:layout_height="@dimen/pointer_fill_style_circle_diameter" android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:maxWidth="@dimen/pointer_fill_style_circle_diameter" android:maxWidth="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:contentDescription="@string/pointer_fill_style_pink_button" android:contentDescription="@string/pointer_fill_style_pink_button"
android:scaleType="center" android:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background" android:src="@drawable/pointer_icon_fill_color_background" />
android:src="@drawable/pointer_icon_fill_color_foreground" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
@@ -133,19 +128,18 @@
android:focusable="false" android:focusable="false"
android:clickable="false" android:clickable="false"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:layout_weight="3" /> android:layout_weight="1" />
<ImageView <ImageView
android:id="@+id/button_blue" android:id="@+id/button_blue"
android:layout_width="@dimen/pointer_fill_style_circle_diameter" android:layout_width="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:layout_weight="0" android:layout_weight="0"
android:layout_height="@dimen/pointer_fill_style_circle_diameter" android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:maxWidth="@dimen/pointer_fill_style_circle_diameter" android:maxWidth="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:contentDescription="@string/pointer_fill_style_blue_button" android:contentDescription="@string/pointer_fill_style_blue_button"
android:scaleType="center" android:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background" android:src="@drawable/pointer_icon_fill_color_background" />
android:src="@drawable/pointer_icon_fill_color_foreground" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
@@ -153,19 +147,18 @@
android:focusable="false" android:focusable="false"
android:clickable="false" android:clickable="false"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:layout_weight="3" /> android:layout_weight="1" />
<ImageView <ImageView
android:id="@+id/button_purple" android:id="@+id/button_purple"
android:layout_width="@dimen/pointer_fill_style_circle_diameter" android:layout_width="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:layout_weight="0" android:layout_weight="0"
android:layout_height="@dimen/pointer_fill_style_circle_diameter" android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:maxWidth="@dimen/pointer_fill_style_circle_diameter" android:maxWidth="@dimen/pointer_fill_style_circle_background_outer_diameter"
android:contentDescription="@string/pointer_fill_style_purple_button" android:contentDescription="@string/pointer_fill_style_purple_button"
android:scaleType="center" android:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background" android:src="@drawable/pointer_icon_fill_color_background" />
android:src="@drawable/pointer_icon_fill_color_foreground" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
@@ -173,7 +166,7 @@
android:focusable="false" android:focusable="false"
android:clickable="false" android:clickable="false"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:layout_weight="4" /> android:layout_weight="1" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -1,86 +0,0 @@
<?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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/suw_lift"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/sud_layout_icon"
style="@style/SudGlifIcon"
android:layout_marginStart="48dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginTop="68dp"
android:scaleType="fitStart"
android:src="@drawable/ic_lock" />
<TextView
style="@style/SudGlifHeaderTitle"
android:id="@+id/suc_layout_title"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="138dp"
android:layout_marginStart="40dp"
android:layout_marginEnd="24dp"/>
<TextView
style="@style/SudDescription.Glif"
android:id="@+id/sud_layout_subtitle"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="206dp"
android:layout_marginStart="40dp"
android:layout_marginEnd="24dp"/>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/illustration_lottie"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_marginTop="206dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="17dp"
android:scaleType="centerInside"
android:visibility="gone"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_speed=".85" />
<include layout="@layout/udfps_enroll_enrolling_v2_udfps_view"/>
<Button
style="@style/SudGlifButton.Secondary"
android:id="@+id/skip_btn"
android:text="@string/security_settings_fingerprint_enroll_enrolling_skip"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:layout_marginBottom="26dp"
android:layout_marginStart="66dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>

View File

@@ -1,36 +0,0 @@
<?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.
-->
<com.android.settings.biometrics2.ui.widget.UdfpsEnrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/udfps_animation_view"
android:layout_width="218.42dp"
android:layout_height="216dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="553dp">
<ImageView
android:id="@+id/udfps_enroll_animation_fp_progress_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Fingerprint -->
<ImageView
android:id="@+id/udfps_enroll_animation_fp_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.settings.biometrics2.ui.widget.UdfpsEnrollView>

View File

@@ -77,8 +77,8 @@
<!-- Switch bar disabled state color--> <!-- Switch bar disabled state color-->
<color name="switch_bar_state_disabled_color">#1FE3E3E3</color> <color name="switch_bar_state_disabled_color">#1FE3E3E3</color>
<!-- Pointer fill color setting outline color--> <!-- Pointer fill color setting, fill selector's background color on hover. -->
<color name="pointer_fill_outline_color">#FFFFFF</color> <color name="pointer_fill_hovered_color">#3E373C</color>
<!-- Connected displays --> <!-- Connected displays -->
<color name="display_topology_background_color">@color/settingslib_color_charcoal</color> <color name="display_topology_background_color">@color/settingslib_color_charcoal</color>

View File

@@ -219,8 +219,8 @@
<!-- Switch bar disabled state color--> <!-- Switch bar disabled state color-->
<color name="switch_bar_state_disabled_color">#1F1F1F1F</color> <color name="switch_bar_state_disabled_color">#1F1F1F1F</color>
<!-- Pointer fill color setting outline color--> <!-- Pointer fill color setting, fill selector's background color on hover. -->
<color name="pointer_fill_outline_color">#000000</color> <color name="pointer_fill_hovered_color">#1C201A1E</color>
<!-- Connected displays --> <!-- Connected displays -->
<color name="display_topology_background_color">@color/settingslib_color_grey100</color> <color name="display_topology_background_color">@color/settingslib_color_grey100</color>

View File

@@ -180,21 +180,28 @@
<dimen name="keyboard_picker_text_size">16sp</dimen> <dimen name="keyboard_picker_text_size">16sp</dimen>
<!-- Pointer --> <!-- Pointer -->
<dimen name="pointer_fill_container_height">88dp</dimen> <dimen name="pointer_fill_container_height">80dp</dimen>
<dimen name="pointer_fill_container_max_width">448dp</dimen> <dimen name="pointer_fill_container_max_width">468dp</dimen>
<dimen name="pointer_fill_style_container_padding">32dp</dimen> <dimen name="pointer_fill_style_container_padding">32dp</dimen>
<dimen name="pointer_fill_style_circle_diameter">56dp</dimen> <dimen name="pointer_fill_style_target_width">64dp</dimen>
<dimen name="pointer_fill_style_circle_hover_selected_diameter">52dp</dimen> <dimen name="pointer_fill_style_circle_diameter">39dp</dimen>
<dimen name="pointer_fill_style_circle_hover_diameter">56dp</dimen> <dimen name="pointer_fill_style_circle_outline_diameter">39.5dp</dimen>
<dimen name="pointer_fill_style_circle_selected_diameter">50dp</dimen> <dimen name="pointer_fill_style_circle_background_corner_radius">20dp</dimen>
<dimen name="pointer_fill_style_circle_inner_diameter">40dp</dimen> <dimen name="pointer_fill_style_circle_background_corner_inner_radius">16dp</dimen>
<dimen name="pointer_fill_style_circle_offset">8dp</dimen> <dimen name="pointer_fill_style_circle_background_outer_diameter">64dp</dimen>
<dimen name="pointer_fill_style_circle_background_outer_diameter_less_stroke">62dp</dimen>
<dimen name="pointer_fill_style_circle_background_outer_diameter_selected">52dp</dimen>
<dimen name="pointer_fill_style_circle_background_outline_offset">1dp</dimen>
<dimen name="pointer_fill_style_circle_offset">12.5dp</dimen>
<dimen name="pointer_fill_style_circle_offset_selected">13.25dp</dimen>
<dimen name="pointer_fill_style_circle_outline_offset">12.25dp</dimen>
<dimen name="pointer_fill_style_circle_outline_offset_selected">13dp</dimen>
<dimen name="pointer_fill_style_circle_background_offset">7dp</dimen>
<dimen name="pointer_fill_style_circle_outline_stroke">1dp</dimen>
<dimen name="pointer_fill_style_circle_background_selected_outline_stroke">2dp</dimen>
<dimen name="pointer_fill_style_circle_padding">16dp</dimen> <dimen name="pointer_fill_style_circle_padding">16dp</dimen>
<dimen name="pointer_fill_style_circle_selected_offset">3dp</dimen> <dimen name="pointer_fill_style_checkmark_selected_padding">16dp</dimen>
<dimen name="pointer_fill_style_checkmark_selected_padding">14dp</dimen> <dimen name="pointer_fill_style_checkmark_size">24dp</dimen>
<dimen name="pointer_fill_style_checkmark_hovered_padding">17dp</dimen>
<dimen name="pointer_fill_style_shape_selected_stroke">2dp</dimen>
<dimen name="pointer_fill_style_shape_hovered_stroke">4dp</dimen>
<dimen name="pointer_stroke_style_padding">8dp</dimen> <dimen name="pointer_stroke_style_padding">8dp</dimen>
<dimen name="pointer_stroke_style_text_padding">21dp</dimen> <dimen name="pointer_stroke_style_text_padding">21dp</dimen>
<dimen name="pointer_scale_padding">8dp</dimen> <dimen name="pointer_scale_padding">8dp</dimen>

View File

@@ -46,4 +46,7 @@
<!-- For screen lock options button --> <!-- For screen lock options button -->
<item type="id" name="screen_lock_options" /> <item type="id" name="screen_lock_options" />
<!-- For some widgets still use this ids under com.android.settings.R-->
<item type="id" name="sud_layout_icon" />
</resources> </resources>

View File

@@ -5415,8 +5415,10 @@
<string name="accessibility_toggle_high_text_contrast_preference_title">High contrast text</string> <string name="accessibility_toggle_high_text_contrast_preference_title">High contrast text</string>
<!-- Summary for the accessibility preference to high contrast text. [CHAR LIMIT=NONE] --> <!-- Summary for the accessibility preference to high contrast text. [CHAR LIMIT=NONE] -->
<string name="accessibility_toggle_high_text_contrast_preference_summary">Change text color to black or white. Maximizes contrast with the background.</string> <string name="accessibility_toggle_high_text_contrast_preference_summary">Change text color to black or white. Maximizes contrast with the background.</string>
<!-- Content for the notification to high contrast text. [CHAR LIMIT=NONE] --> <!-- Title for the notification that high contrast text has been replaced. [CHAR LIMIT=NONE] -->
<string name="accessibility_notification_high_contrast_text_content">High contrast text has a new look and feel.</string> <string name="accessibility_notification_high_contrast_text_title">High contrast text has been replaced</string>
<!-- Text content for the notification that high contrast text has been replaced. [CHAR LIMIT=NONE] -->
<string name="accessibility_notification_high_contrast_text_content">Try Maximize text contrast instead. Find it in Settings.</string>
<!-- Action for the notification to high contrast text. [CHAR LIMIT=35] --> <!-- Action for the notification to high contrast text. [CHAR LIMIT=35] -->
<string name="accessibility_notification_high_contrast_text_action">Open Settings</string> <string name="accessibility_notification_high_contrast_text_action">Open Settings</string>
<!-- Title for the accessibility preference to high contrast text. [CHAR LIMIT=35] --> <!-- Title for the accessibility preference to high contrast text. [CHAR LIMIT=35] -->
@@ -5669,7 +5671,9 @@
<!-- Title for accessibility hearing device page footer. [CHAR LIMIT=40] --> <!-- Title for accessibility hearing device page footer. [CHAR LIMIT=40] -->
<string name="accessibility_hearing_device_about_title">About hearing devices</string> <string name="accessibility_hearing_device_about_title">About hearing devices</string>
<!-- Description for text in accessibility hearing aids footer. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=7451899224828040581] --> <!-- Description for text in accessibility hearing aids footer. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=7451899224828040581] -->
<string name="accessibility_hearing_device_footer_summary">To find other hearing devices that arent supported by ASHA or LE Audio, tap <b>Pair new device</b> > <b>See more devices</b></string> <string name="accessibility_hearing_device_footer_summary"><![CDATA[To find other hearing devices that arent supported by ASHA or LE Audio, tap <b>Pair new device</b> > <b>See more devices</b>]]></string>
<!-- Content description for text in accessibility hearing aids footer to be announce. Replace '>' to 'then' compare to non tts version. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=7451899224828040581] -->
<string name="accessibility_hearing_device_footer_summary_tts">To find other hearing devices that arent supported by ASHA or LE Audio, tap <b>Pair new device</b> then <b>See more devices</b></string>
<!-- Title for the pair hearing device page. [CHAR LIMIT=25] --> <!-- Title for the pair hearing device page. [CHAR LIMIT=25] -->
<string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string> <string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string>
<!-- Subtitle for the pair hearing device page. [CHAR LIMIT=NONE] --> <!-- Subtitle for the pair hearing device page. [CHAR LIMIT=NONE] -->
@@ -10663,6 +10667,11 @@
<string name="admin_financed_message">Your device administrator may be able to access data <string name="admin_financed_message">Your device administrator may be able to access data
associated with this device, manage apps, and change this device\s settings.</string> associated with this device, manage apps, and change this device\s settings.</string>
<!-- Title for dialog displayed when user taps a setting on their phone that's blocked by Advanced Protection. [CHAR LIMIT=50] -->
<string name="disabled_by_advanced_protection_title">Prevented by Advanced Protection</string>
<!-- Short summary for dialog displayed when user taps a setting on their phone that's blocked by Advanced Protection. [CHAR LIMIT=NONE] -->
<string name="disabled_by_advanced_protection_message">This action is not allowed because Advanced Protection is on for your device.</string>
<!-- Turn off a conditional state of the device (e.g. airplane mode, or hotspot) [CHAR LIMIT=30] --> <!-- Turn off a conditional state of the device (e.g. airplane mode, or hotspot) [CHAR LIMIT=30] -->
<string name="condition_turn_off">Turn off</string> <string name="condition_turn_off">Turn off</string>

View File

@@ -33,8 +33,8 @@
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="toggle_high_text_contrast_preference" android:key="toggle_high_text_contrast_preference"
android:persistent="false" android:persistent="false"
android:summary="@string/accessibility_toggle_high_text_contrast_preference_summary" android:title="@string/accessibility_toggle_maximize_text_contrast_preference_title"
android:title="@string/accessibility_toggle_high_text_contrast_preference_title" android:summary="@string/accessibility_toggle_maximize_text_contrast_preference_summary"
settings:controller= settings:controller=
"com.android.settings.accessibility.HighTextContrastPreferenceController" /> "com.android.settings.accessibility.HighTextContrastPreferenceController" />

View File

@@ -65,7 +65,6 @@
<com.android.settings.accessibility.AccessibilityFooterPreference <com.android.settings.accessibility.AccessibilityFooterPreference
android:key="hearing_device_footer" android:key="hearing_device_footer"
android:title="@string/accessibility_hearing_device_footer_summary"
android:selectable="false" android:selectable="false"
settings:searchable="false" settings:searchable="false"
settings:controller="com.android.settings.accessibility.HearingDeviceFooterPreferenceController"/> settings:controller="com.android.settings.accessibility.HearingDeviceFooterPreferenceController"/>

View File

@@ -56,8 +56,8 @@
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="toggle_high_text_contrast_preference" android:key="toggle_high_text_contrast_preference"
android:persistent="false" android:persistent="false"
android:summary="@string/accessibility_toggle_high_text_contrast_preference_summary" android:title="@string/accessibility_toggle_maximize_text_contrast_preference_title"
android:title="@string/accessibility_toggle_high_text_contrast_preference_title" /> android:summary="@string/accessibility_toggle_maximize_text_contrast_preference_summary" />
<com.android.settings.accessibility.TextReadingResetPreference <com.android.settings.accessibility.TextReadingResetPreference
android:key="reset" android:key="reset"

View File

@@ -17,6 +17,10 @@
package com.android.settings.accessibility; package com.android.settings.accessibility;
import android.content.Context; import android.content.Context;
import android.text.Html;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settings.R;
@@ -32,4 +36,24 @@ public class HearingDeviceFooterPreferenceController extends
protected String getIntroductionTitle() { protected String getIntroductionTitle() {
return mContext.getString(R.string.accessibility_hearing_device_about_title); return mContext.getString(R.string.accessibility_hearing_device_about_title);
} }
@Override
public void displayPreference(@NonNull PreferenceScreen screen) {
super.displayPreference(screen);
final AccessibilityFooterPreference footerPreference =
screen.findPreference(getPreferenceKey());
// We use html tag inside footer string, so it is better to load from html to have better
// html tag support.
final CharSequence title = Html.fromHtml(
mContext.getString(R.string.accessibility_hearing_device_footer_summary),
Html.FROM_HTML_MODE_COMPACT, /* imageGetter= */ null, /* tagHandler= */ null);
footerPreference.setTitle(title);
// Need to update contentDescription string to announce "than" rather than ">"
final String summaryTts = mContext.getString(
R.string.accessibility_hearing_device_footer_summary_tts);
final String contentDescription = getIntroductionTitle() + "\n\n" + summaryTts;
footerPreference.setContentDescription(contentDescription);
}
} }

View File

@@ -49,7 +49,7 @@ import java.lang.annotation.RetentionPolicy;
public class HighContrastTextMigrationReceiver extends BroadcastReceiver { public class HighContrastTextMigrationReceiver extends BroadcastReceiver {
private static final String TAG = HighContrastTextMigrationReceiver.class.getSimpleName(); private static final String TAG = HighContrastTextMigrationReceiver.class.getSimpleName();
@VisibleForTesting @VisibleForTesting
static final String NOTIFICATION_CHANNEL = "high_contrast_text_notification_channel"; static final String NOTIFICATION_CHANNEL = "accessibility_notification_channel";
@VisibleForTesting @VisibleForTesting
static final String ACTION_RESTORED = static final String ACTION_RESTORED =
"com.android.settings.accessibility.ACTION_HIGH_CONTRAST_TEXT_RESTORED"; "com.android.settings.accessibility.ACTION_HIGH_CONTRAST_TEXT_RESTORED";
@@ -120,7 +120,7 @@ public class HighContrastTextMigrationReceiver extends BroadcastReceiver {
NOTIFICATION_CHANNEL) NOTIFICATION_CHANNEL)
.setSmallIcon(R.drawable.ic_settings_24dp) .setSmallIcon(R.drawable.ic_settings_24dp)
.setContentTitle(context.getString( .setContentTitle(context.getString(
R.string.accessibility_toggle_high_text_contrast_preference_title)) R.string.accessibility_notification_high_contrast_text_title))
.setContentText(context.getString( .setContentText(context.getString(
R.string.accessibility_notification_high_contrast_text_content)) R.string.accessibility_notification_high_contrast_text_content))
.setAutoCancel(true); .setAutoCancel(true);
@@ -149,8 +149,7 @@ public class HighContrastTextMigrationReceiver extends BroadcastReceiver {
context.getSystemService(NotificationManager.class); context.getSystemService(NotificationManager.class);
NotificationChannel notificationChannel = new NotificationChannel( NotificationChannel notificationChannel = new NotificationChannel(
NOTIFICATION_CHANNEL, NOTIFICATION_CHANNEL,
context.getString( context.getString(R.string.accessibility_settings),
R.string.accessibility_toggle_high_text_contrast_preference_title),
NotificationManager.IMPORTANCE_LOW); NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(notificationChannel); notificationManager.createNotificationChannel(notificationChannel);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());

View File

@@ -21,25 +21,29 @@ import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME_SLIDER;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT; import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT; import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
import android.bluetooth.BluetoothDevice;
import android.content.Context; import android.content.Context;
import android.util.ArrayMap;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.widget.SeekBarPreference; import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.bluetooth.AmbientVolumeUi;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@@ -49,27 +53,13 @@ import java.util.Map;
* separated control for devices in the same set. Toggle the expand icon will make the UI switch * separated control for devices in the same set. Toggle the expand icon will make the UI switch
* between unified and separated control. * between unified and separated control.
*/ */
public class AmbientVolumePreference extends PreferenceGroup { public class AmbientVolumePreference extends PreferenceGroup implements AmbientVolumeUi {
/** Interface definition for a callback to be invoked when the icon is clicked. */ private static final int ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED = 0;
public interface OnIconClickListener { private static final int ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED = 1;
/** Called when the expand icon is clicked. */
void onExpandIconClick();
/** Called when the ambient volume icon is clicked. */
void onAmbientVolumeIconClick();
};
static final float ROTATION_COLLAPSED = 0f;
static final float ROTATION_EXPANDED = 180f;
static final int AMBIENT_VOLUME_LEVEL_MIN = 0;
static final int AMBIENT_VOLUME_LEVEL_MAX = 24;
static final int AMBIENT_VOLUME_LEVEL_DEFAULT = 24;
static final int SIDE_UNIFIED = 999;
static final List<Integer> VALID_SIDES = List.of(SIDE_UNIFIED, SIDE_LEFT, SIDE_RIGHT);
@Nullable @Nullable
private OnIconClickListener mListener; private AmbientVolumeUiListener mListener;
@Nullable @Nullable
private View mExpandIcon; private View mExpandIcon;
@Nullable @Nullable
@@ -78,27 +68,21 @@ public class AmbientVolumePreference extends PreferenceGroup {
private boolean mExpanded = false; private boolean mExpanded = false;
private boolean mMutable = false; private boolean mMutable = false;
private boolean mMuted = false; private boolean mMuted = false;
private Map<Integer, SeekBarPreference> mSideToSliderMap = new ArrayMap<>(); private final BiMap<Integer, SeekBarPreference> mSideToSliderMap = HashBiMap.create();
/**
* Ambient volume level for hearing device ambient control icon
* <p>
* This icon visually represents the current ambient gain setting.
* It displays separate levels for the left and right sides, each with 5 levels ranging from 0
* to 4.
* <p>
* To represent the combined left/right levels with a single value, the following calculation
* is used:
* finalLevel = (leftLevel * 5) + rightLevel
* For example:
* <ul>
* <li>If left level is 2 and right level is 3, the final level will be 13 (2 * 5 + 3)</li>
* <li>If both left and right levels are 0, the final level will be 0</li>
* <li>If both left and right levels are 4, the final level will be 24</li>
* </ul>
*/
private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT; private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
private final OnPreferenceChangeListener mPreferenceChangeListener =
(slider, v) -> {
if (slider instanceof SeekBarPreference && v instanceof final Integer value) {
final Integer side = mSideToSliderMap.inverse().get(slider);
if (mListener != null && side != null) {
mListener.onSliderValueChange(side, value);
}
return true;
}
return false;
};
public AmbientVolumePreference(@NonNull Context context) { public AmbientVolumePreference(@NonNull Context context) {
super(context, null); super(context, null);
setLayoutResource(R.layout.preference_ambient_volume); setLayoutResource(R.layout.preference_ambient_volume);
@@ -138,7 +122,8 @@ public class AmbientVolumePreference extends PreferenceGroup {
updateExpandIcon(); updateExpandIcon();
} }
void setExpandable(boolean expandable) { @Override
public void setExpandable(boolean expandable) {
mExpandable = expandable; mExpandable = expandable;
if (!mExpandable) { if (!mExpandable) {
setExpanded(false); setExpanded(false);
@@ -146,11 +131,13 @@ public class AmbientVolumePreference extends PreferenceGroup {
updateExpandIcon(); updateExpandIcon();
} }
boolean isExpandable() { @Override
public boolean isExpandable() {
return mExpandable; return mExpandable;
} }
void setExpanded(boolean expanded) { @Override
public void setExpanded(boolean expanded) {
if (!mExpandable && expanded) { if (!mExpandable && expanded) {
return; return;
} }
@@ -159,11 +146,13 @@ public class AmbientVolumePreference extends PreferenceGroup {
updateLayout(); updateLayout();
} }
boolean isExpanded() { @Override
public boolean isExpanded() {
return mExpanded; return mExpanded;
} }
void setMutable(boolean mutable) { @Override
public void setMutable(boolean mutable) {
mMutable = mutable; mMutable = mutable;
if (!mMutable) { if (!mMutable) {
mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT; mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
@@ -172,11 +161,13 @@ public class AmbientVolumePreference extends PreferenceGroup {
updateVolumeIcon(); updateVolumeIcon();
} }
boolean isMutable() { @Override
public boolean isMutable() {
return mMutable; return mMutable;
} }
void setMuted(boolean muted) { @Override
public void setMuted(boolean muted) {
if (!mMutable && muted) { if (!mMutable && muted) {
return; return;
} }
@@ -189,25 +180,35 @@ public class AmbientVolumePreference extends PreferenceGroup {
updateVolumeIcon(); updateVolumeIcon();
} }
boolean isMuted() { @Override
public boolean isMuted() {
return mMuted; return mMuted;
} }
void setOnIconClickListener(@Nullable OnIconClickListener listener) { @Override
public void setListener(@Nullable AmbientVolumeUiListener listener) {
mListener = listener; mListener = listener;
} }
void setSliders(Map<Integer, SeekBarPreference> sideToSliderMap) { @Override
mSideToSliderMap = sideToSliderMap; public void setupSliders(@NonNull Map<Integer, BluetoothDevice> sideToDeviceMap) {
for (SeekBarPreference preference : sideToSliderMap.values()) { sideToDeviceMap.forEach((side, device) ->
if (findPreference(preference.getKey()) == null) { createSlider(side, ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED + side));
addPreference(preference); createSlider(SIDE_UNIFIED, ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED);
if (!mSideToSliderMap.isEmpty()) {
for (int side : VALID_SIDES) {
final SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null && findPreference(slider.getKey()) == null) {
addPreference(slider);
}
} }
} }
updateLayout(); updateLayout();
} }
void setSliderEnabled(int side, boolean enabled) { @Override
public void setSliderEnabled(int side, boolean enabled) {
SeekBarPreference slider = mSideToSliderMap.get(side); SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null && slider.isEnabled() != enabled) { if (slider != null && slider.isEnabled() != enabled) {
slider.setEnabled(enabled); slider.setEnabled(enabled);
@@ -215,7 +216,8 @@ public class AmbientVolumePreference extends PreferenceGroup {
} }
} }
void setSliderValue(int side, int value) { @Override
public void setSliderValue(int side, int value) {
SeekBarPreference slider = mSideToSliderMap.get(side); SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null && slider.getProgress() != value) { if (slider != null && slider.getProgress() != value) {
slider.setProgress(value); slider.setProgress(value);
@@ -223,7 +225,8 @@ public class AmbientVolumePreference extends PreferenceGroup {
} }
} }
void setSliderRange(int side, int min, int max) { @Override
public void setSliderRange(int side, int min, int max) {
SeekBarPreference slider = mSideToSliderMap.get(side); SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null) { if (slider != null) {
slider.setMin(min); slider.setMin(min);
@@ -231,7 +234,8 @@ public class AmbientVolumePreference extends PreferenceGroup {
} }
} }
void updateLayout() { @Override
public void updateLayout() {
mSideToSliderMap.forEach((side, slider) -> { mSideToSliderMap.forEach((side, slider) -> {
if (side == SIDE_UNIFIED) { if (side == SIDE_UNIFIED) {
slider.setVisible(!mExpanded); slider.setVisible(!mExpanded);
@@ -279,8 +283,7 @@ public class AmbientVolumePreference extends PreferenceGroup {
mExpandIcon.setVisibility(mExpandable ? VISIBLE : GONE); mExpandIcon.setVisibility(mExpandable ? VISIBLE : GONE);
mExpandIcon.setRotation(mExpanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED); mExpandIcon.setRotation(mExpanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED);
if (mExpandable) { if (mExpandable) {
final int stringRes = mExpanded final int stringRes = mExpanded ? R.string.bluetooth_ambient_volume_control_collapse
? R.string.bluetooth_ambient_volume_control_collapse
: R.string.bluetooth_ambient_volume_control_expand; : R.string.bluetooth_ambient_volume_control_expand;
mExpandIcon.setContentDescription(getContext().getString(stringRes)); mExpandIcon.setContentDescription(getContext().getString(stringRes));
} else { } else {
@@ -294,8 +297,7 @@ public class AmbientVolumePreference extends PreferenceGroup {
} }
mVolumeIcon.setImageLevel(mMuted ? 0 : mVolumeLevel); mVolumeIcon.setImageLevel(mMuted ? 0 : mVolumeLevel);
if (mMutable) { if (mMutable) {
final int stringRes = mMuted final int stringRes = mMuted ? R.string.bluetooth_ambient_volume_unmute
? R.string.bluetooth_ambient_volume_unmute
: R.string.bluetooth_ambient_volume_mute; : R.string.bluetooth_ambient_volume_mute;
mVolumeIcon.setContentDescription(getContext().getString(stringRes)); mVolumeIcon.setContentDescription(getContext().getString(stringRes));
mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -304,4 +306,27 @@ public class AmbientVolumePreference extends PreferenceGroup {
mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
} }
} }
private void createSlider(int side, int order) {
if (mSideToSliderMap.containsKey(side)) {
return;
}
SeekBarPreference slider = new SeekBarPreference(getContext());
slider.setKey(KEY_AMBIENT_VOLUME_SLIDER + "_" + side);
slider.setOrder(order);
slider.setOnPreferenceChangeListener(mPreferenceChangeListener);
if (side == SIDE_LEFT) {
slider.setTitle(
getContext().getString(R.string.bluetooth_ambient_volume_control_left));
} else if (side == SIDE_RIGHT) {
slider.setTitle(
getContext().getString(R.string.bluetooth_ambient_volume_control_right));
}
mSideToSliderMap.put(side, slider);
}
@VisibleForTesting
Map<Integer, SeekBarPreference> getSliders() {
return mSideToSliderMap;
}
} }

View File

@@ -16,41 +16,20 @@
package com.android.settings.bluetooth; package com.android.settings.bluetooth;
import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
import static android.bluetooth.AudioInputControl.MUTE_MUTED;
import static android.bluetooth.BluetoothDevice.BOND_BONDED;
import static com.android.settings.bluetooth.AmbientVolumePreference.SIDE_UNIFIED;
import static com.android.settings.bluetooth.AmbientVolumePreference.VALID_SIDES;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP; import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_AMBIENT_VOLUME; import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_AMBIENT_VOLUME;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_INVALID;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context; import android.content.Context;
import android.util.ArraySet;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settingslib.bluetooth.AmbientVolumeUiController;
import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.bluetooth.AmbientVolumeController;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager;
import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.VolumeControlProfile; import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -58,39 +37,21 @@ import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop; import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.utils.ThreadUtils;
import com.google.common.collect.BiMap; /** A {@link BluetoothDetailsController} that manages ambient volume preference. */
import com.google.common.collect.HashBiMap; public class BluetoothDetailsAmbientVolumePreferenceController extends BluetoothDetailsController
implements OnStart, OnStop {
import java.util.Map;
import java.util.Set;
/** A {@link BluetoothDetailsController} that manages ambient volume control preferences. */
public class BluetoothDetailsAmbientVolumePreferenceController extends
BluetoothDetailsController implements Preference.OnPreferenceChangeListener,
HearingDeviceLocalDataManager.OnDeviceLocalDataChangeListener, OnStart, OnStop,
AmbientVolumeController.AmbientVolumeControlCallback, BluetoothCallback {
private static final boolean DEBUG = true; private static final boolean DEBUG = true;
private static final String TAG = "AmbientPrefController"; private static final String TAG = "AmbientPrefController";
static final String KEY_AMBIENT_VOLUME = "ambient_volume"; static final String KEY_AMBIENT_VOLUME = "ambient_volume";
static final String KEY_AMBIENT_VOLUME_SLIDER = "ambient_volume_slider"; static final String KEY_AMBIENT_VOLUME_SLIDER = "ambient_volume_slider";
private static final int ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED = 0;
private static final int ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED = 1;
private final LocalBluetoothManager mBluetoothManager; private final LocalBluetoothManager mBluetoothManager;
private final Set<CachedBluetoothDevice> mCachedDevices = new ArraySet<>();
private final BiMap<Integer, BluetoothDevice> mSideToDeviceMap = HashBiMap.create();
private final BiMap<Integer, SeekBarPreference> mSideToSliderMap = HashBiMap.create();
private final HearingDeviceLocalDataManager mLocalDataManager;
private final AmbientVolumeController mVolumeController;
@Nullable
private PreferenceCategory mDeviceControls;
@Nullable @Nullable
private AmbientVolumePreference mPreference; private AmbientVolumePreference mPreference;
@Nullable @Nullable
private Toast mToast; private AmbientVolumeUiController mAmbientUiController;
public BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context, public BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
@NonNull LocalBluetoothManager manager, @NonNull LocalBluetoothManager manager,
@@ -99,45 +60,42 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
@NonNull Lifecycle lifecycle) { @NonNull Lifecycle lifecycle) {
super(context, fragment, device, lifecycle); super(context, fragment, device, lifecycle);
mBluetoothManager = manager; mBluetoothManager = manager;
mLocalDataManager = new HearingDeviceLocalDataManager(context);
mLocalDataManager.setOnDeviceLocalDataChangeListener(this,
ThreadUtils.getBackgroundExecutor());
mVolumeController = new AmbientVolumeController(manager.getProfileManager(), this);
} }
@VisibleForTesting @VisibleForTesting
BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context, public BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
@NonNull LocalBluetoothManager manager, @NonNull LocalBluetoothManager manager,
@NonNull PreferenceFragmentCompat fragment, @NonNull PreferenceFragmentCompat fragment,
@NonNull CachedBluetoothDevice device, @NonNull CachedBluetoothDevice device,
@NonNull Lifecycle lifecycle, @NonNull Lifecycle lifecycle,
@NonNull HearingDeviceLocalDataManager localSettings, @NonNull AmbientVolumeUiController uiController) {
@NonNull AmbientVolumeController volumeController) {
super(context, fragment, device, lifecycle); super(context, fragment, device, lifecycle);
mBluetoothManager = manager; mBluetoothManager = manager;
mLocalDataManager = localSettings; mAmbientUiController = uiController;
mVolumeController = volumeController;
} }
@Override @Override
protected void init(PreferenceScreen screen) { protected void init(PreferenceScreen screen) {
mDeviceControls = screen.findPreference(KEY_HEARING_DEVICE_GROUP); PreferenceCategory deviceControls = screen.findPreference(KEY_HEARING_DEVICE_GROUP);
if (mDeviceControls == null) { if (deviceControls == null) {
return; return;
} }
loadDevices(); mPreference = new AmbientVolumePreference(deviceControls.getContext());
mPreference.setKey(KEY_AMBIENT_VOLUME);
mPreference.setOrder(ORDER_AMBIENT_VOLUME);
deviceControls.addPreference(mPreference);
mAmbientUiController = new AmbientVolumeUiController(mContext, mBluetoothManager,
mPreference);
mAmbientUiController.loadDevice(mCachedDevice);
} }
@Override @Override
public void onStart() { public void onStart() {
ThreadUtils.postOnBackgroundThread(() -> { ThreadUtils.postOnBackgroundThread(() -> {
mBluetoothManager.getEventManager().registerCallback(this); if (mAmbientUiController != null) {
mLocalDataManager.start(); mAmbientUiController.start();
mCachedDevices.forEach(device -> { }
device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
device.getDevice());
});
}); });
} }
@@ -153,12 +111,9 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
@Override @Override
public void onStop() { public void onStop() {
ThreadUtils.postOnBackgroundThread(() -> { ThreadUtils.postOnBackgroundThread(() -> {
mBluetoothManager.getEventManager().unregisterCallback(this); if (mAmbientUiController != null) {
mLocalDataManager.stop(); mAmbientUiController.stop();
mCachedDevices.forEach(device -> { }
device.unregisterCallback(this);
mVolumeController.unregisterCallback(device.getDevice());
});
}); });
} }
@@ -167,16 +122,8 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
if (!isAvailable()) { if (!isAvailable()) {
return; return;
} }
boolean shouldShowAmbientControl = isAmbientControlAvailable(); if (mAmbientUiController != null) {
if (shouldShowAmbientControl) { mAmbientUiController.refresh();
if (mPreference != null) {
mPreference.setVisible(true);
}
loadRemoteDataToUi();
} else {
if (mPreference != null) {
mPreference.setVisible(false);
}
} }
} }
@@ -191,424 +138,4 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
public String getPreferenceKey() { public String getPreferenceKey() {
return KEY_AMBIENT_VOLUME; return KEY_AMBIENT_VOLUME;
} }
@Override
public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) {
if (preference instanceof SeekBarPreference && newValue instanceof final Integer value) {
final int side = mSideToSliderMap.inverse().getOrDefault(preference, SIDE_INVALID);
if (DEBUG) {
Log.d(TAG, "onPreferenceChange: side=" + side + ", value=" + value);
}
setVolumeIfValid(side, value);
Runnable setAmbientRunnable = () -> {
if (side == SIDE_UNIFIED) {
mSideToDeviceMap.forEach((s, d) -> mVolumeController.setAmbient(d, value));
} else {
final BluetoothDevice device = mSideToDeviceMap.get(side);
mVolumeController.setAmbient(device, value);
}
};
if (isControlMuted()) {
// User drag on the volume slider when muted. Unmute the devices first.
if (mPreference != null) {
mPreference.setMuted(false);
}
for (BluetoothDevice device : mSideToDeviceMap.values()) {
mVolumeController.setMuted(device, false);
}
// Restore the value before muted
loadLocalDataToUi();
// Delay set ambient on remote device since the immediately sequential command
// might get failed sometimes
mContext.getMainThreadHandler().postDelayed(setAmbientRunnable, 1000L);
} else {
setAmbientRunnable.run();
}
return true;
}
return false;
}
@Override
public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
if (bluetoothProfile == BluetoothProfile.VOLUME_CONTROL
&& state == BluetoothProfile.STATE_CONNECTED
&& mCachedDevices.contains(cachedDevice)) {
// After VCP connected, AICS may not ready yet and still return invalid value, delay
// a while to wait AICS ready as a workaround
mContext.getMainThreadHandler().postDelayed(this::refresh, 1000L);
}
}
@Override
public void onDeviceAttributesChanged() {
mCachedDevices.forEach(device -> {
device.unregisterCallback(this);
mVolumeController.unregisterCallback(device.getDevice());
});
mContext.getMainExecutor().execute(() -> {
loadDevices();
if (!mCachedDevices.isEmpty()) {
refresh();
}
ThreadUtils.postOnBackgroundThread(() ->
mCachedDevices.forEach(device -> {
device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
device.getDevice());
})
);
});
}
@Override
public void onDeviceLocalDataChange(@NonNull String address, @Nullable Data data) {
if (data == null) {
// The local data is removed because the device is unpaired, do nothing
return;
}
for (BluetoothDevice device : mSideToDeviceMap.values()) {
if (device.getAnonymizedAddress().equals(address)) {
mContext.getMainExecutor().execute(() -> loadLocalDataToUi(device));
return;
}
}
}
@Override
public void onVolumeControlServiceConnected() {
mCachedDevices.forEach(
device -> mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
device.getDevice()));
}
@Override
public void onAmbientChanged(@NonNull BluetoothDevice device, int gainSettings) {
if (DEBUG) {
Log.d(TAG, "onAmbientChanged, value:" + gainSettings + ", device:" + device);
}
Data data = mLocalDataManager.get(device);
boolean isInitiatedFromUi = (isControlExpanded() && data.ambient() == gainSettings)
|| (!isControlExpanded() && data.groupAmbient() == gainSettings);
if (isInitiatedFromUi) {
// The change is initiated from UI, no need to update UI
return;
}
// We have to check if we need to expand the controls by getting all remote
// device's ambient value, delay for a while to wait all remote devices update
// to the latest value to avoid unnecessary expand action.
mContext.getMainThreadHandler().postDelayed(this::refresh, 1200L);
}
@Override
public void onMuteChanged(@NonNull BluetoothDevice device, int mute) {
if (DEBUG) {
Log.d(TAG, "onMuteChanged, mute:" + mute + ", device:" + device);
}
boolean isInitiatedFromUi = (isControlMuted() && mute == MUTE_MUTED)
|| (!isControlMuted() && mute == MUTE_NOT_MUTED);
if (isInitiatedFromUi) {
// The change is initiated from UI, no need to update UI
return;
}
// We have to check if we need to mute the devices by getting all remote
// device's mute state, delay for a while to wait all remote devices update
// to the latest value.
mContext.getMainThreadHandler().postDelayed(this::refresh, 1200L);
}
@Override
public void onCommandFailed(@NonNull BluetoothDevice device) {
Log.w(TAG, "onCommandFailed, device:" + device);
mContext.getMainExecutor().execute(() -> {
showErrorToast();
refresh();
});
}
private void loadDevices() {
mSideToDeviceMap.clear();
mCachedDevices.clear();
if (VALID_SIDES.contains(mCachedDevice.getDeviceSide())
&& mCachedDevice.getBondState() == BOND_BONDED) {
mSideToDeviceMap.put(mCachedDevice.getDeviceSide(), mCachedDevice.getDevice());
mCachedDevices.add(mCachedDevice);
}
for (CachedBluetoothDevice memberDevice : mCachedDevice.getMemberDevice()) {
if (VALID_SIDES.contains(memberDevice.getDeviceSide())
&& memberDevice.getBondState() == BOND_BONDED) {
mSideToDeviceMap.put(memberDevice.getDeviceSide(), memberDevice.getDevice());
mCachedDevices.add(memberDevice);
}
}
createAmbientVolumePreference();
createSliderPreferences();
if (mPreference != null) {
mPreference.setExpandable(mSideToDeviceMap.size() > 1);
mPreference.setSliders((mSideToSliderMap));
}
}
private void createAmbientVolumePreference() {
if (mPreference != null || mDeviceControls == null) {
return;
}
mPreference = new AmbientVolumePreference(mDeviceControls.getContext());
mPreference.setKey(KEY_AMBIENT_VOLUME);
mPreference.setOrder(ORDER_AMBIENT_VOLUME);
mPreference.setOnIconClickListener(
new AmbientVolumePreference.OnIconClickListener() {
@Override
public void onExpandIconClick() {
mSideToDeviceMap.forEach((s, d) -> {
if (!isControlMuted()) {
// Apply previous collapsed/expanded volume to remote device
Data data = mLocalDataManager.get(d);
int volume = isControlExpanded()
? data.ambient() : data.groupAmbient();
mVolumeController.setAmbient(d, volume);
}
// Update new value to local data
mLocalDataManager.updateAmbientControlExpanded(d, isControlExpanded());
});
}
@Override
public void onAmbientVolumeIconClick() {
if (!isControlMuted()) {
loadLocalDataToUi();
}
for (BluetoothDevice device : mSideToDeviceMap.values()) {
mVolumeController.setMuted(device, isControlMuted());
}
}
});
if (mDeviceControls.findPreference(mPreference.getKey()) == null) {
mDeviceControls.addPreference(mPreference);
}
}
private void createSliderPreferences() {
mSideToDeviceMap.forEach((s, d) ->
createSliderPreference(s, ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED + s));
createSliderPreference(SIDE_UNIFIED, ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED);
}
private void createSliderPreference(int side, int order) {
if (mSideToSliderMap.containsKey(side) || mDeviceControls == null) {
return;
}
SeekBarPreference preference = new SeekBarPreference(mDeviceControls.getContext());
preference.setKey(KEY_AMBIENT_VOLUME_SLIDER + "_" + side);
preference.setOrder(order);
preference.setOnPreferenceChangeListener(this);
if (side == SIDE_LEFT) {
preference.setTitle(mContext.getString(R.string.bluetooth_ambient_volume_control_left));
} else if (side == SIDE_RIGHT) {
preference.setTitle(
mContext.getString(R.string.bluetooth_ambient_volume_control_right));
}
mSideToSliderMap.put(side, preference);
}
/** Refreshes the control UI visibility and enabled state. */
private void refreshControlUi() {
if (mPreference != null) {
boolean isAnySliderEnabled = false;
for (Map.Entry<Integer, BluetoothDevice> entry : mSideToDeviceMap.entrySet()) {
final int side = entry.getKey();
final BluetoothDevice device = entry.getValue();
final boolean enabled = isDeviceConnectedToVcp(device)
&& mVolumeController.isAmbientControlAvailable(device);
isAnySliderEnabled |= enabled;
mPreference.setSliderEnabled(side, enabled);
}
mPreference.setSliderEnabled(SIDE_UNIFIED, isAnySliderEnabled);
mPreference.updateLayout();
}
}
/** Sets the volume to the corresponding control slider. */
private void setVolumeIfValid(int side, int volume) {
if (volume == INVALID_VOLUME) {
return;
}
if (mPreference != null) {
mPreference.setSliderValue(side, volume);
}
// Update new value to local data
if (side == SIDE_UNIFIED) {
mSideToDeviceMap.forEach((s, d) -> mLocalDataManager.updateGroupAmbient(d, volume));
} else {
mLocalDataManager.updateAmbient(mSideToDeviceMap.get(side), volume);
}
}
private void loadLocalDataToUi() {
mSideToDeviceMap.forEach((s, d) -> loadLocalDataToUi(d));
}
private void loadLocalDataToUi(BluetoothDevice device) {
final Data data = mLocalDataManager.get(device);
if (DEBUG) {
Log.d(TAG, "loadLocalDataToUi, data=" + data + ", device=" + device);
}
final int side = mSideToDeviceMap.inverse().getOrDefault(device, SIDE_INVALID);
if (isDeviceConnectedToVcp(device) && !isControlMuted()) {
setVolumeIfValid(side, data.ambient());
setVolumeIfValid(SIDE_UNIFIED, data.groupAmbient());
}
setControlExpanded(data.ambientControlExpanded());
refreshControlUi();
}
private void loadRemoteDataToUi() {
BluetoothDevice leftDevice = mSideToDeviceMap.get(SIDE_LEFT);
AmbientVolumeController.RemoteAmbientState leftState =
mVolumeController.refreshAmbientState(leftDevice);
BluetoothDevice rightDevice = mSideToDeviceMap.get(SIDE_RIGHT);
AmbientVolumeController.RemoteAmbientState rightState =
mVolumeController.refreshAmbientState(rightDevice);
if (DEBUG) {
Log.d(TAG, "loadRemoteDataToUi, left=" + leftState + ", right=" + rightState);
}
if (mPreference != null) {
mSideToDeviceMap.forEach((side, device) -> {
int ambientMax = mVolumeController.getAmbientMax(device);
int ambientMin = mVolumeController.getAmbientMin(device);
if (ambientMin != ambientMax) {
mPreference.setSliderRange(side, ambientMin, ambientMax);
mPreference.setSliderRange(SIDE_UNIFIED, ambientMin, ambientMax);
}
});
}
// Update ambient volume
final int leftAmbient = leftState != null ? leftState.gainSetting() : INVALID_VOLUME;
final int rightAmbient = rightState != null ? rightState.gainSetting() : INVALID_VOLUME;
if (isControlExpanded()) {
setVolumeIfValid(SIDE_LEFT, leftAmbient);
setVolumeIfValid(SIDE_RIGHT, rightAmbient);
} else {
if (leftAmbient != rightAmbient && leftAmbient != INVALID_VOLUME
&& rightAmbient != INVALID_VOLUME) {
setVolumeIfValid(SIDE_LEFT, leftAmbient);
setVolumeIfValid(SIDE_RIGHT, rightAmbient);
setControlExpanded(true);
} else {
int unifiedAmbient = leftAmbient != INVALID_VOLUME ? leftAmbient : rightAmbient;
setVolumeIfValid(SIDE_UNIFIED, unifiedAmbient);
}
}
// Initialize local data between side and group value
initLocalDataIfNeeded();
// Update mute state
boolean mutable = true;
boolean muted = true;
if (isDeviceConnectedToVcp(leftDevice) && leftState != null) {
mutable &= leftState.isMutable();
muted &= leftState.isMuted();
}
if (isDeviceConnectedToVcp(rightDevice) && rightState != null) {
mutable &= rightState.isMutable();
muted &= rightState.isMuted();
}
if (mPreference != null) {
mPreference.setMutable(mutable);
mPreference.setMuted(muted);
}
// Ensure remote device mute state is synced
syncMuteStateIfNeeded(leftDevice, leftState, muted);
syncMuteStateIfNeeded(rightDevice, rightState, muted);
refreshControlUi();
}
/** Check if any device in the group has valid ambient control points */
private boolean isAmbientControlAvailable() {
for (BluetoothDevice device : mSideToDeviceMap.values()) {
// Found ambient local data for this device, show the ambient control
if (mLocalDataManager.get(device).hasAmbientData()) {
return true;
}
// Found remote ambient control points on this device, show the ambient control
if (mVolumeController.isAmbientControlAvailable(device)) {
return true;
}
}
return false;
}
private boolean isControlExpanded() {
return mPreference != null && mPreference.isExpanded();
}
private void setControlExpanded(boolean expanded) {
if (mPreference != null && mPreference.isExpanded() != expanded) {
mPreference.setExpanded(expanded);
}
mSideToDeviceMap.forEach((s, d) -> {
// Update new value to local data
mLocalDataManager.updateAmbientControlExpanded(d, expanded);
});
}
private boolean isControlMuted() {
return mPreference != null && mPreference.isMuted();
}
private void initLocalDataIfNeeded() {
int smallerVolumeAmongGroup = Integer.MAX_VALUE;
for (BluetoothDevice device : mSideToDeviceMap.values()) {
Data data = mLocalDataManager.get(device);
if (data.ambient() != INVALID_VOLUME) {
smallerVolumeAmongGroup = Math.min(data.ambient(), smallerVolumeAmongGroup);
} else if (data.groupAmbient() != INVALID_VOLUME) {
// Initialize side ambient from group ambient value
mLocalDataManager.updateAmbient(device, data.groupAmbient());
}
}
if (smallerVolumeAmongGroup != Integer.MAX_VALUE) {
for (BluetoothDevice device : mSideToDeviceMap.values()) {
Data data = mLocalDataManager.get(device);
if (data.groupAmbient() == INVALID_VOLUME) {
// Initialize group ambient from smaller side ambient value
mLocalDataManager.updateGroupAmbient(device, smallerVolumeAmongGroup);
}
}
}
}
private void syncMuteStateIfNeeded(@Nullable BluetoothDevice device,
@Nullable AmbientVolumeController.RemoteAmbientState state, boolean muted) {
if (isDeviceConnectedToVcp(device) && state != null && state.isMutable()) {
if (state.isMuted() != muted) {
mVolumeController.setMuted(device, muted);
}
}
}
private boolean isDeviceConnectedToVcp(@Nullable BluetoothDevice device) {
return device != null && device.isConnected()
&& mBluetoothManager.getProfileManager().getVolumeControlProfile()
.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED;
}
private void showErrorToast() {
if (mToast != null) {
mToast.cancel();
}
mToast = Toast.makeText(mContext, R.string.bluetooth_ambient_volume_error,
Toast.LENGTH_SHORT);
mToast.show();
}
} }

View File

@@ -40,7 +40,7 @@ import com.android.settingslib.datastore.SettingsSystemStore
import com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX import com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX
import com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MIN import com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MIN
import com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat import com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat
import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.FloatPersistentPreference
import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.metadata.ReadWritePermit
@@ -52,7 +52,7 @@ import java.text.NumberFormat
// LINT.IfChange // LINT.IfChange
class BrightnessLevelPreference : class BrightnessLevelPreference :
PreferenceMetadata, PreferenceMetadata,
PersistentPreference<Float>, FloatPersistentPreference,
PreferenceBinding, PreferenceBinding,
PreferenceRestrictionMixin, PreferenceRestrictionMixin,
PreferenceSummaryProvider, PreferenceSummaryProvider,
@@ -78,7 +78,7 @@ class BrightnessLevelPreference :
override val useAdminDisabledSummary: Boolean override val useAdminDisabledSummary: Boolean
get() = true get() = true
override fun intent(context: Context) = override fun intent(context: Context): Intent? =
Intent(ACTION_SHOW_BRIGHTNESS_DIALOG) Intent(ACTION_SHOW_BRIGHTNESS_DIALOG)
.setPackage(Utils.SYSTEMUI_PACKAGE_NAME) .setPackage(Utils.SYSTEMUI_PACKAGE_NAME)
.putExtra( .putExtra(

View File

@@ -109,12 +109,9 @@ public class ActionDisabledByAdminDialog extends Activity
} }
if (enforcingAdmin.getAuthority() instanceof UnknownAuthority authority if (enforcingAdmin.getAuthority() instanceof UnknownAuthority authority
&& ADVANCED_PROTECTION_SYSTEM_ENTITY.equals(authority.getName())) { && ADVANCED_PROTECTION_SYSTEM_ENTITY.equals(authority.getName())) {
AdvancedProtectionManager apm = getSystemService(AdvancedProtectionManager.class); Intent apmSupportIntent = AdvancedProtectionManager
if (apm == null) { .createSupportIntentForPolicyIdentifierOrRestriction(restriction,
return; AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_UNKNOWN);
}
Intent apmSupportIntent = apm.createSupportIntentForPolicyIdentifierOrRestriction(
restriction, /* type */ null);
startActivityAsUser(apmSupportIntent, UserHandle.of(userId)); startActivityAsUser(apmSupportIntent, UserHandle.of(userId));
finish(); finish();
} else { } else {

View File

@@ -32,6 +32,8 @@ import android.util.Pair;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
@@ -149,6 +151,13 @@ public class PowerUsageAdvanced extends PowerUsageBase {
} }
} }
@Override
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
final RecyclerView.Adapter adapter = super.onCreateAdapter(preferenceScreen);
adapter.setHasStableIds(true);
return adapter;
}
@Override @Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>(); final List<AbstractPreferenceController> controllers = new ArrayList<>();

View File

@@ -25,6 +25,9 @@ import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_RED;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.provider.Settings; import android.provider.Settings;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.PointerIcon; import android.view.PointerIcon;
@@ -41,6 +44,11 @@ import com.android.settings.R;
public class PointerFillStylePreference extends Preference { public class PointerFillStylePreference extends Preference {
private static final int[] STATE_HOVERED_SELECTED =
new int[]{android.R.attr.state_hovered, android.R.attr.state_selected};
private static final int[] STATE_SELECTED = new int[]{android.R.attr.state_selected};
private static final int[] STATE_HOVERED = new int[]{android.R.attr.state_hovered};
private static final int[] STATE_DEFAULT = new int[]{};
@Nullable private LinearLayout mButtonHolder; @Nullable private LinearLayout mButtonHolder;
@@ -82,11 +90,7 @@ public class PointerFillStylePreference extends Preference {
if (button == null) { if (button == null) {
return; return;
} }
int[] attrs = {com.android.internal.R.attr.pointerIconVectorFill}; tintButtonByStyle(button, style);
try (TypedArray ta = getContext().obtainStyledAttributes(
PointerIcon.vectorFillStyleToResource(style), attrs)) {
button.getBackground().setTint(ta.getColor(0, Color.BLACK));
}
button.setOnClickListener( button.setOnClickListener(
(v) -> { (v) -> {
getPreferenceDataStore().putInt(Settings.System.POINTER_FILL_STYLE, style); getPreferenceDataStore().putInt(Settings.System.POINTER_FILL_STYLE, style);
@@ -96,6 +100,32 @@ public class PointerFillStylePreference extends Preference {
button.setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_ARROW)); button.setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_ARROW));
} }
private void tintButtonByStyle(ImageView button, int style) {
int[] attrs = {com.android.internal.R.attr.pointerIconVectorFill};
try (TypedArray ta = getContext().obtainStyledAttributes(
PointerIcon.vectorFillStyleToResource(style), attrs)) {
// Index 0, as there is only one attribute returned here.
int color = ta.getColor(/* index= */ 0, Color.BLACK);
StateListDrawable stateListDrawable = (StateListDrawable) button.getDrawable();
tintDrawableByLayerId(stateListDrawable, STATE_HOVERED_SELECTED,
R.id.tintableCircleHoveredSelected, color);
tintDrawableByLayerId(stateListDrawable, STATE_SELECTED, R.id.tintableCircleSelected,
color);
tintDrawableByLayerId(stateListDrawable, STATE_HOVERED, R.id.tintableCircleHovered,
color);
tintDrawableByLayerId(stateListDrawable, STATE_DEFAULT, R.id.tintableCircleDefault,
color);
}
}
private void tintDrawableByLayerId(StateListDrawable stateListDrawable, int[] stateSet,
int layerId, int color) {
int index = stateListDrawable.findStateDrawableIndex(stateSet);
LayerDrawable layerDrawable = (LayerDrawable) stateListDrawable.getStateDrawable(index);
Drawable drawable = layerDrawable.findDrawableByLayerId(layerId);
drawable.setTint(color);
}
private void setButtonChecked(int id) { private void setButtonChecked(int id) {
if (mButtonHolder == null) { if (mButtonHolder == null) {
return; return;

View File

@@ -54,6 +54,7 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@@ -345,6 +346,7 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
List<SubscriptionInfoEntity> availableSubInfoEntityList) { List<SubscriptionInfoEntity> availableSubInfoEntityList) {
List<SubscriptionInfoEntity> activeSubInfoEntityList = List<SubscriptionInfoEntity> activeSubInfoEntityList =
availableSubInfoEntityList.stream() availableSubInfoEntityList.stream()
.filter(entity -> Objects.nonNull(entity))
.filter(SubscriptionInfoEntity::isActiveSubscription) .filter(SubscriptionInfoEntity::isActiveSubscription)
.filter(SubscriptionInfoEntity::isSubscriptionVisible) .filter(SubscriptionInfoEntity::isSubscriptionVisible)
.collect(Collectors.toList()); .collect(Collectors.toList());

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security;
import android.app.Activity
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.android.settings.R
import androidx.appcompat.app.AlertDialog;
class ActionDisabledByAdvancedProtectionDialog : Activity(), DialogInterface.OnDismissListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dialogView = layoutInflater.inflate(R.layout.support_details_dialog, null) as ViewGroup
val builder = AlertDialog.Builder(this)
.setPositiveButton(R.string.okay, null)
.setView(dialogView)
.setOnDismissListener(this)
initializeDialogView(dialogView)
builder.show()
}
override fun onDismiss(dialog: DialogInterface) {
finish()
}
private fun initializeDialogView(dialogView: View) {
setSupportTitle(dialogView)
setSupportDetails(dialogView)
}
private fun setSupportTitle(root: View) {
val titleView: TextView = root.findViewById(R.id.admin_support_dialog_title) ?: return
titleView.setText(R.string.disabled_by_advanced_protection_title)
}
private fun setSupportDetails(root: View) {
val textView: TextView = root.findViewById(R.id.admin_support_msg)
textView.setText(R.string.disabled_by_advanced_protection_message)
}
}

View File

@@ -204,5 +204,7 @@ private fun PreferenceProto.toMetadata(
.setWritable(persistent) .setWritable(persistent)
.setLaunchIntent(launchIntent.toIntent()) .setLaunchIntent(launchIntent.toIntent())
.setWriteSensitivity(sensitivity) .setWriteSensitivity(sensitivity)
.setReadPermissions(readPermissionsList)
.setWritePermissions(writePermissionsList)
.build() .build()
} }

View File

@@ -142,9 +142,13 @@ public class SeekBarPreference extends RestrictedPreference
mSeekBar.setContentDescription(mSeekBarContentDescription); mSeekBar.setContentDescription(mSeekBarContentDescription);
} else if (!TextUtils.isEmpty(title)) { } else if (!TextUtils.isEmpty(title)) {
mSeekBar.setContentDescription(title); mSeekBar.setContentDescription(title);
} else {
mSeekBar.setContentDescription(null);
} }
if (!TextUtils.isEmpty(mSeekBarStateDescription)) { if (!TextUtils.isEmpty(mSeekBarStateDescription)) {
mSeekBar.setStateDescription(mSeekBarStateDescription); mSeekBar.setStateDescription(mSeekBarStateDescription);
} else {
mSeekBar.setStateDescription(null);
} }
if (mSeekBar instanceof DefaultIndicatorSeekBar) { if (mSeekBar instanceof DefaultIndicatorSeekBar) {
((DefaultIndicatorSeekBar) mSeekBar).setDefaultProgress(mDefaultProgress); ((DefaultIndicatorSeekBar) mSeekBar).setDefaultProgress(mDefaultProgress);

View File

@@ -162,10 +162,10 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
emit(aapmManager?.isAdvancedProtectionEnabled ?: false) }.flowOn(Dispatchers.Default) emit(aapmManager?.isAdvancedProtectionEnabled ?: false) }.flowOn(Dispatchers.Default)
private fun startSupportIntent() { private fun startSupportIntent() {
aapmManager?.createSupportIntent( AdvancedProtectionManager.createSupportIntent(
AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP, AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP,
AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_DISABLED_SETTING AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_DISABLED_SETTING
)?.let { mContext.startActivity(it) } ).let { mContext.startActivity(it) }
} }
val wepAllowedFlow = val wepAllowedFlow =

View File

@@ -203,7 +203,7 @@ public class HighContrastTextMigrationReceiverTest {
ShadowNotification shadowNotification = Shadows.shadowOf(notification); ShadowNotification shadowNotification = Shadows.shadowOf(notification);
assertThat(shadowNotification.getContentTitle()).isEqualTo(mContext.getString( assertThat(shadowNotification.getContentTitle()).isEqualTo(mContext.getString(
R.string.accessibility_toggle_high_text_contrast_preference_title)); R.string.accessibility_notification_high_contrast_text_title));
assertThat(shadowNotification.getContentText()).isEqualTo( assertThat(shadowNotification.getContentText()).isEqualTo(
mContext.getString(R.string.accessibility_notification_high_contrast_text_content)); mContext.getString(R.string.accessibility_notification_high_contrast_text_content));

View File

@@ -26,8 +26,10 @@ import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_R
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.content.Context; import android.content.Context;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.view.View; import android.view.View;
@@ -40,6 +42,7 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.widget.SeekBarPreference; import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.bluetooth.AmbientVolumeUi;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@@ -69,14 +72,14 @@ public class AmbientVolumePreferenceTest {
@Spy @Spy
private Context mContext = ApplicationProvider.getApplicationContext(); private Context mContext = ApplicationProvider.getApplicationContext();
@Mock @Mock
private AmbientVolumePreference.OnIconClickListener mListener; private AmbientVolumeUi.AmbientVolumeUiListener mListener;
@Mock @Mock
private View mItemView; private View mItemView;
private AmbientVolumePreference mPreference; private AmbientVolumePreference mPreference;
private ImageView mExpandIcon; private ImageView mExpandIcon;
private ImageView mVolumeIcon; private ImageView mVolumeIcon;
private final Map<Integer, SeekBarPreference> mSideToSlidersMap = new ArrayMap<>(); private final Map<Integer, BluetoothDevice> mSideToDeviceMap = new ArrayMap<>();
@Before @Before
public void setUp() { public void setUp() {
@@ -84,13 +87,27 @@ public class AmbientVolumePreferenceTest {
PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext); PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext);
mPreference = new AmbientVolumePreference(mContext); mPreference = new AmbientVolumePreference(mContext);
mPreference.setKey(KEY_AMBIENT_VOLUME); mPreference.setKey(KEY_AMBIENT_VOLUME);
mPreference.setOnIconClickListener(mListener); mPreference.setListener(mListener);
mPreference.setExpandable(true); mPreference.setExpandable(true);
mPreference.setMutable(true); mPreference.setMutable(true);
preferenceScreen.addPreference(mPreference); preferenceScreen.addPreference(mPreference);
prepareSliders(); prepareDevices();
mPreference.setSliders(mSideToSlidersMap); mPreference.setupSliders(mSideToDeviceMap);
mPreference.getSliders().forEach((side, slider) -> {
slider.setMin(0);
slider.setMax(4);
if (side == SIDE_LEFT) {
slider.setKey(KEY_LEFT_SLIDER);
slider.setProgress(TEST_LEFT_VOLUME_LEVEL);
} else if (side == SIDE_RIGHT) {
slider.setKey(KEY_RIGHT_SLIDER);
slider.setProgress(TEST_RIGHT_VOLUME_LEVEL);
} else {
slider.setKey(KEY_UNIFIED_SLIDER);
slider.setProgress(TEST_UNIFIED_VOLUME_LEVEL);
}
});
mExpandIcon = new ImageView(mContext); mExpandIcon = new ImageView(mContext);
mVolumeIcon = new ImageView(mContext); mVolumeIcon = new ImageView(mContext);
@@ -206,33 +223,16 @@ public class AmbientVolumePreferenceTest {
private void assertControlUiCorrect() { private void assertControlUiCorrect() {
final boolean expanded = mPreference.isExpanded(); final boolean expanded = mPreference.isExpanded();
assertThat(mSideToSlidersMap.get(SIDE_UNIFIED).isVisible()).isEqualTo(!expanded); Map<Integer, SeekBarPreference> sliders = mPreference.getSliders();
assertThat(mSideToSlidersMap.get(SIDE_LEFT).isVisible()).isEqualTo(expanded); assertThat(sliders.get(SIDE_UNIFIED).isVisible()).isEqualTo(!expanded);
assertThat(mSideToSlidersMap.get(SIDE_RIGHT).isVisible()).isEqualTo(expanded); assertThat(sliders.get(SIDE_LEFT).isVisible()).isEqualTo(expanded);
assertThat(sliders.get(SIDE_RIGHT).isVisible()).isEqualTo(expanded);
final float rotation = expanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED; final float rotation = expanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED;
assertThat(mExpandIcon.getRotation()).isEqualTo(rotation); assertThat(mExpandIcon.getRotation()).isEqualTo(rotation);
} }
private void prepareSliders() { private void prepareDevices() {
prepareSlider(SIDE_UNIFIED); mSideToDeviceMap.put(SIDE_LEFT, mock(BluetoothDevice.class));
prepareSlider(SIDE_LEFT); mSideToDeviceMap.put(SIDE_RIGHT, mock(BluetoothDevice.class));
prepareSlider(SIDE_RIGHT);
}
private void prepareSlider(int side) {
SeekBarPreference slider = new SeekBarPreference(mContext);
slider.setMin(0);
slider.setMax(4);
if (side == SIDE_LEFT) {
slider.setKey(KEY_LEFT_SLIDER);
slider.setProgress(TEST_LEFT_VOLUME_LEVEL);
} else if (side == SIDE_RIGHT) {
slider.setKey(KEY_RIGHT_SLIDER);
slider.setProgress(TEST_RIGHT_VOLUME_LEVEL);
} else {
slider.setKey(KEY_UNIFIED_SLIDER);
slider.setProgress(TEST_UNIFIED_VOLUME_LEVEL);
}
mSideToSlidersMap.put(side, slider);
} }
} }

View File

@@ -16,46 +16,25 @@
package com.android.settings.bluetooth; package com.android.settings.bluetooth;
import static android.bluetooth.AudioInputControl.MUTE_DISABLED;
import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
import static android.bluetooth.AudioInputControl.MUTE_MUTED;
import static android.bluetooth.BluetoothDevice.BOND_BONDED;
import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME; import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME;
import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME_SLIDER;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP; import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.ContentResolver;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import com.android.settings.testutils.shadow.ShadowThreadUtils; import com.android.settings.testutils.shadow.ShadowThreadUtils;
import com.android.settings.widget.SeekBarPreference; import com.android.settingslib.bluetooth.AmbientVolumeUiController;
import com.android.settingslib.bluetooth.AmbientVolumeController;
import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile; import com.android.settingslib.bluetooth.VolumeControlProfile;
@@ -69,41 +48,19 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowSettings;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
/** Tests for {@link BluetoothDetailsAmbientVolumePreferenceController}. */ /** Tests for {@link BluetoothDetailsAmbientVolumePreferenceController}. */
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = { @Config(shadows = {
BluetoothDetailsAmbientVolumePreferenceControllerTest.ShadowGlobal.class,
ShadowThreadUtils.class ShadowThreadUtils.class
}) })
public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
BluetoothDetailsControllerTestBase { BluetoothDetailsControllerTestBase {
@Rule @Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule(); public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private static final String LEFT_CONTROL_KEY = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_LEFT;
private static final String RIGHT_CONTROL_KEY = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_RIGHT;
private static final String TEST_ADDRESS = "00:00:00:00:11";
private static final String TEST_MEMBER_ADDRESS = "00:00:00:00:22";
@Mock
private CachedBluetoothDevice mCachedMemberDevice;
@Mock
private BluetoothDevice mDevice;
@Mock
private BluetoothDevice mMemberDevice;
@Mock
private HearingDeviceLocalDataManager mLocalDataManager;
@Mock @Mock
private LocalBluetoothManager mBluetoothManager; private LocalBluetoothManager mBluetoothManager;
@Mock @Mock
@@ -113,9 +70,9 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
@Mock @Mock
private VolumeControlProfile mVolumeControlProfile; private VolumeControlProfile mVolumeControlProfile;
@Mock @Mock
private AmbientVolumeController mVolumeController;
@Mock
private Handler mTestHandler; private Handler mTestHandler;
@Mock
private AmbientVolumeUiController mUiController;
private BluetoothDetailsAmbientVolumePreferenceController mController; private BluetoothDetailsAmbientVolumePreferenceController mController;
@@ -124,24 +81,16 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
super.setUp(); super.setUp();
mContext = spy(mContext); mContext = spy(mContext);
when(mBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mBluetoothManager.getEventManager()).thenReturn(mEventManager);
mController = spy(
new BluetoothDetailsAmbientVolumePreferenceController(mContext, mBluetoothManager,
mFragment, mCachedDevice, mLifecycle, mUiController));
PreferenceCategory deviceControls = new PreferenceCategory(mContext); PreferenceCategory deviceControls = new PreferenceCategory(mContext);
deviceControls.setKey(KEY_HEARING_DEVICE_GROUP); deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
mScreen.addPreference(deviceControls); mScreen.addPreference(deviceControls);
mController = spy(
new BluetoothDetailsAmbientVolumePreferenceController(mContext, mBluetoothManager,
mFragment, mCachedDevice, mLifecycle, mLocalDataManager,
mVolumeController));
when(mBluetoothManager.getEventManager()).thenReturn(mEventManager);
when(mBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile);
when(mVolumeControlProfile.getConnectionStatus(mDevice)).thenReturn(
BluetoothProfile.STATE_CONNECTED);
when(mVolumeControlProfile.getConnectionStatus(mMemberDevice)).thenReturn(
BluetoothProfile.STATE_CONNECTED);
when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
when(mLocalDataManager.get(any(BluetoothDevice.class))).thenReturn(
new HearingDeviceLocalDataManager.Data.Builder().build());
when(mContext.getMainThreadHandler()).thenReturn(mTestHandler); when(mContext.getMainThreadHandler()).thenReturn(mTestHandler);
when(mTestHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer( when(mTestHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
@@ -152,283 +101,42 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
} }
@Test @Test
public void init_deviceWithoutMember_controlNotExpandable() { public void init_preferenceAdded() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen); mController.init(mScreen);
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME); AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull(); assertThat(preference).isNotNull();
assertThat(preference.isExpandable()).isFalse();
} }
@Test @Test
public void init_deviceWithMember_controlExpandable() { public void refresh_deviceNotSupportVcp_verifyUiControllerNoRefresh() {
prepareDevice(/* hasMember= */ true); when(mCachedDevice.getProfiles()).thenReturn(List.of());
mController.init(mScreen); mController.refresh();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME); verify(mUiController, never()).refresh();
assertThat(preference).isNotNull();
assertThat(preference.isExpandable()).isTrue();
} }
@Test @Test
public void onDeviceLocalDataChange_noMemberAndExpanded_uiCorrectAndDataUpdated() { public void refresh_deviceSupportVcp_verifyUiControllerRefresh() {
prepareDevice(/* hasMember= */ false); when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
mController.init(mScreen);
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(0).groupAmbient(0).ambientControlExpanded(true).build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
mController.onDeviceLocalDataChange(TEST_ADDRESS, data); mController.refresh();
shadowOf(Looper.getMainLooper()).idle();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME); verify(mUiController).refresh();
assertThat(preference).isNotNull();
assertThat(preference.isExpanded()).isFalse();
verifyDeviceDataUpdated(mDevice);
} }
@Test @Test
public void onDeviceLocalDataChange_noMemberAndCollapsed_uiCorrectAndDataUpdated() { public void onStart_verifyUiControllerStart() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(0).groupAmbient(0).ambientControlExpanded(false).build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
shadowOf(Looper.getMainLooper()).idle();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpanded()).isFalse();
verifyDeviceDataUpdated(mDevice);
}
@Test
public void onDeviceLocalDataChange_hasMemberAndExpanded_uiCorrectAndDataUpdated() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(0).groupAmbient(0).ambientControlExpanded(true).build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
shadowOf(Looper.getMainLooper()).idle();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpanded()).isTrue();
verifyDeviceDataUpdated(mDevice);
}
@Test
public void onDeviceLocalDataChange_hasMemberAndCollapsed_uiCorrectAndDataUpdated() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(0).groupAmbient(0).ambientControlExpanded(false).build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
shadowOf(Looper.getMainLooper()).idle();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpanded()).isFalse();
verifyDeviceDataUpdated(mDevice);
}
@Test
public void onStart_localDataManagerStartAndCallbackRegistered() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
mController.onStart(); mController.onStart();
verify(mLocalDataManager, atLeastOnce()).start(); verify(mUiController).start();
verify(mVolumeController).registerCallback(any(Executor.class), eq(mDevice));
verify(mVolumeController).registerCallback(any(Executor.class), eq(mMemberDevice));
verify(mCachedDevice).registerCallback(any(Executor.class),
any(CachedBluetoothDevice.Callback.class));
verify(mCachedMemberDevice).registerCallback(any(Executor.class),
any(CachedBluetoothDevice.Callback.class));
} }
@Test @Test
public void onStop_localDataManagerStopAndCallbackUnregistered() { public void onStop_verifyUiControllerStop() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
mController.onStop(); mController.onStop();
verify(mLocalDataManager).stop(); verify(mUiController).stop();
verify(mVolumeController).unregisterCallback(mDevice);
verify(mVolumeController).unregisterCallback(mMemberDevice);
verify(mCachedDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
verify(mCachedMemberDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
}
@Test
public void onDeviceAttributesChanged_newDevice_newPreference() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
// check the right control is null before onDeviceAttributesChanged()
SeekBarPreference leftControl = mScreen.findPreference(LEFT_CONTROL_KEY);
SeekBarPreference rightControl = mScreen.findPreference(RIGHT_CONTROL_KEY);
assertThat(leftControl).isNotNull();
assertThat(rightControl).isNull();
prepareDevice(/* hasMember= */ true);
mController.onDeviceAttributesChanged();
shadowOf(Looper.getMainLooper()).idle();
// check the right control is created after onDeviceAttributesChanged()
SeekBarPreference updatedLeftControl = mScreen.findPreference(LEFT_CONTROL_KEY);
SeekBarPreference updatedRightControl = mScreen.findPreference(RIGHT_CONTROL_KEY);
assertThat(updatedLeftControl).isEqualTo(leftControl);
assertThat(updatedRightControl).isNotNull();
}
@Test
public void onAmbientChanged_refreshWhenNotInitiateFromUi() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
final int testAmbient = 10;
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(testAmbient)
.groupAmbient(testAmbient)
.ambientControlExpanded(false)
.build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
getPreference().setExpanded(true);
mController.onAmbientChanged(mDevice, testAmbient);
verify(mController, never()).refresh();
final int updatedTestAmbient = 20;
mController.onAmbientChanged(mDevice, updatedTestAmbient);
verify(mController).refresh();
}
@Test
public void onMuteChanged_refreshWhenNotInitiateFromUi() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
final int testMute = MUTE_NOT_MUTED;
AmbientVolumeController.RemoteAmbientState state =
new AmbientVolumeController.RemoteAmbientState(testMute, 0);
when(mVolumeController.refreshAmbientState(mDevice)).thenReturn(state);
getPreference().setMuted(false);
mController.onMuteChanged(mDevice, testMute);
verify(mController, never()).refresh();
final int updatedTestMute = MUTE_MUTED;
mController.onMuteChanged(mDevice, updatedTestMute);
verify(mController).refresh();
}
@Test
public void refresh_leftAndRightDifferentGainSetting_expandControl() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
prepareRemoteData(mDevice, 10, MUTE_NOT_MUTED);
prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
getPreference().setExpanded(false);
mController.refresh();
assertThat(getPreference().isExpanded()).isTrue();
}
@Test
public void refresh_oneSideNotMutable_controlNotMutableAndNotMuted() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
prepareRemoteData(mDevice, 10, MUTE_DISABLED);
prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
getPreference().setMutable(true);
getPreference().setMuted(true);
mController.refresh();
assertThat(getPreference().isMutable()).isFalse();
assertThat(getPreference().isMuted()).isFalse();
}
@Test
public void refresh_oneSideNotMuted_controlNotMutedAndSyncToRemote() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
prepareRemoteData(mDevice, 10, MUTE_MUTED);
prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
getPreference().setMutable(true);
getPreference().setMuted(true);
mController.refresh();
assertThat(getPreference().isMutable()).isTrue();
assertThat(getPreference().isMuted()).isFalse();
verify(mVolumeController).setMuted(mDevice, false);
}
private void prepareDevice(boolean hasMember) {
when(mCachedDevice.getDeviceSide()).thenReturn(SIDE_LEFT);
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getBondState()).thenReturn(BOND_BONDED);
when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
when(mDevice.getAnonymizedAddress()).thenReturn(TEST_ADDRESS);
when(mDevice.isConnected()).thenReturn(true);
if (hasMember) {
when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedMemberDevice));
when(mCachedMemberDevice.getDeviceSide()).thenReturn(SIDE_RIGHT);
when(mCachedMemberDevice.getDevice()).thenReturn(mMemberDevice);
when(mCachedMemberDevice.getBondState()).thenReturn(BOND_BONDED);
when(mMemberDevice.getAddress()).thenReturn(TEST_MEMBER_ADDRESS);
when(mMemberDevice.getAnonymizedAddress()).thenReturn(TEST_MEMBER_ADDRESS);
when(mMemberDevice.isConnected()).thenReturn(true);
}
}
private void prepareRemoteData(BluetoothDevice device, int gainSetting, int mute) {
when(mVolumeController.isAmbientControlAvailable(device)).thenReturn(true);
when(mVolumeController.refreshAmbientState(device)).thenReturn(
new AmbientVolumeController.RemoteAmbientState(gainSetting, mute));
}
private void verifyDeviceDataUpdated(BluetoothDevice device) {
verify(mLocalDataManager, atLeastOnce()).updateAmbient(eq(device), anyInt());
verify(mLocalDataManager, atLeastOnce()).updateGroupAmbient(eq(device), anyInt());
verify(mLocalDataManager, atLeastOnce()).updateAmbientControlExpanded(eq(device),
anyBoolean());
}
private AmbientVolumePreference getPreference() {
return mScreen.findPreference(KEY_AMBIENT_VOLUME);
}
@Implements(value = Settings.Global.class)
public static class ShadowGlobal extends ShadowSettings.ShadowGlobal {
private static final Map<ContentResolver, Map<String, String>> sDataMap = new HashMap<>();
@Implementation
protected static boolean putStringForUser(
ContentResolver cr, String name, String value, int userHandle) {
get(cr).put(name, value);
return true;
}
@Implementation
protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
return get(cr).get(name);
}
private static Map<String, String> get(ContentResolver cr) {
return sDataMap.computeIfAbsent(cr, k -> new HashMap<>());
}
} }
} }

View File

@@ -20,9 +20,14 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.security.advancedprotection.AdvancedProtectionManager.ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG; import static android.security.advancedprotection.AdvancedProtectionManager.ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG;
import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY; import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
import static android.security.advancedprotection.AdvancedProtectionManager.EXTRA_SUPPORT_DIALOG_FEATURE; import static android.security.advancedprotection.AdvancedProtectionManager.EXTRA_SUPPORT_DIALOG_FEATURE;
import static android.security.advancedprotection.AdvancedProtectionManager.EXTRA_SUPPORT_DIALOG_TYPE;
import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
import static android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_UNKNOWN;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@@ -40,7 +45,6 @@ import android.os.UserManager;
import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.security.advancedprotection.AdvancedProtectionManager;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -48,6 +52,7 @@ import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
@@ -59,8 +64,6 @@ public class ActionDisabledByAdminDialogTest {
@Mock @Mock
private DevicePolicyManager mDevicePolicyManager; private DevicePolicyManager mDevicePolicyManager;
@Mock
private AdvancedProtectionManager mAdvancedProtectionManager;
private ActionDisabledByAdminDialog mDialog; private ActionDisabledByAdminDialog mDialog;
private final ComponentName mAdminComponent = new ComponentName("admin", "adminclass"); private final ComponentName mAdminComponent = new ComponentName("admin", "adminclass");
@@ -70,8 +73,6 @@ public class ActionDisabledByAdminDialogTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mDialog = spy(new ActionDisabledByAdminDialog()); mDialog = spy(new ActionDisabledByAdminDialog());
doReturn(mDevicePolicyManager).when(mDialog).getSystemService(DevicePolicyManager.class); doReturn(mDevicePolicyManager).when(mDialog).getSystemService(DevicePolicyManager.class);
doReturn(mAdvancedProtectionManager).when(mDialog).getSystemService(
AdvancedProtectionManager.class);
} }
@Test @Test
@@ -118,24 +119,28 @@ public class ActionDisabledByAdminDialogTest {
advancedProtectionAuthority, UserHandle.of(userId), mAdminComponent); advancedProtectionAuthority, UserHandle.of(userId), mAdminComponent);
final String userRestriction = UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY; final String userRestriction = UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY;
final Intent apmIntent = new Intent(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG);
apmIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
apmIntent.putExtra(EXTRA_SUPPORT_DIALOG_FEATURE, "featureId");
final Intent dialogIntent = new Intent(); final Intent dialogIntent = new Intent();
dialogIntent.putExtra(Intent.EXTRA_USER_ID, userId); dialogIntent.putExtra(Intent.EXTRA_USER_ID, userId);
dialogIntent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, userRestriction); dialogIntent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, userRestriction);
when(mDevicePolicyManager.getEnforcingAdmin(userId, userRestriction)) when(mDevicePolicyManager.getEnforcingAdmin(userId, userRestriction))
.thenReturn(advancedProtectionEnforcingAdmin); .thenReturn(advancedProtectionEnforcingAdmin);
when(mAdvancedProtectionManager.createSupportIntentForPolicyIdentifierOrRestriction( doNothing().when(mDialog).startActivityAsUser(any(), eq(UserHandle.of(userId)));
userRestriction, /* type */ null)).thenReturn(apmIntent);
doNothing().when(mDialog).startActivityAsUser(apmIntent, UserHandle.of(userId));
mDialog.getAdminDetailsFromIntent(dialogIntent); mDialog.getAdminDetailsFromIntent(dialogIntent);
verify(mDialog).startActivityAsUser(apmIntent, UserHandle.of(userId)); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mDialog).startActivityAsUser(intentCaptor.capture(), eq(UserHandle.of(userId)));
assertTrue(mDialog.isFinishing()); assertTrue(mDialog.isFinishing());
Intent launchedIntent = intentCaptor.getValue();
assertEquals("Intent action is incorrect", ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG,
launchedIntent.getAction());
assertEquals("Feature ID extra is incorrect", FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES,
launchedIntent.getIntExtra(EXTRA_SUPPORT_DIALOG_FEATURE, -1));
assertEquals("Type is incorrect", SUPPORT_DIALOG_TYPE_UNKNOWN,
launchedIntent.getIntExtra(EXTRA_SUPPORT_DIALOG_TYPE, -1));
assertEquals(FLAG_ACTIVITY_NEW_TASK, launchedIntent.getFlags());
} }
@RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API) @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)

View File

@@ -41,10 +41,8 @@ import com.android.settingslib.graph.preferenceGroupProto
import com.android.settingslib.graph.preferenceOrGroupProto import com.android.settingslib.graph.preferenceOrGroupProto
import com.android.settingslib.graph.preferenceProto import com.android.settingslib.graph.preferenceProto
import com.android.settingslib.graph.preferenceScreenProto import com.android.settingslib.graph.preferenceScreenProto
import com.android.settingslib.graph.preferenceValueProto
import com.android.settingslib.graph.proto.PreferenceGraphProto import com.android.settingslib.graph.proto.PreferenceGraphProto
import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.graph.proto.PreferenceValueProto
import com.android.settingslib.graph.proto.TextProto
import com.android.settingslib.graph.textProto import com.android.settingslib.graph.textProto
import com.android.settingslib.graph.toProto import com.android.settingslib.graph.toProto
import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SensitivityLevel
@@ -57,8 +55,7 @@ import org.junit.runner.RunWith
@RequiresFlagsEnabled(FLAG_SETTINGS_CATALYST) @RequiresFlagsEnabled(FLAG_SETTINGS_CATALYST)
class PreferenceServiceRequestTransformerTest { class PreferenceServiceRequestTransformerTest {
@get:Rule @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@Test @Test
fun transformCatalystGetMetadataResponse_emptyGraph_returnsFrameworkResponseWithError() { fun transformCatalystGetMetadataResponse_emptyGraph_returnsFrameworkResponseWithError() {
@@ -97,7 +94,7 @@ class PreferenceServiceRequestTransformerTest {
title = textProto { string = "title2" } title = textProto { string = "title2" }
enabled = false enabled = false
} }
} },
) )
) )
} }
@@ -110,21 +107,23 @@ class PreferenceServiceRequestTransformerTest {
assertThat(metadataList.size).isEqualTo(2) assertThat(metadataList.size).isEqualTo(2)
} }
assertThat( assertThat(
fResult.metadataList.any { fResult.metadataList.any {
it.key == "key1" && it.key == "key1" &&
it.screenKey == "screen" && it.screenKey == "screen" &&
it.title == "title1" && it.title == "title1" &&
it.isEnabled == true it.isEnabled
} }
).isTrue() )
.isTrue()
assertThat( assertThat(
fResult.metadataList.any { fResult.metadataList.any {
it.key == "key2" && it.key == "key2" &&
it.screenKey == "screen" && it.screenKey == "screen" &&
it.title == "title2" && it.title == "title2" &&
it.isEnabled == false !it.isEnabled
} }
).isTrue() )
.isTrue()
} }
@Test @Test
@@ -143,26 +142,28 @@ class PreferenceServiceRequestTransformerTest {
fun transformCatalystGetValueResponse_success_returnsValidFrameworkResponse() { fun transformCatalystGetValueResponse_success_returnsValidFrameworkResponse() {
val context: Context = ApplicationProvider.getApplicationContext() val context: Context = ApplicationProvider.getApplicationContext()
val fRequest = GetValueRequest.Builder("screen", "key").build() val fRequest = GetValueRequest.Builder("screen", "key").build()
val cResult = PreferenceGetterResponse( val cResult =
emptyMap(), PreferenceGetterResponse(
mapOf( emptyMap(),
PreferenceCoordinate(fRequest.screenKey, fRequest.preferenceKey) to mapOf(
PreferenceProto.newBuilder() PreferenceCoordinate(fRequest.screenKey, fRequest.preferenceKey) to
.setKey("key") preferenceProto {
.setTitle(TextProto.newBuilder().setString("title")) key = "key"
.setSummary(TextProto.newBuilder().setString("summary")) title = textProto { string = "title" }
.setEnabled(true) summary = textProto { string = "summary" }
.setAvailable(true) enabled = true
.setRestricted(true) available = true
.setPersistent(true) restricted = true
.setSensitivityLevel(SensitivityLevel.LOW_SENSITIVITY) persistent = true
.setLaunchIntent( sensitivityLevel = SensitivityLevel.LOW_SENSITIVITY
addReadPermissions("read_permission")
addWritePermissions("write_permission")
launchIntent =
Intent(context, SettingsHomepageActivity::class.java).toProto() Intent(context, SettingsHomepageActivity::class.java).toProto()
) value = preferenceValueProto { booleanValue = true }
.setValue(PreferenceValueProto.newBuilder().setBooleanValue(true)) }
.build() ),
) )
)
val fResult = transformCatalystGetValueResponse(context, fRequest, cResult) val fResult = transformCatalystGetValueResponse(context, fRequest, cResult)
assertThat(fResult!!.resultCode).isEqualTo(GetValueResult.RESULT_OK) assertThat(fResult!!.resultCode).isEqualTo(GetValueResult.RESULT_OK)
with(fResult.metadata!!) { with(fResult.metadata!!) {
@@ -174,6 +175,8 @@ class PreferenceServiceRequestTransformerTest {
assertThat(isWritable).isTrue() assertThat(isWritable).isTrue()
assertThat(writeSensitivity) assertThat(writeSensitivity)
.isEqualTo(SettingsPreferenceMetadata.EXPECT_POST_CONFIRMATION) .isEqualTo(SettingsPreferenceMetadata.EXPECT_POST_CONFIRMATION)
assertThat(readPermissions).containsExactly("read_permission")
assertThat(writePermissions).containsExactly("write_permission")
assertThat(launchIntent).isNotNull() assertThat(launchIntent).isNotNull()
assertThat(launchIntent!!.component!!.className) assertThat(launchIntent!!.component!!.className)
.isEqualTo(SettingsHomepageActivity::class.java.name) .isEqualTo(SettingsHomepageActivity::class.java.name)
@@ -188,13 +191,14 @@ class PreferenceServiceRequestTransformerTest {
fun transformCatalystGetValueResponse_failure_returnsValidFrameworkResponse() { fun transformCatalystGetValueResponse_failure_returnsValidFrameworkResponse() {
val context: Context = ApplicationProvider.getApplicationContext() val context: Context = ApplicationProvider.getApplicationContext()
val fRequest = GetValueRequest.Builder("screen", "key").build() val fRequest = GetValueRequest.Builder("screen", "key").build()
val cResult = PreferenceGetterResponse( val cResult =
mapOf( PreferenceGetterResponse(
PreferenceCoordinate(fRequest.screenKey, fRequest.preferenceKey) to mapOf(
PreferenceCoordinate(fRequest.screenKey, fRequest.preferenceKey) to
PreferenceGetterErrorCode.NOT_FOUND PreferenceGetterErrorCode.NOT_FOUND
), ),
emptyMap() emptyMap(),
) )
val fResult = transformCatalystGetValueResponse(context, fRequest, cResult) val fResult = transformCatalystGetValueResponse(context, fRequest, cResult)
with(fResult!!) { with(fResult!!) {
assertThat(resultCode).isEqualTo(GetValueResult.RESULT_UNSUPPORTED) assertThat(resultCode).isEqualTo(GetValueResult.RESULT_UNSUPPORTED)
@@ -214,13 +218,15 @@ class PreferenceServiceRequestTransformerTest {
@Test @Test
fun transformFrameworkSetValueRequest_typeBoolean_returnsValidCatalystRequest() { fun transformFrameworkSetValueRequest_typeBoolean_returnsValidCatalystRequest() {
val fRequest = SetValueRequest.Builder( val fRequest =
"screen", SetValueRequest.Builder(
"pref", "screen",
SettingsPreferenceValue.Builder(SettingsPreferenceValue.TYPE_BOOLEAN) "pref",
.setBooleanValue(true) SettingsPreferenceValue.Builder(SettingsPreferenceValue.TYPE_BOOLEAN)
.setBooleanValue(true)
.build(),
)
.build() .build()
).build()
val cRequest = transformFrameworkSetValueRequest(fRequest) val cRequest = transformFrameworkSetValueRequest(fRequest)
with(cRequest!!) { with(cRequest!!) {
assertThat(screenKey).isEqualTo(fRequest.screenKey) assertThat(screenKey).isEqualTo(fRequest.screenKey)
@@ -232,13 +238,15 @@ class PreferenceServiceRequestTransformerTest {
@Test @Test
fun transformFrameworkSetValueRequest_typeInt_returnsValidCatalystRequest() { fun transformFrameworkSetValueRequest_typeInt_returnsValidCatalystRequest() {
val fRequest = SetValueRequest.Builder( val fRequest =
"screen", SetValueRequest.Builder(
"pref", "screen",
SettingsPreferenceValue.Builder(SettingsPreferenceValue.TYPE_INT) "pref",
.setIntValue(5) SettingsPreferenceValue.Builder(SettingsPreferenceValue.TYPE_INT)
.setIntValue(5)
.build(),
)
.build() .build()
).build()
val cRequest = transformFrameworkSetValueRequest(fRequest) val cRequest = transformFrameworkSetValueRequest(fRequest)
with(cRequest!!) { with(cRequest!!) {
assertThat(screenKey).isEqualTo(fRequest.screenKey) assertThat(screenKey).isEqualTo(fRequest.screenKey)
@@ -250,59 +258,59 @@ class PreferenceServiceRequestTransformerTest {
@Test @Test
fun transformFrameworkSetValueRequest_typeString_returnsNull() { fun transformFrameworkSetValueRequest_typeString_returnsNull() {
val fRequest = SetValueRequest.Builder( val fRequest =
"screen", SetValueRequest.Builder(
"pref", "screen",
SettingsPreferenceValue.Builder(SettingsPreferenceValue.TYPE_STRING) "pref",
.setStringValue("value") SettingsPreferenceValue.Builder(SettingsPreferenceValue.TYPE_STRING)
.setStringValue("value")
.build(),
)
.build() .build()
).build()
val cRequest = transformFrameworkSetValueRequest(fRequest) val cRequest = transformFrameworkSetValueRequest(fRequest)
assertThat(cRequest).isNull() assertThat(cRequest).isNull()
} }
@Test @Test
fun transformCatalystSetValueResponse_returnsValidFrameworkResponse() { fun transformCatalystSetValueResponse_returnsValidFrameworkResponse() {
assertThat( assertThat(transformCatalystSetValueResponse(PreferenceSetterResult.OK).resultCode)
transformCatalystSetValueResponse(PreferenceSetterResult.OK).resultCode .isEqualTo(SetValueResult.RESULT_OK)
).isEqualTo(SetValueResult.RESULT_OK)
assertThat(transformCatalystSetValueResponse(PreferenceSetterResult.UNAVAILABLE).resultCode)
.isEqualTo(SetValueResult.RESULT_UNAVAILABLE)
assertThat(transformCatalystSetValueResponse(PreferenceSetterResult.DISABLED).resultCode)
.isEqualTo(SetValueResult.RESULT_DISABLED)
assertThat(transformCatalystSetValueResponse(PreferenceSetterResult.UNSUPPORTED).resultCode)
.isEqualTo(SetValueResult.RESULT_UNSUPPORTED)
assertThat(transformCatalystSetValueResponse(PreferenceSetterResult.DISALLOW).resultCode)
.isEqualTo(SetValueResult.RESULT_DISALLOW)
assertThat( assertThat(
transformCatalystSetValueResponse(PreferenceSetterResult.UNAVAILABLE).resultCode transformCatalystSetValueResponse(PreferenceSetterResult.REQUIRE_APP_PERMISSION)
).isEqualTo(SetValueResult.RESULT_UNAVAILABLE) .resultCode
)
.isEqualTo(SetValueResult.RESULT_REQUIRE_APP_PERMISSION)
assertThat( assertThat(
transformCatalystSetValueResponse(PreferenceSetterResult.DISABLED).resultCode transformCatalystSetValueResponse(PreferenceSetterResult.REQUIRE_USER_AGREEMENT)
).isEqualTo(SetValueResult.RESULT_DISABLED) .resultCode
)
.isEqualTo(SetValueResult.RESULT_REQUIRE_USER_CONSENT)
assertThat(transformCatalystSetValueResponse(PreferenceSetterResult.RESTRICTED).resultCode)
.isEqualTo(SetValueResult.RESULT_RESTRICTED)
assertThat( assertThat(
transformCatalystSetValueResponse(PreferenceSetterResult.UNSUPPORTED).resultCode transformCatalystSetValueResponse(PreferenceSetterResult.INVALID_REQUEST).resultCode
).isEqualTo(SetValueResult.RESULT_UNSUPPORTED) )
.isEqualTo(SetValueResult.RESULT_INVALID_REQUEST)
assertThat( assertThat(
transformCatalystSetValueResponse(PreferenceSetterResult.DISALLOW).resultCode transformCatalystSetValueResponse(PreferenceSetterResult.INTERNAL_ERROR).resultCode
).isEqualTo(SetValueResult.RESULT_DISALLOW) )
.isEqualTo(SetValueResult.RESULT_INTERNAL_ERROR)
assertThat(
transformCatalystSetValueResponse(PreferenceSetterResult.REQUIRE_APP_PERMISSION)
.resultCode
).isEqualTo(SetValueResult.RESULT_REQUIRE_APP_PERMISSION)
assertThat(
transformCatalystSetValueResponse(PreferenceSetterResult.REQUIRE_USER_AGREEMENT)
.resultCode
).isEqualTo(SetValueResult.RESULT_REQUIRE_USER_CONSENT)
assertThat(
transformCatalystSetValueResponse(PreferenceSetterResult.RESTRICTED).resultCode
).isEqualTo(SetValueResult.RESULT_RESTRICTED)
assertThat(
transformCatalystSetValueResponse(PreferenceSetterResult.INVALID_REQUEST).resultCode
).isEqualTo(SetValueResult.RESULT_INVALID_REQUEST)
assertThat(
transformCatalystSetValueResponse(PreferenceSetterResult.INTERNAL_ERROR).resultCode
).isEqualTo(SetValueResult.RESULT_INTERNAL_ERROR)
} }
} }

View File

@@ -200,7 +200,6 @@ class WepNetworksPreferenceControllerTest {
fun whenClick_aapmEnabled_openDialog() { fun whenClick_aapmEnabled_openDialog() {
mockAapmManager.stub { mockAapmManager.stub {
on { isAdvancedProtectionEnabled } doReturn true on { isAdvancedProtectionEnabled } doReturn true
on { createSupportIntent(any(), any()) } doReturn Intent()
} }
doNothing().whenever(context).startActivity(any()) doNothing().whenever(context).startActivity(any())
composeTestRule.setContent { controller.Content() } composeTestRule.setContent { controller.Content() }