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>
</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
android:name="Settings$ManageExternalStorageActivity"
android:knownActivityEmbeddingCerts="@array/config_known_host_certs"
@@ -5440,7 +5453,7 @@
</service>
<receiver android:name="com.android.settings.connecteddevice.audiosharing.AudioSharingReceiver"
android:exported="false">
android:exported="true">
<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_STOP" />

View File

@@ -14,15 +14,217 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetBottom="@dimen/pointer_fill_style_circle_offset"
android:insetLeft="@dimen/pointer_fill_style_circle_offset"
android:insetRight="@dimen/pointer_fill_style_circle_offset"
android:insetTop="@dimen/pointer_fill_style_circle_offset">
<shape android:shape="oval">
<size
android:width="@dimen/pointer_fill_style_circle_inner_diameter"
android:height="@dimen/pointer_fill_style_circle_inner_diameter" />
<solid android:color="@android:color/white" />
</shape>
</inset>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" >
<item
android:state_hovered="true"
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="@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_marginBottom="16dp"
android:visibility="gone"
android:alpha="0"
android:contentDescription="@string/hourly_battery_usage_chart"
android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary" />

View File

@@ -42,7 +42,6 @@
android:layout_height="@dimen/pointer_fill_container_height"
android:layout_marginBottom="@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:paddingTop="@dimen/pointer_fill_style_container_padding"
android:paddingBottom="@dimen/pointer_fill_style_container_padding">
@@ -53,19 +52,18 @@
android:focusable="false"
android:clickable="false"
android:importantForAccessibility="no"
android:layout_weight="4" />
android:layout_weight="1" />
<ImageView
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_height="@dimen/pointer_fill_style_circle_diameter"
android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
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:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background"
android:src="@drawable/pointer_icon_fill_color_foreground" />
android:src="@drawable/pointer_icon_fill_color_background" />
<View
android:layout_width="0dp"
@@ -73,19 +71,18 @@
android:focusable="false"
android:clickable="false"
android:importantForAccessibility="no"
android:layout_weight="3" />
android:layout_weight="1" />
<ImageView
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_height="@dimen/pointer_fill_style_circle_diameter"
android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
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:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background"
android:src="@drawable/pointer_icon_fill_color_foreground" />
android:src="@drawable/pointer_icon_fill_color_background" />
<View
android:layout_width="0dp"
@@ -93,19 +90,18 @@
android:focusable="false"
android:clickable="false"
android:importantForAccessibility="no"
android:layout_weight="3" />
android:layout_weight="1" />
<ImageView
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_height="@dimen/pointer_fill_style_circle_diameter"
android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
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:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background"
android:src="@drawable/pointer_icon_fill_color_foreground" />
android:src="@drawable/pointer_icon_fill_color_background" />
<View
android:layout_width="0dp"
@@ -113,19 +109,18 @@
android:focusable="false"
android:clickable="false"
android:importantForAccessibility="no"
android:layout_weight="3" />
android:layout_weight="1" />
<ImageView
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_height="@dimen/pointer_fill_style_circle_diameter"
android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
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:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background"
android:src="@drawable/pointer_icon_fill_color_foreground" />
android:src="@drawable/pointer_icon_fill_color_background" />
<View
android:layout_width="0dp"
@@ -133,19 +128,18 @@
android:focusable="false"
android:clickable="false"
android:importantForAccessibility="no"
android:layout_weight="3" />
android:layout_weight="1" />
<ImageView
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_height="@dimen/pointer_fill_style_circle_diameter"
android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
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:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background"
android:src="@drawable/pointer_icon_fill_color_foreground" />
android:src="@drawable/pointer_icon_fill_color_background" />
<View
android:layout_width="0dp"
@@ -153,19 +147,18 @@
android:focusable="false"
android:clickable="false"
android:importantForAccessibility="no"
android:layout_weight="3" />
android:layout_weight="1" />
<ImageView
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_height="@dimen/pointer_fill_style_circle_diameter"
android:layout_height="@dimen/pointer_fill_style_circle_background_outer_diameter"
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:scaleType="center"
android:background="@drawable/pointer_icon_fill_color_background"
android:src="@drawable/pointer_icon_fill_color_foreground" />
android:src="@drawable/pointer_icon_fill_color_background" />
<View
android:layout_width="0dp"
@@ -173,7 +166,7 @@
android:focusable="false"
android:clickable="false"
android:importantForAccessibility="no"
android:layout_weight="4" />
android:layout_weight="1" />
</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-->
<color name="switch_bar_state_disabled_color">#1FE3E3E3</color>
<!-- Pointer fill color setting outline color-->
<color name="pointer_fill_outline_color">#FFFFFF</color>
<!-- Pointer fill color setting, fill selector's background color on hover. -->
<color name="pointer_fill_hovered_color">#3E373C</color>
<!-- Connected displays -->
<color name="display_topology_background_color">@color/settingslib_color_charcoal</color>

View File

@@ -219,8 +219,8 @@
<!-- Switch bar disabled state color-->
<color name="switch_bar_state_disabled_color">#1F1F1F1F</color>
<!-- Pointer fill color setting outline color-->
<color name="pointer_fill_outline_color">#000000</color>
<!-- Pointer fill color setting, fill selector's background color on hover. -->
<color name="pointer_fill_hovered_color">#1C201A1E</color>
<!-- Connected displays -->
<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>
<!-- Pointer -->
<dimen name="pointer_fill_container_height">88dp</dimen>
<dimen name="pointer_fill_container_max_width">448dp</dimen>
<dimen name="pointer_fill_container_height">80dp</dimen>
<dimen name="pointer_fill_container_max_width">468dp</dimen>
<dimen name="pointer_fill_style_container_padding">32dp</dimen>
<dimen name="pointer_fill_style_circle_diameter">56dp</dimen>
<dimen name="pointer_fill_style_circle_hover_selected_diameter">52dp</dimen>
<dimen name="pointer_fill_style_circle_hover_diameter">56dp</dimen>
<dimen name="pointer_fill_style_circle_selected_diameter">50dp</dimen>
<dimen name="pointer_fill_style_circle_inner_diameter">40dp</dimen>
<dimen name="pointer_fill_style_circle_offset">8dp</dimen>
<dimen name="pointer_fill_style_target_width">64dp</dimen>
<dimen name="pointer_fill_style_circle_diameter">39dp</dimen>
<dimen name="pointer_fill_style_circle_outline_diameter">39.5dp</dimen>
<dimen name="pointer_fill_style_circle_background_corner_radius">20dp</dimen>
<dimen name="pointer_fill_style_circle_background_corner_inner_radius">16dp</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_selected_offset">3dp</dimen>
<dimen name="pointer_fill_style_checkmark_selected_padding">14dp</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_fill_style_checkmark_selected_padding">16dp</dimen>
<dimen name="pointer_fill_style_checkmark_size">24dp</dimen>
<dimen name="pointer_stroke_style_padding">8dp</dimen>
<dimen name="pointer_stroke_style_text_padding">21dp</dimen>
<dimen name="pointer_scale_padding">8dp</dimen>

View File

@@ -46,4 +46,7 @@
<!-- For screen lock options button -->
<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>

View File

@@ -5415,8 +5415,10 @@
<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] -->
<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] -->
<string name="accessibility_notification_high_contrast_text_content">High contrast text has a new look and feel.</string>
<!-- Title for the notification that high contrast text has been replaced. [CHAR LIMIT=NONE] -->
<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] -->
<string name="accessibility_notification_high_contrast_text_action">Open Settings</string>
<!-- 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] -->
<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] -->
<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] -->
<string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string>
<!-- 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
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] -->
<string name="condition_turn_off">Turn off</string>

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,10 @@
package com.android.settings.accessibility;
import android.content.Context;
import android.text.Html;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
@@ -32,4 +36,24 @@ public class HearingDeviceFooterPreferenceController extends
protected String getIntroductionTitle() {
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 {
private static final String TAG = HighContrastTextMigrationReceiver.class.getSimpleName();
@VisibleForTesting
static final String NOTIFICATION_CHANNEL = "high_contrast_text_notification_channel";
static final String NOTIFICATION_CHANNEL = "accessibility_notification_channel";
@VisibleForTesting
static final String ACTION_RESTORED =
"com.android.settings.accessibility.ACTION_HIGH_CONTRAST_TEXT_RESTORED";
@@ -120,7 +120,7 @@ public class HighContrastTextMigrationReceiver extends BroadcastReceiver {
NOTIFICATION_CHANNEL)
.setSmallIcon(R.drawable.ic_settings_24dp)
.setContentTitle(context.getString(
R.string.accessibility_toggle_high_text_contrast_preference_title))
R.string.accessibility_notification_high_contrast_text_title))
.setContentText(context.getString(
R.string.accessibility_notification_high_contrast_text_content))
.setAutoCancel(true);
@@ -149,8 +149,7 @@ public class HighContrastTextMigrationReceiver extends BroadcastReceiver {
context.getSystemService(NotificationManager.class);
NotificationChannel notificationChannel = new NotificationChannel(
NOTIFICATION_CHANNEL,
context.getString(
R.string.accessibility_toggle_high_text_contrast_preference_title),
context.getString(R.string.accessibility_settings),
NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(notificationChannel);
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.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_RIGHT;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.util.ArrayMap;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
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 java.util.List;
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
* 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. */
public interface OnIconClickListener {
/** 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);
private static final int ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED = 0;
private static final int ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED = 1;
@Nullable
private OnIconClickListener mListener;
private AmbientVolumeUiListener mListener;
@Nullable
private View mExpandIcon;
@Nullable
@@ -78,27 +68,21 @@ public class AmbientVolumePreference extends PreferenceGroup {
private boolean mExpanded = false;
private boolean mMutable = false;
private boolean mMuted = false;
private Map<Integer, SeekBarPreference> mSideToSliderMap = new ArrayMap<>();
/**
* 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 final BiMap<Integer, SeekBarPreference> mSideToSliderMap = HashBiMap.create();
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) {
super(context, null);
setLayoutResource(R.layout.preference_ambient_volume);
@@ -138,7 +122,8 @@ public class AmbientVolumePreference extends PreferenceGroup {
updateExpandIcon();
}
void setExpandable(boolean expandable) {
@Override
public void setExpandable(boolean expandable) {
mExpandable = expandable;
if (!mExpandable) {
setExpanded(false);
@@ -146,11 +131,13 @@ public class AmbientVolumePreference extends PreferenceGroup {
updateExpandIcon();
}
boolean isExpandable() {
@Override
public boolean isExpandable() {
return mExpandable;
}
void setExpanded(boolean expanded) {
@Override
public void setExpanded(boolean expanded) {
if (!mExpandable && expanded) {
return;
}
@@ -159,11 +146,13 @@ public class AmbientVolumePreference extends PreferenceGroup {
updateLayout();
}
boolean isExpanded() {
@Override
public boolean isExpanded() {
return mExpanded;
}
void setMutable(boolean mutable) {
@Override
public void setMutable(boolean mutable) {
mMutable = mutable;
if (!mMutable) {
mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
@@ -172,11 +161,13 @@ public class AmbientVolumePreference extends PreferenceGroup {
updateVolumeIcon();
}
boolean isMutable() {
@Override
public boolean isMutable() {
return mMutable;
}
void setMuted(boolean muted) {
@Override
public void setMuted(boolean muted) {
if (!mMutable && muted) {
return;
}
@@ -189,25 +180,35 @@ public class AmbientVolumePreference extends PreferenceGroup {
updateVolumeIcon();
}
boolean isMuted() {
@Override
public boolean isMuted() {
return mMuted;
}
void setOnIconClickListener(@Nullable OnIconClickListener listener) {
@Override
public void setListener(@Nullable AmbientVolumeUiListener listener) {
mListener = listener;
}
void setSliders(Map<Integer, SeekBarPreference> sideToSliderMap) {
mSideToSliderMap = sideToSliderMap;
for (SeekBarPreference preference : sideToSliderMap.values()) {
if (findPreference(preference.getKey()) == null) {
addPreference(preference);
@Override
public void setupSliders(@NonNull Map<Integer, BluetoothDevice> sideToDeviceMap) {
sideToDeviceMap.forEach((side, device) ->
createSlider(side, ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED + side));
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();
}
void setSliderEnabled(int side, boolean enabled) {
@Override
public void setSliderEnabled(int side, boolean enabled) {
SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null && slider.isEnabled() != 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);
if (slider != null && slider.getProgress() != 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);
if (slider != null) {
slider.setMin(min);
@@ -231,7 +234,8 @@ public class AmbientVolumePreference extends PreferenceGroup {
}
}
void updateLayout() {
@Override
public void updateLayout() {
mSideToSliderMap.forEach((side, slider) -> {
if (side == SIDE_UNIFIED) {
slider.setVisible(!mExpanded);
@@ -279,8 +283,7 @@ public class AmbientVolumePreference extends PreferenceGroup {
mExpandIcon.setVisibility(mExpandable ? VISIBLE : GONE);
mExpandIcon.setRotation(mExpanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED);
if (mExpandable) {
final int stringRes = mExpanded
? R.string.bluetooth_ambient_volume_control_collapse
final int stringRes = mExpanded ? R.string.bluetooth_ambient_volume_control_collapse
: R.string.bluetooth_ambient_volume_control_expand;
mExpandIcon.setContentDescription(getContext().getString(stringRes));
} else {
@@ -294,8 +297,7 @@ public class AmbientVolumePreference extends PreferenceGroup {
}
mVolumeIcon.setImageLevel(mMuted ? 0 : mVolumeLevel);
if (mMutable) {
final int stringRes = mMuted
? R.string.bluetooth_ambient_volume_unmute
final int stringRes = mMuted ? R.string.bluetooth_ambient_volume_unmute
: R.string.bluetooth_ambient_volume_mute;
mVolumeIcon.setContentDescription(getContext().getString(stringRes));
mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -304,4 +306,27 @@ public class AmbientVolumePreference extends PreferenceGroup {
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;
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.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.util.ArraySet;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.bluetooth.AmbientVolumeController;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.AmbientVolumeUiController;
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.VolumeControlProfile;
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.utils.ThreadUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
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 {
/** A {@link BluetoothDetailsController} that manages ambient volume preference. */
public class BluetoothDetailsAmbientVolumePreferenceController extends BluetoothDetailsController
implements OnStart, OnStop {
private static final boolean DEBUG = true;
private static final String TAG = "AmbientPrefController";
static final String KEY_AMBIENT_VOLUME = "ambient_volume";
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 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
private AmbientVolumePreference mPreference;
@Nullable
private Toast mToast;
private AmbientVolumeUiController mAmbientUiController;
public BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
@NonNull LocalBluetoothManager manager,
@@ -99,45 +60,42 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
@NonNull Lifecycle lifecycle) {
super(context, fragment, device, lifecycle);
mBluetoothManager = manager;
mLocalDataManager = new HearingDeviceLocalDataManager(context);
mLocalDataManager.setOnDeviceLocalDataChangeListener(this,
ThreadUtils.getBackgroundExecutor());
mVolumeController = new AmbientVolumeController(manager.getProfileManager(), this);
}
@VisibleForTesting
BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
public BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
@NonNull LocalBluetoothManager manager,
@NonNull PreferenceFragmentCompat fragment,
@NonNull CachedBluetoothDevice device,
@NonNull Lifecycle lifecycle,
@NonNull HearingDeviceLocalDataManager localSettings,
@NonNull AmbientVolumeController volumeController) {
@NonNull AmbientVolumeUiController uiController) {
super(context, fragment, device, lifecycle);
mBluetoothManager = manager;
mLocalDataManager = localSettings;
mVolumeController = volumeController;
mAmbientUiController = uiController;
}
@Override
protected void init(PreferenceScreen screen) {
mDeviceControls = screen.findPreference(KEY_HEARING_DEVICE_GROUP);
if (mDeviceControls == null) {
PreferenceCategory deviceControls = screen.findPreference(KEY_HEARING_DEVICE_GROUP);
if (deviceControls == null) {
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
public void onStart() {
ThreadUtils.postOnBackgroundThread(() -> {
mBluetoothManager.getEventManager().registerCallback(this);
mLocalDataManager.start();
mCachedDevices.forEach(device -> {
device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
device.getDevice());
});
if (mAmbientUiController != null) {
mAmbientUiController.start();
}
});
}
@@ -153,12 +111,9 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
@Override
public void onStop() {
ThreadUtils.postOnBackgroundThread(() -> {
mBluetoothManager.getEventManager().unregisterCallback(this);
mLocalDataManager.stop();
mCachedDevices.forEach(device -> {
device.unregisterCallback(this);
mVolumeController.unregisterCallback(device.getDevice());
});
if (mAmbientUiController != null) {
mAmbientUiController.stop();
}
});
}
@@ -167,16 +122,8 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
if (!isAvailable()) {
return;
}
boolean shouldShowAmbientControl = isAmbientControlAvailable();
if (shouldShowAmbientControl) {
if (mPreference != null) {
mPreference.setVisible(true);
}
loadRemoteDataToUi();
} else {
if (mPreference != null) {
mPreference.setVisible(false);
}
if (mAmbientUiController != null) {
mAmbientUiController.refresh();
}
}
@@ -191,424 +138,4 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
public String getPreferenceKey() {
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_MIN
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.PreferenceSummaryProvider
import com.android.settingslib.metadata.ReadWritePermit
@@ -52,7 +52,7 @@ import java.text.NumberFormat
// LINT.IfChange
class BrightnessLevelPreference :
PreferenceMetadata,
PersistentPreference<Float>,
FloatPersistentPreference,
PreferenceBinding,
PreferenceRestrictionMixin,
PreferenceSummaryProvider,
@@ -78,7 +78,7 @@ class BrightnessLevelPreference :
override val useAdminDisabledSummary: Boolean
get() = true
override fun intent(context: Context) =
override fun intent(context: Context): Intent? =
Intent(ACTION_SHOW_BRIGHTNESS_DIALOG)
.setPackage(Utils.SYSTEMUI_PACKAGE_NAME)
.putExtra(

View File

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

View File

@@ -32,6 +32,8 @@ import android.util.Pair;
import androidx.annotation.VisibleForTesting;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
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
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
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.res.TypedArray;
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.util.AttributeSet;
import android.view.PointerIcon;
@@ -41,6 +44,11 @@ import com.android.settings.R;
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;
@@ -82,11 +90,7 @@ public class PointerFillStylePreference extends Preference {
if (button == null) {
return;
}
int[] attrs = {com.android.internal.R.attr.pointerIconVectorFill};
try (TypedArray ta = getContext().obtainStyledAttributes(
PointerIcon.vectorFillStyleToResource(style), attrs)) {
button.getBackground().setTint(ta.getColor(0, Color.BLACK));
}
tintButtonByStyle(button, style);
button.setOnClickListener(
(v) -> {
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));
}
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) {
if (mButtonHolder == null) {
return;

View File

@@ -54,6 +54,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -345,6 +346,7 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
List<SubscriptionInfoEntity> availableSubInfoEntityList) {
List<SubscriptionInfoEntity> activeSubInfoEntityList =
availableSubInfoEntityList.stream()
.filter(entity -> Objects.nonNull(entity))
.filter(SubscriptionInfoEntity::isActiveSubscription)
.filter(SubscriptionInfoEntity::isSubscriptionVisible)
.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)
.setLaunchIntent(launchIntent.toIntent())
.setWriteSensitivity(sensitivity)
.setReadPermissions(readPermissionsList)
.setWritePermissions(writePermissionsList)
.build()
}

View File

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

View File

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

View File

@@ -203,7 +203,7 @@ public class HighContrastTextMigrationReceiverTest {
ShadowNotification shadowNotification = Shadows.shadowOf(notification);
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(
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 org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.util.ArrayMap;
import android.view.View;
@@ -40,6 +42,7 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.bluetooth.AmbientVolumeUi;
import org.junit.Before;
import org.junit.Rule;
@@ -69,14 +72,14 @@ public class AmbientVolumePreferenceTest {
@Spy
private Context mContext = ApplicationProvider.getApplicationContext();
@Mock
private AmbientVolumePreference.OnIconClickListener mListener;
private AmbientVolumeUi.AmbientVolumeUiListener mListener;
@Mock
private View mItemView;
private AmbientVolumePreference mPreference;
private ImageView mExpandIcon;
private ImageView mVolumeIcon;
private final Map<Integer, SeekBarPreference> mSideToSlidersMap = new ArrayMap<>();
private final Map<Integer, BluetoothDevice> mSideToDeviceMap = new ArrayMap<>();
@Before
public void setUp() {
@@ -84,13 +87,27 @@ public class AmbientVolumePreferenceTest {
PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext);
mPreference = new AmbientVolumePreference(mContext);
mPreference.setKey(KEY_AMBIENT_VOLUME);
mPreference.setOnIconClickListener(mListener);
mPreference.setListener(mListener);
mPreference.setExpandable(true);
mPreference.setMutable(true);
preferenceScreen.addPreference(mPreference);
prepareSliders();
mPreference.setSliders(mSideToSlidersMap);
prepareDevices();
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);
mVolumeIcon = new ImageView(mContext);
@@ -206,33 +223,16 @@ public class AmbientVolumePreferenceTest {
private void assertControlUiCorrect() {
final boolean expanded = mPreference.isExpanded();
assertThat(mSideToSlidersMap.get(SIDE_UNIFIED).isVisible()).isEqualTo(!expanded);
assertThat(mSideToSlidersMap.get(SIDE_LEFT).isVisible()).isEqualTo(expanded);
assertThat(mSideToSlidersMap.get(SIDE_RIGHT).isVisible()).isEqualTo(expanded);
Map<Integer, SeekBarPreference> sliders = mPreference.getSliders();
assertThat(sliders.get(SIDE_UNIFIED).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;
assertThat(mExpandIcon.getRotation()).isEqualTo(rotation);
}
private void prepareSliders() {
prepareSlider(SIDE_UNIFIED);
prepareSlider(SIDE_LEFT);
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);
private void prepareDevices() {
mSideToDeviceMap.put(SIDE_LEFT, mock(BluetoothDevice.class));
mSideToDeviceMap.put(SIDE_RIGHT, mock(BluetoothDevice.class));
}
}

View File

@@ -16,46 +16,25 @@
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_SLIDER;
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 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.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.ContentResolver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import androidx.preference.PreferenceCategory;
import com.android.settings.testutils.shadow.ShadowThreadUtils;
import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.bluetooth.AmbientVolumeController;
import com.android.settingslib.bluetooth.AmbientVolumeUiController;
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.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
@@ -69,41 +48,19 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
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.Map;
import java.util.Set;
import java.util.concurrent.Executor;
/** Tests for {@link BluetoothDetailsAmbientVolumePreferenceController}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
BluetoothDetailsAmbientVolumePreferenceControllerTest.ShadowGlobal.class,
ShadowThreadUtils.class
})
public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
BluetoothDetailsControllerTestBase {
@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
private LocalBluetoothManager mBluetoothManager;
@Mock
@@ -113,9 +70,9 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
@Mock
private VolumeControlProfile mVolumeControlProfile;
@Mock
private AmbientVolumeController mVolumeController;
@Mock
private Handler mTestHandler;
@Mock
private AmbientVolumeUiController mUiController;
private BluetoothDetailsAmbientVolumePreferenceController mController;
@@ -124,24 +81,16 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
super.setUp();
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);
deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
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(mTestHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
@@ -152,283 +101,42 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
}
@Test
public void init_deviceWithoutMember_controlNotExpandable() {
prepareDevice(/* hasMember= */ false);
public void init_preferenceAdded() {
mController.init(mScreen);
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpandable()).isFalse();
}
@Test
public void init_deviceWithMember_controlExpandable() {
prepareDevice(/* hasMember= */ true);
public void refresh_deviceNotSupportVcp_verifyUiControllerNoRefresh() {
when(mCachedDevice.getProfiles()).thenReturn(List.of());
mController.init(mScreen);
mController.refresh();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpandable()).isTrue();
verify(mUiController, never()).refresh();
}
@Test
public void onDeviceLocalDataChange_noMemberAndExpanded_uiCorrectAndDataUpdated() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(0).groupAmbient(0).ambientControlExpanded(true).build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
public void refresh_deviceSupportVcp_verifyUiControllerRefresh() {
when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
shadowOf(Looper.getMainLooper()).idle();
mController.refresh();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpanded()).isFalse();
verifyDeviceDataUpdated(mDevice);
verify(mUiController).refresh();
}
@Test
public void onDeviceLocalDataChange_noMemberAndCollapsed_uiCorrectAndDataUpdated() {
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);
public void onStart_verifyUiControllerStart() {
mController.onStart();
verify(mLocalDataManager, atLeastOnce()).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));
verify(mUiController).start();
}
@Test
public void onStop_localDataManagerStopAndCallbackUnregistered() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
public void onStop_verifyUiControllerStop() {
mController.onStop();
verify(mLocalDataManager).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<>());
}
verify(mUiController).stop();
}
}

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.ADVANCED_PROTECTION_SYSTEM_ENTITY;
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.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -40,7 +45,6 @@ import android.os.UserManager;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.security.advancedprotection.AdvancedProtectionManager;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -48,6 +52,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@@ -59,8 +64,6 @@ public class ActionDisabledByAdminDialogTest {
@Mock
private DevicePolicyManager mDevicePolicyManager;
@Mock
private AdvancedProtectionManager mAdvancedProtectionManager;
private ActionDisabledByAdminDialog mDialog;
private final ComponentName mAdminComponent = new ComponentName("admin", "adminclass");
@@ -70,8 +73,6 @@ public class ActionDisabledByAdminDialogTest {
MockitoAnnotations.initMocks(this);
mDialog = spy(new ActionDisabledByAdminDialog());
doReturn(mDevicePolicyManager).when(mDialog).getSystemService(DevicePolicyManager.class);
doReturn(mAdvancedProtectionManager).when(mDialog).getSystemService(
AdvancedProtectionManager.class);
}
@Test
@@ -118,24 +119,28 @@ public class ActionDisabledByAdminDialogTest {
advancedProtectionAuthority, UserHandle.of(userId), mAdminComponent);
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();
dialogIntent.putExtra(Intent.EXTRA_USER_ID, userId);
dialogIntent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, userRestriction);
when(mDevicePolicyManager.getEnforcingAdmin(userId, userRestriction))
.thenReturn(advancedProtectionEnforcingAdmin);
when(mAdvancedProtectionManager.createSupportIntentForPolicyIdentifierOrRestriction(
userRestriction, /* type */ null)).thenReturn(apmIntent);
doNothing().when(mDialog).startActivityAsUser(apmIntent, UserHandle.of(userId));
doNothing().when(mDialog).startActivityAsUser(any(), eq(UserHandle.of(userId)));
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());
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)

View File

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

View File

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