Snap for 11865526 from 1a5eb3ed59 to 24Q3-release
Change-Id: Id0d226a30060d55daa9185fe113478cac53b1461
This commit is contained in:
@@ -2777,6 +2777,7 @@
|
||||
<activity android:name=".biometrics.fingerprint2.ui.enrollment.activity.FingerprintEnrollmentV2Activity"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.MANAGE_FINGERPRINT"
|
||||
android:configChanges="density"
|
||||
android:theme="@style/GlifTheme.Light">
|
||||
<intent-filter>
|
||||
<action android:name="android.settings.FINGERPRINT_SETUP" />
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@*android:color/ripple_material_light">
|
||||
<item
|
||||
android:left="?android:attr/listPreferredItemPaddingStart"
|
||||
android:right="?android:attr/listPreferredItemPaddingEnd">
|
||||
<shape android:shape="rectangle">
|
||||
<solid
|
||||
android:color="@color/settingslib_materialColorPrimaryContainer" />
|
||||
<corners
|
||||
android:radius="?android:attr/dialogCornerRadius" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:attr/colorControlHighlight">
|
||||
<item android:id="@android:id/mask"
|
||||
android:left="?android:attr/listPreferredItemPaddingStart"
|
||||
android:right="?android:attr/listPreferredItemPaddingEnd">
|
||||
<shape android:shape="rectangle">
|
||||
<solid
|
||||
android:color="@android:color/white" />
|
||||
<corners
|
||||
android:radius="?android:attr/dialogCornerRadius" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
25
res/drawable/ic_modes_event.xml
Normal file
25
res/drawable/ic_modes_event.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dp"
|
||||
android:height="24.0dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
|
||||
<path android:fillColor="@android:color/white"
|
||||
android:pathData="M17.0,12.0l-5.0,0.0l0.0,5.0l5.0,0.0l0.0,-5.0zM16.0,1.0l0.0,2.0L8.0,3.0L8.0,1.0L6.0,1.0l0.0,2.0L5.0,3.0c-1.11,0.0 -1.9,0.9 -1.99,2.0L3.0,19.0c0.0,1.0 0.89,2.0 2.0,2.0l14.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L21.0,5.0c0.0,-1.1 -0.9,-2.0 -2.0,-2.0l-1.0,0.0L18.0,1.0l-2.0,0.0zm3.0,18.0L5.0,19.0L5.0,8.0l14.0,0.0l0.0,11.0z"/>
|
||||
</vector>
|
||||
26
res/drawable/ic_modes_time.xml
Normal file
26
res/drawable/ic_modes_time.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dp"
|
||||
android:height="24.0dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
|
||||
<path android:fillColor="@android:color/white"
|
||||
android:pathData="M612,668L668,612L520,464L520,280L440,280L440,496L612,668ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM480,800Q613,800 706.5,706.5Q800,613 800,480Q800,347 706.5,253.5Q613,160 480,160Q347,160 253.5,253.5Q160,347 160,480Q160,613 253.5,706.5Q347,800 480,800Z"/>
|
||||
</vector>
|
||||
100
res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml
Normal file
100
res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml
Normal file
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/udfps_layout"
|
||||
style="?attr/fingerprint_layout_theme"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- This is used to grab style attributes and apply them
|
||||
to this layout -->
|
||||
<com.google.android.setupdesign.GlifLayout
|
||||
android:id="@+id/dummy_glif_layout"
|
||||
style="?attr/fingerprint_layout_theme"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sud_layout_icon"
|
||||
style="@style/SudGlifIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scaleType="fitStart"
|
||||
android:src="@drawable/ic_lock" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
style="@style/SudGlifHeaderTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="2"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
style="@style/SudDescription.Glif"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="3"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
/>
|
||||
|
||||
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:id="@+id/illustration_lottie"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="gone"
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_loop="true"
|
||||
app:lottie_speed=".85"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/layout_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
android:clipToPadding="false"
|
||||
>
|
||||
|
||||
<include layout="@layout/fingerprint_v2_udfps_enroll_view" />
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/setup_wizard_layout"
|
||||
android:id="@+id/udfps_layout"
|
||||
style="?attr/fingerprint_layout_theme"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -17,28 +17,34 @@
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/SearchBarStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1">
|
||||
<Toolbar
|
||||
android:id="@+id/search_action_bar_unified"
|
||||
style="@style/SearchBarStyle_v2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/search_action_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/search_bar_height"
|
||||
android:paddingStart="8dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="72dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:background="@drawable/search_bar_selected_background"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
android:nextFocusForward="@+id/homepage_container"
|
||||
android:contentInsetStartWithNavigation="@dimen/search_bar_content_inset"
|
||||
android:navigationIcon="@drawable/ic_homepage_search">
|
||||
android:background="@drawable/search_bar_selected_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/ic_homepage_search" />
|
||||
<TextView
|
||||
android:id="@+id/search_bar_title"
|
||||
style="@style/TextAppearance.SearchBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:layout_gravity="start"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/search_settings"/>
|
||||
</Toolbar>
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/app_bar_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:padding="6dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<include layout="@layout/search_bar_unified_version"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -38,7 +38,6 @@
|
||||
android:id="@+id/suggestion_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd" />
|
||||
|
||||
@@ -62,19 +61,21 @@
|
||||
android:id="@+id/app_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:elevation="0dp"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
android:keyboardNavigationCluster="false">
|
||||
<LinearLayout
|
||||
android:id="@+id/app_bar_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:minHeight="76dp"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="24dp"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<include layout="@layout/search_bar_unified_version"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/homepage_app_bar_unified_view"
|
||||
layout="@layout/settings_homepage_app_bar_unified_layout"/>
|
||||
</LinearLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -459,6 +459,13 @@
|
||||
<item name="cardElevation">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="SearchBarStyle_v2">
|
||||
<item name="cardCornerRadius">40dp</item>
|
||||
<item name="cardElevation">0dp</item>
|
||||
<item name="strokeWidth">1dp</item>
|
||||
<item name="strokeColor">@color/settingslib_materialColorOutlineVariant</item>
|
||||
</style>
|
||||
|
||||
<style name="ConditionCardBorderlessButton"
|
||||
parent="android:Widget.DeviceDefault.Button.Borderless">
|
||||
<item name="android:textColor">?android:attr/colorAccent</item>
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
<style name="Theme.Settings.HomeBase" parent="Theme.Settings.NoActionBar">
|
||||
<item name="colorPrimary">@*android:color/primary_device_default_settings_light</item>
|
||||
<item name="colorAccent">@*android:color/accent_device_default_light</item>
|
||||
<item name="android:listPreferredItemPaddingStart">16dp</item>
|
||||
<item name="preferenceTheme">@style/SettingsPreferenceTheme</item>
|
||||
|
||||
<!-- action bar, needed for search bar icon tinting -->
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/zen_modes_list_title" >
|
||||
|
||||
<!-- TODO: b/308819292 - implement page, delete this test preference -->
|
||||
<Preference
|
||||
android:key="zen_mode_test" />
|
||||
<com.android.settingslib.widget.LayoutPreference
|
||||
android:key="header"
|
||||
android:layout="@layout/settings_entity_header" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -22,12 +22,14 @@
|
||||
|
||||
<PreferenceCategory
|
||||
android:order="-140"
|
||||
android:key="top_level_account_category">
|
||||
android:key="top_level_account_category"
|
||||
android:layout="@layout/settingslib_preference_category_no_title">
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:order="-130"
|
||||
android:key="top_level_connectivity_category">
|
||||
android:key="top_level_connectivity_category"
|
||||
android:layout="@layout/settingslib_preference_category_no_title">
|
||||
<com.android.settings.widget.HomepagePreference
|
||||
android:fragment="com.android.settings.network.NetworkDashboardFragment"
|
||||
android:icon="@drawable/ic_settings_wireless_filled"
|
||||
@@ -51,7 +53,8 @@
|
||||
|
||||
<PreferenceCategory
|
||||
android:order="-120"
|
||||
android:key="top_level_personalize_category">
|
||||
android:key="top_level_personalize_category"
|
||||
android:layout="@layout/settingslib_preference_category_no_title">
|
||||
<com.android.settings.widget.HomepagePreference
|
||||
android:fragment="com.android.settings.applications.AppDashboardFragment"
|
||||
android:icon="@drawable/ic_apps_filled"
|
||||
@@ -111,7 +114,8 @@
|
||||
|
||||
<PreferenceCategory
|
||||
android:order="-110"
|
||||
android:key="top_level_system_info_category">
|
||||
android:key="top_level_system_info_category"
|
||||
android:layout="@layout/settingslib_preference_category_no_title">
|
||||
<com.android.settings.widget.HomepagePreference
|
||||
android:fragment="com.android.settings.deviceinfo.StorageDashboardFragment"
|
||||
android:icon="@drawable/ic_storage_filled"
|
||||
@@ -154,7 +158,8 @@
|
||||
|
||||
<PreferenceCategory
|
||||
android:order="-100"
|
||||
android:key="top_level_security_privacy_category">
|
||||
android:key="top_level_security_privacy_category"
|
||||
android:layout="@layout/settingslib_preference_category_no_title">
|
||||
<com.android.settings.widget.HomepagePreference
|
||||
android:icon="@drawable/ic_settings_safety_center_filled"
|
||||
android:key="top_level_safety_center"
|
||||
@@ -203,20 +208,22 @@
|
||||
android:summary="@string/summary_placeholder"
|
||||
settings:highlightableMenuKey="@string/menu_key_accounts"
|
||||
settings:controller="com.android.settings.accounts.TopLevelAccountEntryPreferenceController"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:order="100"
|
||||
android:key="top_level_support_category">
|
||||
<com.android.settings.widget.HomepagePreference
|
||||
android:key="top_level_emergency"
|
||||
android:title="@string/emergency_settings_preference_title"
|
||||
android:summary="@string/emergency_dashboard_summary"
|
||||
android:icon="@drawable/ic_settings_emergency_filled"
|
||||
android:order="-30"
|
||||
android:order="10"
|
||||
android:fragment="com.android.settings.emergency.EmergencyDashboardFragment"
|
||||
settings:isPreferenceVisible="@bool/config_show_emergency_settings"
|
||||
settings:highlightableMenuKey="@string/menu_key_emergency"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:order="100"
|
||||
android:key="top_level_support_category"
|
||||
android:layout="@layout/settingslib_preference_category_no_title">
|
||||
|
||||
<com.android.settings.widget.HomepagePreference
|
||||
android:fragment="com.android.settings.accessibility.AccessibilitySettings"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.data.repository
|
||||
|
||||
import android.os.Build
|
||||
|
||||
/** Indicates if the developer has debugging features enabled. */
|
||||
interface DebuggingRepository {
|
||||
|
||||
/** A function that will return if a build is debuggable */
|
||||
fun isDebuggingEnabled(): Boolean
|
||||
/** A function that will return if udfps enrollment should be swapped with debug repos */
|
||||
fun isUdfpsEnrollmentDebuggingEnabled(): Boolean
|
||||
}
|
||||
|
||||
class DebuggingRepositoryImpl : DebuggingRepository {
|
||||
/**
|
||||
* This flag can be flipped by the engineer which should allow for certain debugging features to
|
||||
* be enabled.
|
||||
*/
|
||||
private val isBuildDebuggable = Build.IS_DEBUGGABLE
|
||||
/** This flag indicates if udfps should use debug repos to supply data to its various views. */
|
||||
private val udfpsEnrollmentDebugEnabled = true
|
||||
|
||||
override fun isDebuggingEnabled(): Boolean {
|
||||
return isBuildDebuggable
|
||||
}
|
||||
|
||||
override fun isUdfpsEnrollmentDebuggingEnabled(): Boolean {
|
||||
return isDebuggingEnabled() && udfpsEnrollmentDebugEnabled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.data.repository
|
||||
|
||||
import android.graphics.Point
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* This repository simulates touch events. This is mainly used to debug accessibility and ensure
|
||||
* that talkback is correct.
|
||||
*/
|
||||
interface SimulatedTouchEventsRepository {
|
||||
/**
|
||||
* A flow simulating user touches.
|
||||
*/
|
||||
val touchExplorationDebug: Flow<Point>
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.data.repository
|
||||
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensor
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.android.systemui.biometrics.shared.model.SensorStrength
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
/**
|
||||
* This class is used to simulate enroll data. This has two major use cases. 1). Ease of Development
|
||||
* 2). Bug Fixes
|
||||
*/
|
||||
class UdfpsEnrollDebugRepositoryImpl :
|
||||
FingerprintEnrollInteractor, FingerprintSensorRepository, SimulatedTouchEventsRepository {
|
||||
|
||||
override suspend fun enroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason) = flow {
|
||||
emit(FingerEnrollState.OverlayShown)
|
||||
delay(200)
|
||||
emit(FingerEnrollState.EnrollHelp(helpMsgId, "Hello world"))
|
||||
delay(200)
|
||||
emit(FingerEnrollState.EnrollProgress(15, 16))
|
||||
delay(300)
|
||||
emit(FingerEnrollState.EnrollHelp(helpMsgId, "Hello world"))
|
||||
delay(1000)
|
||||
emit(FingerEnrollState.EnrollProgress(14, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(13, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(12, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(11, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(10, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(9, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(8, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(7, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(6, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(5, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(4, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(3, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(2, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(1, 16))
|
||||
delay(500)
|
||||
emit(FingerEnrollState.EnrollProgress(0, 16))
|
||||
}
|
||||
|
||||
/** Provides touch events to the UdfpsEnrollFragment */
|
||||
override val touchExplorationDebug: Flow<Point> = flow {
|
||||
delay(2000)
|
||||
emit(pointToLeftOfSensor(sensorRect))
|
||||
delay(2000)
|
||||
emit(pointBelowSensor(sensorRect))
|
||||
delay(2000)
|
||||
emit(pointToRightOfSensor(sensorRect))
|
||||
delay(2000)
|
||||
emit(pointAboveSensor(sensorRect))
|
||||
}
|
||||
|
||||
override val fingerprintSensor: Flow<FingerprintSensor> = flowOf(sensorProps)
|
||||
|
||||
private fun pointToLeftOfSensor(sensorLocation: Rect) =
|
||||
Point(sensorLocation.right + 5, sensorLocation.centerY())
|
||||
|
||||
private fun pointToRightOfSensor(sensorLocation: Rect) =
|
||||
Point(sensorLocation.left - 5, sensorLocation.centerY())
|
||||
|
||||
private fun pointBelowSensor(sensorLocation: Rect) =
|
||||
Point(sensorLocation.centerX(), sensorLocation.bottom + 5)
|
||||
|
||||
private fun pointAboveSensor(sensorLocation: Rect) =
|
||||
Point(sensorLocation.centerX(), sensorLocation.top - 5)
|
||||
|
||||
companion object {
|
||||
|
||||
private val helpMsgId: Int = 1
|
||||
private val sensorLocationInternal = Pair(540, 1713)
|
||||
private val sensorRadius = 100
|
||||
private val sensorRect =
|
||||
Rect(
|
||||
this.sensorLocationInternal.first - sensorRadius,
|
||||
this.sensorLocationInternal.second - sensorRadius,
|
||||
this.sensorLocationInternal.first + sensorRadius,
|
||||
this.sensorLocationInternal.second + sensorRadius,
|
||||
)
|
||||
val sensorProps =
|
||||
FingerprintSensor(
|
||||
1,
|
||||
SensorStrength.STRONG,
|
||||
5,
|
||||
FingerprintSensorType.UDFPS_OPTICAL,
|
||||
sensorRect,
|
||||
sensorRadius,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.domain.interactor
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
/** Interactor indicating if certain debug flows are enabled. */
|
||||
interface DebuggingInteractor {
|
||||
/** This indicates that certain debug flows are enabled. */
|
||||
val debuggingEnabled: Flow<Boolean>
|
||||
/** This indicates if udfps should instead use debug repos to supply data to its various views. */
|
||||
val udfpsEnrollmentDebuggingEnabled: Flow<Boolean>
|
||||
}
|
||||
|
||||
/**
|
||||
* This interactor essentially forwards the [DebuggingRepository]
|
||||
*/
|
||||
class DebuggingInteractorImpl(val debuggingRepository: DebuggingRepository) : DebuggingInteractor {
|
||||
override val debuggingEnabled: Flow<Boolean> = flow {
|
||||
emit(debuggingRepository.isDebuggingEnabled())
|
||||
}
|
||||
override val udfpsEnrollmentDebuggingEnabled: Flow<Boolean> = flow {
|
||||
emit(debuggingRepository.isUdfpsEnrollmentDebuggingEnabled())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.domain.interactor
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
/**
|
||||
* This class is responsible for handling updates to fontScale and displayDensity and forwarding
|
||||
* these events to classes that need them
|
||||
*/
|
||||
interface DisplayDensityInteractor {
|
||||
/** Indicates the display density has been updated. */
|
||||
fun updateDisplayDensity(density: Int)
|
||||
|
||||
/** Indicates the font scale has been updates. */
|
||||
fun updateFontScale(fontScale: Float)
|
||||
|
||||
/** A flow that propagates fontscale. */
|
||||
val fontScale: Flow<Float>
|
||||
|
||||
/** A flow that propagates displayDensity. */
|
||||
val displayDensity: Flow<Int>
|
||||
|
||||
/** A flow that propagates the default display density. */
|
||||
val defaultDisplayDensity: Flow<Int>
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the [DisplayDensityInteractor]. This interactor is used to forward activity
|
||||
* information to the rest of the application.
|
||||
*/
|
||||
class DisplayDensityInteractorImpl(
|
||||
currentFontScale: Float,
|
||||
currentDisplayDensity: Int,
|
||||
defaultDisplayDensity: Int,
|
||||
scope: CoroutineScope,
|
||||
) : DisplayDensityInteractor {
|
||||
override fun updateDisplayDensity(density: Int) {
|
||||
_displayDensity.update { density }
|
||||
}
|
||||
|
||||
override fun updateFontScale(fontScale: Float) {
|
||||
_fontScale.update { fontScale }
|
||||
}
|
||||
|
||||
private val _fontScale = MutableStateFlow(currentFontScale)
|
||||
private val _displayDensity = MutableStateFlow(currentDisplayDensity)
|
||||
|
||||
override val fontScale: Flow<Float> = _fontScale.asStateFlow()
|
||||
|
||||
override val displayDensity: Flow<Int> = _displayDensity.asStateFlow()
|
||||
|
||||
override val defaultDisplayDensity: Flow<Int> =
|
||||
flowOf(defaultDisplayDensity).shareIn(scope, SharingStarted.Eagerly, 1)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.domain.interactor
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
typealias EnrollStageThresholds = Map<Float, StageViewModel>
|
||||
|
||||
/** Interactor that provides enroll stages for enrollment. */
|
||||
interface EnrollStageInteractor {
|
||||
|
||||
/** Provides enroll stages for enrollment. */
|
||||
val enrollStageThresholds: Flow<EnrollStageThresholds>
|
||||
}
|
||||
|
||||
class EnrollStageInteractorImpl() : EnrollStageInteractor {
|
||||
override val enrollStageThresholds: Flow<EnrollStageThresholds> =
|
||||
flowOf(
|
||||
mapOf(
|
||||
0.0f to StageViewModel.Center,
|
||||
0.25f to StageViewModel.Guided,
|
||||
0.5f to StageViewModel.Fingertip,
|
||||
0.75f to StageViewModel.LeftEdge,
|
||||
0.875f to StageViewModel.RightEdge,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.CancellationSignal
|
||||
import android.util.Log
|
||||
import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
|
||||
import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.onFailure
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
/** This repository is responsible for collecting all state related to the enroll API. */
|
||||
interface FingerprintEnrollInteractor {
|
||||
|
||||
/**
|
||||
* By calling this function, [fingerEnrollState] will begin to be populated with data on success.
|
||||
*/
|
||||
suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
enrollReason: EnrollReason,
|
||||
): Flow<FingerEnrollState>
|
||||
}
|
||||
|
||||
class FingerprintEnrollInteractorImpl(
|
||||
private val applicationContext: Context,
|
||||
private val fingerprintEnrollOptions: FingerprintEnrollOptions,
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
private val fingerprintFlow: FingerprintFlow,
|
||||
) : FingerprintEnrollInteractor {
|
||||
private val enrollRequestOutstanding = MutableStateFlow(false)
|
||||
|
||||
override suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
enrollReason: EnrollReason,
|
||||
): Flow<FingerEnrollState> = callbackFlow {
|
||||
// TODO (b/308456120) Improve this logic
|
||||
if (enrollRequestOutstanding.value) {
|
||||
Log.d(TAG, "Outstanding enroll request, waiting 150ms")
|
||||
delay(150)
|
||||
if (enrollRequestOutstanding.value) {
|
||||
Log.e(TAG, "Request still present, continuing")
|
||||
}
|
||||
}
|
||||
|
||||
enrollRequestOutstanding.update { true }
|
||||
|
||||
var streamEnded = false
|
||||
var totalSteps: Int? = null
|
||||
val enrollmentCallback =
|
||||
object : FingerprintManager.EnrollmentCallback() {
|
||||
override fun onEnrollmentProgress(remaining: Int) {
|
||||
// This is sort of an implementation detail, but unfortunately the API isn't
|
||||
// very expressive. If anything we should look at changing the FingerprintManager API.
|
||||
if (totalSteps == null) {
|
||||
totalSteps = remaining + 1
|
||||
}
|
||||
|
||||
trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure { error ->
|
||||
Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
|
||||
}
|
||||
|
||||
if (remaining == 0) {
|
||||
streamEnded = true
|
||||
enrollRequestOutstanding.update { false }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
|
||||
trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString())).onFailure { error
|
||||
->
|
||||
Log.d(TAG, "onEnrollmentHelp failed to send, due to $error")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
|
||||
trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard)).onFailure { error ->
|
||||
Log.d(TAG, "onEnrollmentError failed to send, due to $error")
|
||||
}
|
||||
Log.d(TAG, "onEnrollmentError($errMsgId)")
|
||||
streamEnded = true
|
||||
enrollRequestOutstanding.update { false }
|
||||
}
|
||||
}
|
||||
|
||||
val cancellationSignal = CancellationSignal()
|
||||
|
||||
fingerprintManager.enroll(
|
||||
hardwareAuthToken,
|
||||
cancellationSignal,
|
||||
applicationContext.userId,
|
||||
enrollmentCallback,
|
||||
enrollReason.toOriginalReason(),
|
||||
fingerprintEnrollOptions,
|
||||
)
|
||||
awaitClose {
|
||||
// If the stream has not been ended, and the user has stopped collecting the flow
|
||||
// before it was over, send cancel.
|
||||
if (!streamEnded) {
|
||||
Log.e(TAG, "Cancel is sent from settings for enroll()")
|
||||
cancellationSignal.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FingerprintEnrollStateRepository"
|
||||
}
|
||||
}
|
||||
@@ -18,43 +18,25 @@ package com.android.settings.biometrics.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.hardware.biometrics.BiometricConstants;
|
||||
import android.hardware.biometrics.BiometricFingerprintConstants
|
||||
import android.hardware.biometrics.SensorLocationInternal
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions;
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
|
||||
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||
import android.os.CancellationSignal
|
||||
import android.util.Log
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.BiometricUtils
|
||||
import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
|
||||
import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
|
||||
import com.android.settings.password.ChooseLockSettingsHelper
|
||||
import com.android.systemui.biometrics.shared.model.toFingerprintSensor
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.onFailure
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -66,9 +48,7 @@ class FingerprintManagerInteractorImpl(
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
fingerprintSensorRepository: FingerprintSensorRepository,
|
||||
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
|
||||
private val pressToAuthInteractor: PressToAuthInteractor,
|
||||
private val fingerprintFlow: FingerprintFlow,
|
||||
private val intent: Intent,
|
||||
private val fingerprintEnrollStateRepository: FingerprintEnrollInteractor,
|
||||
) : FingerprintManagerInteractor {
|
||||
|
||||
private val maxFingerprints =
|
||||
@@ -77,7 +57,6 @@ class FingerprintManagerInteractorImpl(
|
||||
)
|
||||
private val applicationContext = applicationContext.applicationContext
|
||||
|
||||
private val enrollRequestOutstanding = MutableStateFlow(false)
|
||||
|
||||
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
|
||||
suspendCoroutine {
|
||||
@@ -113,85 +92,8 @@ class FingerprintManagerInteractorImpl(
|
||||
|
||||
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
|
||||
|
||||
override suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
enrollReason: EnrollReason,
|
||||
): Flow<FingerEnrollState> = callbackFlow {
|
||||
// TODO (b/308456120) Improve this logic
|
||||
if (enrollRequestOutstanding.value) {
|
||||
Log.d(TAG, "Outstanding enroll request, waiting 150ms")
|
||||
delay(150)
|
||||
if (enrollRequestOutstanding.value) {
|
||||
Log.e(TAG, "Request still present, continuing")
|
||||
}
|
||||
}
|
||||
|
||||
enrollRequestOutstanding.update { true }
|
||||
|
||||
var streamEnded = false
|
||||
var totalSteps: Int? = null
|
||||
val enrollmentCallback =
|
||||
object : FingerprintManager.EnrollmentCallback() {
|
||||
override fun onEnrollmentProgress(remaining: Int) {
|
||||
// This is sort of an implementation detail, but unfortunately the API isn't
|
||||
// very expressive. If anything we should look at changing the FingerprintManager API.
|
||||
if (totalSteps == null) {
|
||||
totalSteps = remaining + 1
|
||||
}
|
||||
|
||||
trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure { error ->
|
||||
Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
|
||||
}
|
||||
|
||||
if (remaining == 0) {
|
||||
streamEnded = true
|
||||
enrollRequestOutstanding.update { false }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
|
||||
trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString())).onFailure { error
|
||||
->
|
||||
Log.d(TAG, "onEnrollmentHelp failed to send, due to $error")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
|
||||
trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard)).onFailure { error ->
|
||||
Log.d(TAG, "onEnrollmentError failed to send, due to $error")
|
||||
}
|
||||
Log.d(TAG, "onEnrollmentError($errMsgId)")
|
||||
streamEnded = true
|
||||
enrollRequestOutstanding.update { false }
|
||||
}
|
||||
}
|
||||
|
||||
val cancellationSignal = CancellationSignal()
|
||||
|
||||
if (intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) === -1) {
|
||||
val isSuw: Boolean = WizardManagerHelper.isAnySetupWizard(intent)
|
||||
intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON,
|
||||
if (isSuw) FingerprintEnrollOptions.ENROLL_REASON_SUW else
|
||||
FingerprintEnrollOptions.ENROLL_REASON_SETTINGS)
|
||||
}
|
||||
|
||||
fingerprintManager.enroll(
|
||||
hardwareAuthToken,
|
||||
cancellationSignal,
|
||||
applicationContext.userId,
|
||||
enrollmentCallback,
|
||||
enrollReason.toOriginalReason(),
|
||||
toFingerprintEnrollOptions(intent)
|
||||
)
|
||||
awaitClose {
|
||||
// If the stream has not been ended, and the user has stopped collecting the flow
|
||||
// before it was over, send cancel.
|
||||
if (!streamEnded) {
|
||||
Log.e(TAG, "Cancel is sent from settings for enroll()")
|
||||
cancellationSignal.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
override suspend fun enroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason): Flow<FingerEnrollState> =
|
||||
fingerprintEnrollStateRepository.enroll(hardwareAuthToken, enrollReason)
|
||||
|
||||
override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
|
||||
val callback =
|
||||
@@ -263,14 +165,4 @@ class FingerprintManagerInteractorImpl(
|
||||
)
|
||||
}
|
||||
|
||||
private fun toFingerprintEnrollOptions(intent: Intent): FingerprintEnrollOptions {
|
||||
val reason: Int =
|
||||
intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1)
|
||||
val builder: FingerprintEnrollOptions.Builder = FingerprintEnrollOptions.Builder()
|
||||
builder.setEnrollReason(FingerprintEnrollOptions.ENROLL_REASON_UNKNOWN)
|
||||
if (reason != -1) {
|
||||
builder.setEnrollReason(reason)
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.settings.biometrics.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.view.OrientationEventListener
|
||||
import com.android.internal.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -24,16 +25,23 @@ import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transform
|
||||
|
||||
/**
|
||||
* Interactor which provides information about orientation
|
||||
*/
|
||||
/** Interactor which provides information about orientation */
|
||||
interface OrientationInteractor {
|
||||
/** A flow that contains the information about the orientation changing */
|
||||
val orientation: Flow<Int>
|
||||
/** A flow that contains the rotation info */
|
||||
/**
|
||||
* A flow that contains the rotation info
|
||||
*/
|
||||
val rotation: Flow<Int>
|
||||
/**
|
||||
* A flow that contains the rotation info matched against the def [config_reverseDefaultRotation]
|
||||
*/
|
||||
val rotationFromDefault: Flow<Int>
|
||||
/**
|
||||
* A Helper function that computes rotation if device is in
|
||||
* [R.bool.config_reverseDefaultConfigRotation]
|
||||
@@ -53,24 +61,11 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor
|
||||
}
|
||||
orientationEventListener.enable()
|
||||
awaitClose { orientationEventListener.disable() }
|
||||
}
|
||||
}.shareIn(activityScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
override val rotation: Flow<Int> =
|
||||
callbackFlow {
|
||||
val orientationEventListener =
|
||||
object : OrientationEventListener(context) {
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
trySend(getRotationFromDefault(context.display!!.rotation))
|
||||
}
|
||||
}
|
||||
orientationEventListener.enable()
|
||||
awaitClose { orientationEventListener.disable() }
|
||||
}
|
||||
.stateIn(
|
||||
activityScope, // This is tied to the activity scope
|
||||
SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener
|
||||
context.display!!.rotation,
|
||||
)
|
||||
override val rotation: Flow<Int> = orientation.transform { emit(context.display!!.rotation) }
|
||||
|
||||
override val rotationFromDefault: Flow<Int> = rotation.map { getRotationFromDefault(it) }
|
||||
|
||||
override fun getRotationFromDefault(rotation: Int): Int {
|
||||
val isReverseDefaultRotation =
|
||||
@@ -81,4 +76,4 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor
|
||||
rotation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Process
|
||||
import android.os.VibrationAttributes
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
|
||||
/** Indicates the possible vibration effects for fingerprint enrollment */
|
||||
sealed class FingerprintVibrationEffects {
|
||||
/** A vibration indicating an error */
|
||||
data object UdfpsError : FingerprintVibrationEffects()
|
||||
|
||||
/**
|
||||
* A vibration indicating success, this usually occurs when progress on the UDFPS enrollment has
|
||||
* been made
|
||||
*/
|
||||
data object UdfpsSuccess : FingerprintVibrationEffects()
|
||||
|
||||
/** This vibration typically occurs when a help message is shown during UDFPS enrollment */
|
||||
data object UdfpsHelp : FingerprintVibrationEffects()
|
||||
}
|
||||
/** Interface for sending haptic feedback */
|
||||
interface VibrationInteractor {
|
||||
/** This will send a haptic vibration */
|
||||
fun vibrate(effect: FingerprintVibrationEffects, caller: String)
|
||||
}
|
||||
|
||||
/** Implementation of the VibrationInteractor interface */
|
||||
class VibrationInteractorImpl(val vibrator: Vibrator, val applicationContext: Context) :
|
||||
VibrationInteractor {
|
||||
override fun vibrate(effect: FingerprintVibrationEffects, caller: String) {
|
||||
val callerString = "$caller::$effect"
|
||||
val res =
|
||||
when (effect) {
|
||||
FingerprintVibrationEffects.UdfpsHelp,
|
||||
FingerprintVibrationEffects.UdfpsError ->
|
||||
Pair(VIBRATE_EFFECT_ERROR, FINGERPRINT_ENROLLING_SONIFICATION_ATTRIBUTES)
|
||||
FingerprintVibrationEffects.UdfpsSuccess ->
|
||||
Pair(VIBRATE_EFFECT_SUCCESS, HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES)
|
||||
}
|
||||
vibrator.vibrate(
|
||||
Process.myUid(),
|
||||
applicationContext.opPackageName,
|
||||
res.first,
|
||||
callerString,
|
||||
res.second,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val VIBRATE_EFFECT_ERROR = VibrationEffect.createWaveform(longArrayOf(0, 5, 55, 60), -1)
|
||||
private val FINGERPRINT_ENROLLING_SONIFICATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY)
|
||||
private val HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)
|
||||
private val VIBRATE_EFFECT_SUCCESS = VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,7 @@ interface FingerprintManagerInteractor {
|
||||
|
||||
/**
|
||||
* Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
|
||||
* enrollment. Returning the [FingerEnrollState] that represents this fingerprint enrollment
|
||||
* state.
|
||||
* enrollment. If successful data in the [fingerprintEnrollState] should be populated.
|
||||
*/
|
||||
suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
|
||||
@@ -42,4 +42,16 @@ sealed class FingerEnrollState {
|
||||
val shouldRetryEnrollment: Boolean,
|
||||
val isCancelled: Boolean,
|
||||
) : FingerEnrollState()
|
||||
|
||||
/** Indicates an acquired event has occurred */
|
||||
data class Acquired(val acquiredGood: Boolean) : FingerEnrollState()
|
||||
|
||||
/** Indicates a pointer down event has occurred */
|
||||
data object PointerDown : FingerEnrollState()
|
||||
|
||||
/** Indicates a pointer up event has occurred */
|
||||
data object PointerUp : FingerEnrollState()
|
||||
|
||||
/** Indicates the overlay has shown */
|
||||
data object OverlayShown : FingerEnrollState()
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
|
||||
package com.android.settings.biometrics.fingerprint2.lib.model
|
||||
|
||||
/**
|
||||
* A view model that describes the various stages of UDFPS Enrollment. This stages typically update
|
||||
@@ -19,8 +19,10 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.activity
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.Bundle
|
||||
import android.os.Vibrator
|
||||
import android.util.Log
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -35,21 +37,35 @@ import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
|
||||
import com.android.settings.biometrics.BiometricEnrollBase
|
||||
import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
|
||||
import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
|
||||
import com.android.settings.biometrics.BiometricUtils
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.UdfpsEnrollDebugRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.Default
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.Settings
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.RfpsEnrollFindSensorFragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.SfpsEnrollFindSensorFragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.UdfpsEnrollFindSensorFragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.util.toFingerprintEnrollOptions
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.fragment.UdfpsEnrollFragment
|
||||
@@ -77,6 +93,7 @@ import com.android.settings.flags.Flags
|
||||
import com.android.settings.password.ChooseLockGeneric
|
||||
import com.android.settings.password.ChooseLockSettingsHelper
|
||||
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
|
||||
import com.android.settingslib.display.DisplayDensityUtils
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper
|
||||
import com.google.android.setupdesign.util.ThemeHelper
|
||||
@@ -95,14 +112,17 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
private lateinit var navigationViewModel: FingerprintNavigationViewModel
|
||||
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
|
||||
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
|
||||
private lateinit var vibrationInteractor: VibrationInteractor
|
||||
private lateinit var foldStateInteractor: FoldStateInteractor
|
||||
private lateinit var orientationInteractor: OrientationInteractor
|
||||
private lateinit var displayDensityInteractor: DisplayDensityInteractor
|
||||
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
|
||||
private lateinit var backgroundViewModel: BackgroundViewModel
|
||||
private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
|
||||
private lateinit var fingerprintEnrollConfirmationViewModel:
|
||||
FingerprintEnrollConfirmationViewModel
|
||||
private lateinit var udfpsViewModel: UdfpsViewModel
|
||||
private lateinit var enrollStageInteractor: EnrollStageInteractor
|
||||
private val coroutineDispatcher = Dispatchers.Default
|
||||
|
||||
/** Result listener for ChooseLock activity flow. */
|
||||
@@ -135,6 +155,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
foldStateInteractor.onConfigurationChange(newConfig)
|
||||
val displayDensityUtils = DisplayDensityUtils(applicationContext)
|
||||
val currIndex = displayDensityUtils.currentIndexForDefaultDisplay
|
||||
displayDensityInteractor.updateFontScale(resources.configuration.fontScale)
|
||||
displayDensityInteractor.updateDisplayDensity(
|
||||
displayDensityUtils.defaultDisplayDensityValues[currIndex]
|
||||
)
|
||||
}
|
||||
|
||||
private fun onConfirmDevice(resultCode: Int, data: Intent?) {
|
||||
@@ -193,10 +219,43 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
fingerprintFlowViewModel =
|
||||
ViewModelProvider(this, FingerprintFlowViewModel.FingerprintFlowViewModelFactory(enrollType))[
|
||||
FingerprintFlowViewModel::class.java]
|
||||
val displayDensityUtils = DisplayDensityUtils(context)
|
||||
val currIndex = displayDensityUtils.currentIndexForDefaultDisplay
|
||||
val defaultDisplayDensity = displayDensityUtils.defaultDensityForDefaultDisplay
|
||||
displayDensityInteractor =
|
||||
DisplayDensityInteractorImpl(
|
||||
resources.configuration.fontScale,
|
||||
displayDensityUtils.defaultDisplayDensityValues[currIndex],
|
||||
defaultDisplayDensity,
|
||||
lifecycleScope,
|
||||
)
|
||||
|
||||
val debuggingRepo = DebuggingRepositoryImpl()
|
||||
val debuggingInteractor = DebuggingInteractorImpl(debuggingRepo)
|
||||
val udfpsEnrollDebugRepositoryImpl = UdfpsEnrollDebugRepositoryImpl()
|
||||
|
||||
val fingerprintSensorRepo =
|
||||
FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
|
||||
val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher)
|
||||
if (debuggingRepo.isUdfpsEnrollmentDebuggingEnabled()) udfpsEnrollDebugRepositoryImpl
|
||||
else FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
|
||||
|
||||
if (intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) === -1) {
|
||||
val isSuw: Boolean = WizardManagerHelper.isAnySetupWizard(intent)
|
||||
intent.putExtra(
|
||||
BiometricUtils.EXTRA_ENROLL_REASON,
|
||||
if (isSuw) FingerprintEnrollOptions.ENROLL_REASON_SUW
|
||||
else FingerprintEnrollOptions.ENROLL_REASON_SETTINGS,
|
||||
)
|
||||
}
|
||||
|
||||
val fingerprintEnrollStateRepository =
|
||||
if (debuggingRepo.isUdfpsEnrollmentDebuggingEnabled()) udfpsEnrollDebugRepositoryImpl
|
||||
else
|
||||
FingerprintEnrollInteractorImpl(
|
||||
context.applicationContext,
|
||||
intent.toFingerprintEnrollOptions(),
|
||||
fingerprintManager,
|
||||
Settings,
|
||||
)
|
||||
|
||||
val fingerprintManagerInteractor =
|
||||
FingerprintManagerInteractorImpl(
|
||||
@@ -205,12 +264,10 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
fingerprintManager,
|
||||
fingerprintSensorRepo,
|
||||
GatekeeperPasswordProvider(LockPatternUtils(context)),
|
||||
pressToAuthInteractor,
|
||||
enrollType,
|
||||
getIntent(),
|
||||
fingerprintEnrollStateRepository,
|
||||
)
|
||||
|
||||
var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
|
||||
var challenge = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
|
||||
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
||||
val gatekeeperInfo = FingerprintGatekeeperViewModel.toGateKeeperInfo(challenge, token)
|
||||
|
||||
@@ -256,6 +313,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
foldStateInteractor.onConfigurationChange(resources.configuration)
|
||||
|
||||
orientationInteractor = OrientationInteractorImpl(context, lifecycleScope)
|
||||
vibrationInteractor =
|
||||
VibrationInteractorImpl(context.getSystemService(Vibrator::class.java)!!, context)
|
||||
|
||||
// Initialize FingerprintViewModel
|
||||
fingerprintEnrollViewModel =
|
||||
@@ -309,10 +368,23 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
),
|
||||
)[RFPSViewModel::class.java]
|
||||
|
||||
enrollStageInteractor = EnrollStageInteractorImpl()
|
||||
|
||||
udfpsViewModel =
|
||||
ViewModelProvider(
|
||||
this,
|
||||
UdfpsViewModel.UdfpsEnrollmentFactory(),
|
||||
UdfpsViewModel.UdfpsEnrollmentFactory(
|
||||
vibrationInteractor,
|
||||
displayDensityInteractor,
|
||||
navigationViewModel,
|
||||
debuggingInteractor,
|
||||
fingerprintEnrollEnrollingViewModel,
|
||||
udfpsEnrollDebugRepositoryImpl,
|
||||
enrollStageInteractor,
|
||||
orientationInteractor,
|
||||
backgroundViewModel,
|
||||
fingerprintSensorRepo,
|
||||
),
|
||||
)[UdfpsViewModel::class.java]
|
||||
|
||||
fingerprintEnrollConfirmationViewModel =
|
||||
@@ -348,7 +420,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
when (step) {
|
||||
Confirmation -> FingerprintEnrollConfirmationV2Fragment()
|
||||
is Education -> {
|
||||
FingerprintEnrollFindSensorV2Fragment(step.sensor.sensorType)
|
||||
when (step.sensor.sensorType) {
|
||||
FingerprintSensorType.REAR -> RfpsEnrollFindSensorFragment()
|
||||
FingerprintSensorType.UDFPS_OPTICAL,
|
||||
FingerprintSensorType.UDFPS_ULTRASONIC -> UdfpsEnrollFindSensorFragment()
|
||||
else -> SfpsEnrollFindSensorFragment()
|
||||
}
|
||||
}
|
||||
is Enrollment -> {
|
||||
when (step.sensor.sensorType) {
|
||||
@@ -370,7 +447,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.setReorderingAllowed(true)
|
||||
.add(R.id.fragment_container_view, theClass, null)
|
||||
.add(R.id.fragment_container_view, theClass::class.java, null)
|
||||
.commit()
|
||||
navigationViewModel.update(
|
||||
FingerprintAction.TRANSITION_FINISHED,
|
||||
@@ -386,7 +463,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
navigationViewModel.shouldFinish.filterNotNull().collect {
|
||||
Log.d(TAG, "FingerprintSettingsNav.finishing($it)")
|
||||
if (it.result != null) {
|
||||
finishActivity(it.result as Int)
|
||||
finishActivity(it.result)
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.ui.enrollment.fragment.education
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
|
||||
import com.google.android.setupcompat.template.FooterBarMixin
|
||||
import com.google.android.setupcompat.template.FooterButton
|
||||
import com.google.android.setupdesign.GlifLayout
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* A fragment that is used to educate the user about the rear fingerprint sensor on this device.
|
||||
*
|
||||
* The main goals of this page are
|
||||
* 1. Inform the user where the fingerprint sensor is on their device
|
||||
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
|
||||
* will work.
|
||||
*/
|
||||
class RfpsEnrollFindSensorFragment() : Fragment() {
|
||||
/** Used for testing purposes */
|
||||
private var factory: ViewModelProvider.Factory? = null
|
||||
|
||||
@VisibleForTesting
|
||||
constructor(theFactory: ViewModelProvider.Factory) : this() {
|
||||
factory = theFactory
|
||||
}
|
||||
|
||||
private val viewModelProvider: ViewModelProvider by lazy {
|
||||
if (factory != null) {
|
||||
ViewModelProvider(requireActivity(), factory!!)
|
||||
} else {
|
||||
ViewModelProvider(requireActivity())
|
||||
}
|
||||
}
|
||||
|
||||
private var animation: FingerprintFindSensorAnimation? = null
|
||||
|
||||
private val viewModel: FingerprintEnrollFindSensorViewModel by lazy {
|
||||
viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View? {
|
||||
val view =
|
||||
inflater.inflate(R.layout.fingerprint_v2_enroll_find_sensor, container, false)!! as GlifLayout
|
||||
view.setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message)
|
||||
|
||||
// Set up footer bar
|
||||
val footerBarMixin = view.getMixin(FooterBarMixin::class.java)
|
||||
setupSecondaryButton(footerBarMixin)
|
||||
lifecycleScope.launch {
|
||||
viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.showRfpsAnimation.collect {
|
||||
animation = view.findViewById(R.id.fingerprint_sensor_location_animation)
|
||||
animation!!.startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.showErrorDialog.collect { (errMsgId, isSetup) ->
|
||||
// TODO: Covert error dialog kotlin as well
|
||||
FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup)
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
animation?.stopAnimation()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) {
|
||||
footerBarMixin.secondaryButton =
|
||||
FooterButton.Builder(requireActivity())
|
||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||
.setListener { viewModel.secondaryButtonClicked() }
|
||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun setupPrimaryButton(footerBarMixin: FooterBarMixin) {
|
||||
footerBarMixin.primaryButton =
|
||||
FooterButton.Builder(requireActivity())
|
||||
.setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
|
||||
.setListener {
|
||||
Log.d(TAG, "onStartButtonClick")
|
||||
viewModel.proceedToEnrolling()
|
||||
}
|
||||
.setButtonType(FooterButton.ButtonType.NEXT)
|
||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "RfpsEnrollFindSensor"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.ui.enrollment.fragment.education
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
|
||||
import com.google.android.setupcompat.template.FooterBarMixin
|
||||
import com.google.android.setupcompat.template.FooterButton
|
||||
import com.google.android.setupdesign.GlifLayout
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* A fragment that is used to educate the user about the side fingerprint sensor on this device.
|
||||
*
|
||||
* The main goals of this page are
|
||||
* 1. Inform the user where the fingerprint sensor is on their device
|
||||
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
|
||||
* will work.
|
||||
*/
|
||||
class SfpsEnrollFindSensorFragment() : Fragment() {
|
||||
/** Used for testing purposes */
|
||||
private var factory: ViewModelProvider.Factory? = null
|
||||
|
||||
@VisibleForTesting
|
||||
constructor(theFactory: ViewModelProvider.Factory) : this() {
|
||||
factory = theFactory
|
||||
}
|
||||
|
||||
private val viewModelProvider: ViewModelProvider by lazy {
|
||||
if (factory != null) {
|
||||
ViewModelProvider(requireActivity(), factory!!)
|
||||
} else {
|
||||
ViewModelProvider(requireActivity())
|
||||
}
|
||||
}
|
||||
|
||||
private val viewModel: FingerprintEnrollFindSensorViewModel by lazy {
|
||||
viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View? {
|
||||
val view =
|
||||
inflater.inflate(R.layout.sfps_enroll_find_sensor_layout, container, false)!! as GlifLayout
|
||||
view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message)
|
||||
|
||||
// Set up footer bar
|
||||
val footerBarMixin = view.getMixin(FooterBarMixin::class.java)
|
||||
setupSecondaryButton(footerBarMixin)
|
||||
|
||||
// Set up lottie
|
||||
lifecycleScope.launch {
|
||||
viewModel.sfpsLottieInfo.collect { (isFolded, rotation) ->
|
||||
setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation))
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.showErrorDialog.collect { (errMsgId, isSetup) ->
|
||||
// TODO: Covert error dialog kotlin as well
|
||||
FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup)
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) {
|
||||
footerBarMixin.secondaryButton =
|
||||
FooterButton.Builder(requireActivity())
|
||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||
.setListener { viewModel.secondaryButtonClicked() }
|
||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun setupPrimaryButton(footerBarMixin: FooterBarMixin) {
|
||||
footerBarMixin.primaryButton =
|
||||
FooterButton.Builder(requireActivity())
|
||||
.setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
|
||||
.setListener {
|
||||
Log.d(TAG, "onStartButtonClick")
|
||||
viewModel.proceedToEnrolling()
|
||||
}
|
||||
.setButtonType(FooterButton.ButtonType.NEXT)
|
||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int {
|
||||
val animation: Int
|
||||
when (rotation) {
|
||||
Surface.ROTATION_90 ->
|
||||
animation =
|
||||
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_left
|
||||
else R.raw.fingerprint_edu_lottie_portrait_top_left)
|
||||
Surface.ROTATION_180 ->
|
||||
animation =
|
||||
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_left
|
||||
else R.raw.fingerprint_edu_lottie_landscape_bottom_left)
|
||||
Surface.ROTATION_270 ->
|
||||
animation =
|
||||
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_right
|
||||
else R.raw.fingerprint_edu_lottie_portrait_bottom_right)
|
||||
else ->
|
||||
animation =
|
||||
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_right
|
||||
else R.raw.fingerprint_edu_lottie_landscape_top_right)
|
||||
}
|
||||
return animation
|
||||
}
|
||||
|
||||
private fun setupLottie(
|
||||
view: View,
|
||||
lottieAnimation: Int,
|
||||
lottieClickListener: View.OnClickListener? = null,
|
||||
) {
|
||||
val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie)
|
||||
illustrationLottie?.setAnimation(lottieAnimation)
|
||||
illustrationLottie?.playAnimation()
|
||||
illustrationLottie?.setOnClickListener(lottieClickListener)
|
||||
illustrationLottie?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SfpsEnrollFindSensor"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
@@ -29,36 +29,27 @@ import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.google.android.setupcompat.template.FooterBarMixin
|
||||
import com.google.android.setupcompat.template.FooterButton
|
||||
import com.google.android.setupdesign.GlifLayout
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val TAG = "FingerprintEnrollFindSensorV2Fragment"
|
||||
|
||||
/**
|
||||
* A fragment that is used to educate the user about the fingerprint sensor on this device.
|
||||
*
|
||||
* If the sensor is not a udfps sensor, this fragment listens to fingerprint enrollment for
|
||||
* proceeding to the enroll enrolling.
|
||||
* A fragment that is used to educate the user about the under display fingerprint sensor on this
|
||||
* device.
|
||||
*
|
||||
* The main goals of this page are
|
||||
* 1. Inform the user where the fingerprint sensor is on their device
|
||||
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
|
||||
* will work.
|
||||
*/
|
||||
class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorType) : Fragment() {
|
||||
class UdfpsEnrollFindSensorFragment() : Fragment() {
|
||||
/** Used for testing purposes */
|
||||
private var factory: ViewModelProvider.Factory? = null
|
||||
|
||||
@VisibleForTesting
|
||||
constructor(
|
||||
sensorType: FingerprintSensorType,
|
||||
theFactory: ViewModelProvider.Factory,
|
||||
) : this(sensorType) {
|
||||
constructor(theFactory: ViewModelProvider.Factory) : this() {
|
||||
factory = theFactory
|
||||
}
|
||||
|
||||
@@ -70,10 +61,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
}
|
||||
}
|
||||
|
||||
// This is only for non-udfps or non-sfps sensor. For udfps and sfps, we show lottie.
|
||||
private var animation: FingerprintFindSensorAnimation? = null
|
||||
|
||||
private var contentLayoutId: Int = -1
|
||||
private val viewModel: FingerprintEnrollFindSensorViewModel by lazy {
|
||||
viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java]
|
||||
}
|
||||
@@ -83,31 +70,18 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View? {
|
||||
|
||||
contentLayoutId =
|
||||
when (sensorType) {
|
||||
FingerprintSensorType.UDFPS_OPTICAL,
|
||||
FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
|
||||
FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
|
||||
else -> R.layout.fingerprint_v2_enroll_find_sensor
|
||||
}
|
||||
|
||||
val view = inflater.inflate(contentLayoutId, container, false)!! as GlifLayout
|
||||
setTexts(sensorType, view)
|
||||
val view =
|
||||
inflater.inflate(R.layout.udfps_enroll_find_sensor_layout, container, false)!! as GlifLayout
|
||||
view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message)
|
||||
|
||||
// Set up footer bar
|
||||
val footerBarMixin = view.getMixin(FooterBarMixin::class.java)
|
||||
setupSecondaryButton(footerBarMixin)
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
|
||||
}
|
||||
|
||||
// Set up lottie or animation
|
||||
lifecycleScope.launch {
|
||||
viewModel.sfpsLottieInfo.collect { (isFolded, rotation) ->
|
||||
setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation))
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
viewModel.udfpsLottieInfo.collect { isAccessibilityEnabled ->
|
||||
val lottieAnimation =
|
||||
@@ -115,12 +89,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
setupLottie(view, lottieAnimation) { viewModel.proceedToEnrolling() }
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
viewModel.showRfpsAnimation.collect {
|
||||
animation = view.findViewById(R.id.fingerprint_sensor_location_animation)
|
||||
animation!!.startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.showErrorDialog.collect { (errMsgId, isSetup) ->
|
||||
@@ -131,11 +99,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
animation?.stopAnimation()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) {
|
||||
footerBarMixin.secondaryButton =
|
||||
FooterButton.Builder(requireActivity())
|
||||
@@ -159,36 +122,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun setupLottie(
|
||||
view: View,
|
||||
lottieAnimation: Int,
|
||||
lottieClickListener: View.OnClickListener? = null,
|
||||
) {
|
||||
val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie)
|
||||
illustrationLottie?.setAnimation(lottieAnimation)
|
||||
illustrationLottie?.playAnimation()
|
||||
illustrationLottie?.setOnClickListener(lottieClickListener)
|
||||
illustrationLottie?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun setTexts(sensorType: FingerprintSensorType?, view: GlifLayout) {
|
||||
when (sensorType) {
|
||||
FingerprintSensorType.UDFPS_OPTICAL,
|
||||
FingerprintSensorType.UDFPS_ULTRASONIC -> {
|
||||
view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message)
|
||||
}
|
||||
FingerprintSensorType.POWER_BUTTON -> {
|
||||
view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message)
|
||||
}
|
||||
else -> {
|
||||
view.setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int {
|
||||
val animation: Int
|
||||
when (rotation) {
|
||||
@@ -211,4 +144,20 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
}
|
||||
return animation
|
||||
}
|
||||
|
||||
private fun setupLottie(
|
||||
view: View,
|
||||
lottieAnimation: Int,
|
||||
lottieClickListener: View.OnClickListener? = null,
|
||||
) {
|
||||
val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie)
|
||||
illustrationLottie?.setAnimation(lottieAnimation)
|
||||
illustrationLottie?.playAnimation()
|
||||
illustrationLottie?.setOnClickListener(lottieClickListener)
|
||||
illustrationLottie?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UdfpsEnrollFindSensor"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.util
|
||||
|
||||
import android.content.Intent
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions
|
||||
import com.android.settings.biometrics.BiometricUtils
|
||||
|
||||
fun Intent.toFingerprintEnrollOptions(): FingerprintEnrollOptions {
|
||||
val reason: Int = this.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1)
|
||||
val builder: FingerprintEnrollOptions.Builder = FingerprintEnrollOptions.Builder()
|
||||
builder.setEnrollReason(FingerprintEnrollOptions.ENROLL_REASON_UNKNOWN)
|
||||
if (reason != -1) {
|
||||
builder.setEnrollReason(reason)
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
@@ -29,8 +29,6 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
|
||||
private const val TAG = "FingerprintErrorDialog"
|
||||
|
||||
/** A Dialog used for fingerprint enrollment when an error occurs. */
|
||||
class FingerprintErrorDialog : InstrumentedDialogFragment() {
|
||||
private lateinit var onContinue: DialogInterface.OnClickListener
|
||||
@@ -82,6 +80,7 @@ class FingerprintErrorDialog : InstrumentedDialogFragment() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FingerprintErrorDialog"
|
||||
private const val KEY_MESSAGE = "fingerprint_message"
|
||||
private const val KEY_TITLE = "fingerprint_title"
|
||||
private const val KEY_SHOULD_TRY_AGAIN = "should_try_again"
|
||||
@@ -34,9 +34,9 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.IconTouchDialog
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
|
||||
|
||||
@@ -43,7 +43,7 @@ class RFPSViewModel(
|
||||
orientationInteractor: OrientationInteractor,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _textViewIsVisible = MutableStateFlow<Boolean>(false)
|
||||
private val _textViewIsVisible = MutableStateFlow(false)
|
||||
/** Value to indicate if the text view is visible or not */
|
||||
val textViewIsVisible: Flow<Boolean> = _textViewIsVisible.asStateFlow()
|
||||
|
||||
@@ -52,7 +52,7 @@ class RFPSViewModel(
|
||||
/** Indicates if the icon should be animating or not */
|
||||
val shouldAnimateIcon = _shouldAnimateIcon
|
||||
|
||||
private var enrollFlow: Flow<FingerEnrollState?> = fingerprintEnrollViewModel.enrollFLow
|
||||
private var enrollFlow: Flow<FingerEnrollState?> = fingerprintEnrollViewModel.enrollFlow
|
||||
|
||||
/**
|
||||
* Enroll progress message with a replay of size 1 allowing for new subscribers to get the most
|
||||
@@ -142,7 +142,7 @@ class RFPSViewModel(
|
||||
_textViewIsVisible.update { false }
|
||||
_shouldAnimateIcon = fingerprintEnrollViewModel.enrollFlowShouldBeRunning
|
||||
/** Indicates if the icon should be animating or not */
|
||||
enrollFlow = fingerprintEnrollViewModel.enrollFLow
|
||||
enrollFlow = fingerprintEnrollViewModel.enrollFlow
|
||||
}
|
||||
|
||||
class RFPSViewModelFactory(
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrol
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.view.MotionEvent.ACTION_HOVER_MOVE
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.TextView
|
||||
@@ -30,10 +32,12 @@ import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.airbnb.lottie.LottieCompositionFactory
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.DescriptionText
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.HeaderText
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.EducationAnimationModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.HeaderText
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
|
||||
@@ -47,6 +51,7 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
private var factory: ViewModelProvider.Factory? = null
|
||||
private val viewModel: UdfpsViewModel by lazy { viewModelProvider[UdfpsViewModel::class.java] }
|
||||
private lateinit var udfpsEnrollView: UdfpsEnrollViewV2
|
||||
private lateinit var lottie: LottieAnimationView
|
||||
|
||||
private val viewModelProvider: ViewModelProvider by lazy {
|
||||
if (factory != null) {
|
||||
@@ -63,7 +68,8 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val illustrationLottie: LottieAnimationView = view.findViewById(R.id.illustration_lottie)!!
|
||||
val fragment = this
|
||||
lottie = view.findViewById(R.id.illustration_lottie)!!
|
||||
udfpsEnrollView = view.findViewById(R.id.udfps_animation_view)!!
|
||||
val titleTextView = view.findViewById<TextView>(R.id.title)!!
|
||||
val descriptionTextView = view.findViewById<TextView>(R.id.description)!!
|
||||
@@ -79,6 +85,11 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
launch {
|
||||
viewModel.sensorLocation.collect { sensor ->
|
||||
udfpsEnrollView.setSensorRect(sensor.sensorBounds, sensor.sensorType)
|
||||
}
|
||||
}
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.headerText.collect { titleTextView.setText(it.toResource()) }
|
||||
}
|
||||
@@ -92,35 +103,59 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.sensorLocation.collect { rect -> udfpsEnrollView.setSensorRect(rect) }
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.accessibilityEnabled.collect { isEnabled -> udfpsEnrollView.setAccessibilityEnabled(isEnabled) }
|
||||
viewModel.shouldShowLottie.collect {
|
||||
lottie.visibility = if (it) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.lottie.collect { lottieModel ->
|
||||
if (lottie.visibility == View.GONE) {
|
||||
return@collect
|
||||
}
|
||||
val resource = lottieModel.toResource()
|
||||
if (resource != null) {
|
||||
LottieCompositionFactory.fromRawRes(requireContext(), resource).addListener { comp ->
|
||||
comp?.let { composition ->
|
||||
illustrationLottie.setComposition(composition)
|
||||
illustrationLottie.visibility = View.VISIBLE
|
||||
illustrationLottie.playAnimation()
|
||||
lottie.setComposition(composition)
|
||||
lottie.visibility = View.VISIBLE
|
||||
lottie.playAnimation()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
illustrationLottie.visibility = View.INVISIBLE
|
||||
lottie.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.udfpsEvent.collect {
|
||||
Log.d(TAG, "EnrollEvent $it")
|
||||
udfpsEnrollView.onUdfpsEvent(it) }
|
||||
repeatOnLifecycle(Lifecycle.State.DESTROYED) { viewModel.stopEnrollment() }
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.accessibilityEnabled.collect { enabled ->
|
||||
udfpsEnrollView.setAccessibilityEnabled(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.enrollState.collect {
|
||||
Log.d(TAG, "EnrollEvent $it")
|
||||
if (it is FingerEnrollState.EnrollError) {
|
||||
try {
|
||||
FingerprintErrorDialog.showInstance(it, fragment)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Exception occurred $exception")
|
||||
}
|
||||
} else {
|
||||
udfpsEnrollView.onUdfpsEvent(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.progressSaved.collect { udfpsEnrollView.onEnrollProgressSaved(it) }
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
@@ -128,6 +163,15 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.touchExplorationDebug.collect {
|
||||
udfpsEnrollView.sendDebugTouchExplorationEvent(
|
||||
MotionEvent.obtain(100, 100, ACTION_HOVER_MOVE, it.x.toFloat(), it.y.toFloat(), 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
viewModel.readyForEnrollment()
|
||||
}
|
||||
|
||||
private fun HeaderText.toResource(): Int {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
|
||||
/** Represents the description text for UDFPS enrollment */
|
||||
data class DescriptionText(
|
||||
val isSuw: Boolean,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
|
||||
/** Represents the lottie for UDFPS enrollment */
|
||||
data class EducationAnimationModel(
|
||||
val isSuw: Boolean,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
|
||||
/** Represents the header text for UDFPS enrollment */
|
||||
data class HeaderText(
|
||||
val isSuw: Boolean,
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
|
||||
|
||||
/** A class indicating a udfps enroll event occurred. */
|
||||
sealed class UdfpsEnrollEvent
|
||||
|
||||
/** Describes how many [remainingSteps] and how many [totalSteps] are left in udfps enrollment. */
|
||||
data class UdfpsProgress(val remainingSteps: Int, val totalSteps: Int) : UdfpsEnrollEvent()
|
||||
|
||||
/** Indicates a help event has been sent by enrollment */
|
||||
data class UdfpsHelp(val helpMsgId: Int, val helpString: String) : UdfpsEnrollEvent()
|
||||
|
||||
/** Indicates a error event has been sent by enrollment */
|
||||
data class UdfpsError(val errMsgId: Int, val errString: String) : UdfpsEnrollEvent()
|
||||
|
||||
/** Indicates an acquired event has occurred */
|
||||
data class Acquired(val acquiredGood: Boolean) : UdfpsEnrollEvent()
|
||||
|
||||
/** Indicates a pointer down event has occurred */
|
||||
data object PointerDown : UdfpsEnrollEvent()
|
||||
|
||||
/** Indicates a pointer up event has occurred */
|
||||
data object PointerUp : UdfpsEnrollEvent()
|
||||
|
||||
/** Indicates the overlay has shown */
|
||||
data object OverlayShown : UdfpsEnrollEvent()
|
||||
@@ -16,167 +16,284 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Point
|
||||
import android.view.Surface
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.SimulatedTouchEventsRepository
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintVibrationEffects
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.combineTransform
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */
|
||||
class UdfpsViewModel() : ViewModel() {
|
||||
class UdfpsViewModel(
|
||||
val vibrationInteractor: VibrationInteractor,
|
||||
displayDensityInteractor: DisplayDensityInteractor,
|
||||
val navigationViewModel: FingerprintNavigationViewModel,
|
||||
debuggingInteractor: DebuggingInteractor,
|
||||
val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
|
||||
simulatedTouchEventsDebugRepository: SimulatedTouchEventsRepository,
|
||||
enrollStageInteractor: EnrollStageInteractor,
|
||||
orientationInteractor: OrientationInteractor,
|
||||
backgroundViewModel: BackgroundViewModel,
|
||||
sensorRepository: FingerprintSensorRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
private val isSetupWizard = flowOf(false)
|
||||
|
||||
/** Indicates which Enrollment stage we are currently in. */
|
||||
private val sensorLocationInternal = Pair(540, 1713)
|
||||
private val sensorRadius = 100
|
||||
private val sensorRect =
|
||||
Rect(
|
||||
this.sensorLocationInternal.first - sensorRadius,
|
||||
this.sensorLocationInternal.second - sensorRadius,
|
||||
this.sensorLocationInternal.first + sensorRadius,
|
||||
this.sensorLocationInternal.second + sensorRadius,
|
||||
)
|
||||
|
||||
private val stageThresholds = flowOf(listOf(.25, .5, .75, .875))
|
||||
|
||||
/** Indicates if accessibility is enabled */
|
||||
val accessibilityEnabled = flowOf(false)
|
||||
|
||||
/** Indicates the locates of the fingerprint sensor. */
|
||||
val sensorLocation: Flow<Rect> = flowOf(sensorRect)
|
||||
|
||||
/** This is currently not hooked up to fingerprint manager, and is being fed mock events. */
|
||||
val udfpsEvent: Flow<UdfpsEnrollEvent> =
|
||||
flow {
|
||||
enrollEvents.forEach { events ->
|
||||
events.forEach { event -> emit(event) }
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
|
||||
/** Determines the current [StageViewModel] enrollment is in */
|
||||
val enrollStage: Flow<StageViewModel> =
|
||||
combine(stageThresholds, udfpsEvent) { thresholds, event ->
|
||||
if (event is UdfpsProgress) {
|
||||
thresholdToStageMap(thresholds, event.totalSteps - event.remainingSteps, event.totalSteps)
|
||||
private var _enrollState: Flow<FingerEnrollState?> =
|
||||
fingerprintEnrollEnrollingViewModel.enrollFlow
|
||||
/** The current state of the enrollment. */
|
||||
var enrollState: Flow<FingerEnrollState> =
|
||||
combine(fingerprintEnrollEnrollingViewModel.enrollFlowShouldBeRunning, _enrollState) {
|
||||
shouldBeRunning,
|
||||
state ->
|
||||
if (shouldBeRunning) {
|
||||
state
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.filterNotNull()
|
||||
|
||||
/**
|
||||
* Forwards the property sensor information. This is typically used to recreate views that must be
|
||||
* aligned with the sensor.
|
||||
*/
|
||||
val sensorLocation = sensorRepository.fingerprintSensor
|
||||
|
||||
/** Indicates if accessibility is enabled */
|
||||
val accessibilityEnabled = flowOf(true).shareIn(viewModelScope, SharingStarted.Eagerly, 1)
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
enrollState
|
||||
.combine(accessibilityEnabled) { event, isEnabled -> Pair(event, isEnabled) }
|
||||
.collect {
|
||||
if (
|
||||
when (it.first) {
|
||||
is FingerEnrollState.EnrollError -> true
|
||||
is FingerEnrollState.EnrollHelp -> it.second
|
||||
is FingerEnrollState.EnrollProgress -> true
|
||||
else -> false
|
||||
}
|
||||
) {
|
||||
vibrate(it.first)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
backgroundViewModel.background.filter { it }.collect { didGoToBackground() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the saved progress, this is for when views are recreated and need saved state for the
|
||||
* first time.
|
||||
*/
|
||||
var progressSaved: Flow<FingerEnrollState.EnrollProgress> =
|
||||
enrollState
|
||||
.filterIsInstance<FingerEnrollState.EnrollProgress>()
|
||||
.filterNotNull()
|
||||
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
/** This sends touch exploration events only used for debugging purposes. */
|
||||
val touchExplorationDebug: Flow<Point> =
|
||||
debuggingInteractor.debuggingEnabled.combineTransform(
|
||||
simulatedTouchEventsDebugRepository.touchExplorationDebug
|
||||
) { enabled, point ->
|
||||
if (enabled) {
|
||||
emit(point)
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines the current [StageViewModel] enrollment is in */
|
||||
val enrollStage: Flow<StageViewModel> =
|
||||
combine(enrollStageInteractor.enrollStageThresholds, enrollState) { thresholds, event ->
|
||||
if (event is FingerEnrollState.EnrollProgress) {
|
||||
val progress =
|
||||
(event.totalStepsRequired - event.remainingSteps).toFloat() / event.totalStepsRequired
|
||||
var stageToReturn: StageViewModel = StageViewModel.Center
|
||||
thresholds.forEach { (threshold, stage) ->
|
||||
if (progress < threshold) {
|
||||
return@forEach
|
||||
}
|
||||
stageToReturn = stage
|
||||
}
|
||||
stageToReturn
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.filterNotNull()
|
||||
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
/** Indicates if we should show the lottie. */
|
||||
val shouldShowLottie: Flow<Boolean> =
|
||||
combine(
|
||||
displayDensityInteractor.displayDensity,
|
||||
displayDensityInteractor.defaultDisplayDensity,
|
||||
displayDensityInteractor.fontScale,
|
||||
orientationInteractor.rotation,
|
||||
) { currDisplayDensity, defaultDisplayDensity, fontScale, rotation ->
|
||||
val canShowLottieForRotation =
|
||||
when (rotation) {
|
||||
Surface.ROTATION_0 -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
canShowLottieForRotation &&
|
||||
if (fontScale > 1.0f) {
|
||||
false
|
||||
} else {
|
||||
defaultDisplayDensity == currDisplayDensity
|
||||
}
|
||||
}
|
||||
.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
|
||||
|
||||
/** The header text for UDFPS enrollment */
|
||||
val headerText: Flow<HeaderText> =
|
||||
combine(isSetupWizard, accessibilityEnabled, enrollStage) { isSuw, isAccessibility, stage ->
|
||||
return@combine HeaderText(isSuw, isAccessibility, stage)
|
||||
}
|
||||
return@combine HeaderText(isSuw, isAccessibility, stage)
|
||||
}
|
||||
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
private val shouldClearDescriptionText = enrollStage.map { it is StageViewModel.Unknown }
|
||||
|
||||
/** The description text for UDFPS enrollment */
|
||||
val descriptionText: Flow<DescriptionText?> =
|
||||
combine(isSetupWizard, accessibilityEnabled, enrollStage, shouldClearDescriptionText) {
|
||||
isSuw,
|
||||
isAccessibility,
|
||||
stage,
|
||||
shouldClearText ->
|
||||
if (shouldClearText) {
|
||||
return@combine null
|
||||
} else {
|
||||
return@combine DescriptionText(isSuw, isAccessibility, stage)
|
||||
isSuw,
|
||||
isAccessibility,
|
||||
stage,
|
||||
shouldClearText ->
|
||||
if (shouldClearText) {
|
||||
return@combine null
|
||||
} else {
|
||||
return@combine DescriptionText(isSuw, isAccessibility, stage)
|
||||
}
|
||||
}
|
||||
}
|
||||
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
/** Indicates if the consumer is ready for enrollment */
|
||||
fun readyForEnrollment() {
|
||||
fingerprintEnrollEnrollingViewModel.canEnroll()
|
||||
}
|
||||
|
||||
/** Indicates if enrollment should stop */
|
||||
fun stopEnrollment() {
|
||||
fingerprintEnrollEnrollingViewModel.stopEnroll()
|
||||
}
|
||||
|
||||
/** Indicates the negative button has been clicked */
|
||||
fun negativeButtonClicked() {
|
||||
doReset()
|
||||
navigationViewModel.update(
|
||||
FingerprintAction.NEGATIVE_BUTTON_PRESSED,
|
||||
navStep,
|
||||
"$TAG#negativeButtonClicked",
|
||||
)
|
||||
}
|
||||
|
||||
/** Indicates that an enrollment was completed */
|
||||
fun finishedSuccessfully() {
|
||||
doReset()
|
||||
navigationViewModel.update(FingerprintAction.NEXT, navStep, "${TAG}#progressFinished")
|
||||
}
|
||||
|
||||
/** Indicates that the application went to the background. */
|
||||
private fun didGoToBackground() {
|
||||
navigationViewModel.update(
|
||||
FingerprintAction.DID_GO_TO_BACKGROUND,
|
||||
navStep,
|
||||
"$TAG#didGoToBackground",
|
||||
)
|
||||
stopEnrollment()
|
||||
}
|
||||
|
||||
private fun doReset() {
|
||||
/** Indicates if the icon should be animating or not */
|
||||
_enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow
|
||||
}
|
||||
|
||||
/** The lottie that should be shown for UDFPS Enrollment */
|
||||
val lottie: Flow<EducationAnimationModel> =
|
||||
combine(isSetupWizard, accessibilityEnabled, enrollStage) { isSuw, isAccessibility, stage ->
|
||||
return@combine EducationAnimationModel(isSuw, isAccessibility, stage)
|
||||
}.distinctUntilChanged()
|
||||
return@combine EducationAnimationModel(isSuw, isAccessibility, stage)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
class UdfpsEnrollmentFactory() : ViewModelProvider.Factory {
|
||||
/** Indicates we should send a vibration event */
|
||||
private fun vibrate(event: FingerEnrollState) {
|
||||
val vibrationEvent =
|
||||
when (event) {
|
||||
is FingerEnrollState.EnrollError -> FingerprintVibrationEffects.UdfpsError
|
||||
is FingerEnrollState.EnrollHelp -> FingerprintVibrationEffects.UdfpsHelp
|
||||
is FingerEnrollState.EnrollProgress -> FingerprintVibrationEffects.UdfpsSuccess
|
||||
else -> FingerprintVibrationEffects.UdfpsError
|
||||
}
|
||||
vibrationInteractor.vibrate(vibrationEvent, "UdfpsEnrollFragment")
|
||||
}
|
||||
|
||||
class UdfpsEnrollmentFactory(
|
||||
private val vibrationInteractor: VibrationInteractor,
|
||||
private val displayDensityInteractor: DisplayDensityInteractor,
|
||||
private val navigationViewModel: FingerprintNavigationViewModel,
|
||||
private val debuggingInteractor: DebuggingInteractor,
|
||||
private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
|
||||
private val simulatedTouchEventsRepository: SimulatedTouchEventsRepository,
|
||||
private val enrollStageInteractor: EnrollStageInteractor,
|
||||
private val orientationInteractor: OrientationInteractor,
|
||||
private val backgroundViewModel: BackgroundViewModel,
|
||||
private val sensorRepository: FingerprintSensorRepository,
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return UdfpsViewModel() as T
|
||||
return UdfpsViewModel(
|
||||
vibrationInteractor,
|
||||
displayDensityInteractor,
|
||||
navigationViewModel,
|
||||
debuggingInteractor,
|
||||
fingerprintEnrollEnrollingViewModel,
|
||||
simulatedTouchEventsRepository,
|
||||
enrollStageInteractor,
|
||||
orientationInteractor,
|
||||
backgroundViewModel,
|
||||
sensorRepository,
|
||||
)
|
||||
as T
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val navStep = FingerprintNavigationStep.Enrollment::class
|
||||
private const val TAG = "UDFPSViewModel"
|
||||
private val ENROLLMENT_STAGES_ORDERED =
|
||||
listOf(
|
||||
StageViewModel.Center,
|
||||
StageViewModel.Guided,
|
||||
StageViewModel.Fingertip,
|
||||
StageViewModel.LeftEdge,
|
||||
StageViewModel.RightEdge,
|
||||
)
|
||||
|
||||
/**
|
||||
* [thresholds] is a list of 4 numbers from [0,1] that separate enrollment into 5 stages. The
|
||||
* stage is determined by mapping [thresholds] * [maxSteps] and finding where the [currentStep]
|
||||
* is.
|
||||
*
|
||||
* Each number in the array should be strictly increasing such as [0.2, 0.5, 0.6, 0.8]
|
||||
*/
|
||||
private fun thresholdToStageMap(
|
||||
thresholds: List<Double>,
|
||||
currentStep: Int,
|
||||
maxSteps: Int,
|
||||
): StageViewModel {
|
||||
val stageIterator = ENROLLMENT_STAGES_ORDERED.iterator()
|
||||
thresholds.forEach {
|
||||
val thresholdLimit = it * maxSteps
|
||||
val curr = stageIterator.next()
|
||||
if (currentStep < thresholdLimit) {
|
||||
return curr
|
||||
}
|
||||
}
|
||||
return stageIterator.next()
|
||||
}
|
||||
|
||||
/** This will be removed */
|
||||
private val enrollEvents: List<List<UdfpsEnrollEvent>> =
|
||||
listOf(
|
||||
listOf(OverlayShown),
|
||||
listOf(UdfpsHelp(1,"hi")),
|
||||
listOf(UdfpsHelp(1,"hi")),
|
||||
CreateProgress(15, 16),
|
||||
listOf(UdfpsHelp(1,"hi")),
|
||||
CreateProgress(14, 16),
|
||||
listOf(PointerDown, UdfpsHelp(1,"hi"), PointerUp),
|
||||
listOf(PointerDown, UdfpsHelp(1,"hi"), PointerUp),
|
||||
CreateProgress(13, 16),
|
||||
CreateProgress(12, 16),
|
||||
CreateProgress(11, 16),
|
||||
CreateProgress(10, 16),
|
||||
CreateProgress(9, 16),
|
||||
CreateProgress(8, 16),
|
||||
CreateProgress(7, 16),
|
||||
CreateProgress(6, 16),
|
||||
CreateProgress(5, 16),
|
||||
CreateProgress(4, 16),
|
||||
CreateProgress(3, 16),
|
||||
CreateProgress(2, 16),
|
||||
CreateProgress(1, 16),
|
||||
CreateProgress(0, 16),
|
||||
)
|
||||
|
||||
/** This will be removed */
|
||||
private fun CreateProgress(remaining: Int, total: Int): List<UdfpsEnrollEvent> {
|
||||
return listOf(PointerDown, Acquired(true), UdfpsProgress(remaining, total), PointerUp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import android.content.Context
|
||||
import android.graphics.PointF
|
||||
import android.util.TypedValue
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
|
||||
/** Keeps track of which guided enrollment point we should be using */
|
||||
class UdfpsEnrollHelperV2(private val mContext: Context) {
|
||||
@@ -28,6 +28,7 @@ class UdfpsEnrollHelperV2(private val mContext: Context) {
|
||||
private var isGuidedEnrollment: Boolean = false
|
||||
private val accessibilityEnabled: Boolean
|
||||
private val guidedEnrollmentPoints: MutableList<PointF>
|
||||
/** The current index of [guidedEnrollmentPoints] for the guided enrollment. */
|
||||
private var index = 0
|
||||
|
||||
init {
|
||||
@@ -76,7 +77,7 @@ class UdfpsEnrollHelperV2(private val mContext: Context) {
|
||||
if (accessibilityEnabled || !isGuidedEnrollment) {
|
||||
return null
|
||||
}
|
||||
var scale = SCALE
|
||||
val scale = SCALE
|
||||
val originalPoint = guidedEnrollmentPoints[index % guidedEnrollmentPoints.size]
|
||||
return PointF(originalPoint.x * scale, originalPoint.y * scale)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import androidx.core.animation.addListener
|
||||
import androidx.core.graphics.toRect
|
||||
import androidx.core.graphics.toRectF
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
@@ -45,6 +45,7 @@ import kotlin.math.sin
|
||||
* various stages of enrollment
|
||||
*/
|
||||
class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeSet?) : Drawable() {
|
||||
private var targetAnimationDuration: Long = TARGET_ANIM_DURATION_LONG
|
||||
private var targetAnimatorSet: AnimatorSet? = null
|
||||
private val movingTargetFpIcon: Drawable
|
||||
private val fingerprintDrawable: ShapeDrawable
|
||||
@@ -88,22 +89,25 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
it.recycle()
|
||||
}
|
||||
|
||||
sensorOutlinePaint = Paint(0 /* flags */).apply {
|
||||
isAntiAlias = true
|
||||
setColor(movingTargetFill)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
sensorOutlinePaint =
|
||||
Paint(0 /* flags */).apply {
|
||||
isAntiAlias = true
|
||||
setColor(movingTargetFill)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
blueFill = Paint(0 /* flags */).apply {
|
||||
isAntiAlias = true
|
||||
setColor(movingTargetFill)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
blueFill =
|
||||
Paint(0 /* flags */).apply {
|
||||
isAntiAlias = true
|
||||
setColor(movingTargetFill)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
movingTargetFpIcon = context.resources.getDrawable(R.drawable.ic_enrollment_fingerprint, null).apply {
|
||||
setTint(enrollIconColor)
|
||||
mutate()
|
||||
}
|
||||
movingTargetFpIcon =
|
||||
context.resources.getDrawable(R.drawable.ic_enrollment_fingerprint, null).apply {
|
||||
setTint(enrollIconColor)
|
||||
mutate()
|
||||
}
|
||||
|
||||
fingerprintDrawable.setTint(enrollIconColor)
|
||||
setAlpha(255)
|
||||
@@ -140,7 +144,16 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
}
|
||||
|
||||
/** Update the progress of the icon */
|
||||
fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
|
||||
fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) {
|
||||
restoreAnimationTime()
|
||||
// If we are restoring this view from a saved state, set animation duration to 0 to avoid
|
||||
// animating progress that has already occurred.
|
||||
if (isRecreating) {
|
||||
setAnimationTimeToZero()
|
||||
} else {
|
||||
restoreAnimationTime()
|
||||
}
|
||||
|
||||
helper.onEnrollmentProgress(remaining, totalSteps)
|
||||
val offset = helper.guidedEnrollmentLocation
|
||||
val currentBounds = getCurrLocation().toRect()
|
||||
@@ -149,10 +162,10 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
// offsets the initial sensor rect by a bit to get the user to move their finger a bit more.
|
||||
val targetRect = Rect(sensorRectBounds).toRectF()
|
||||
targetRect.offset(offset.x, offset.y)
|
||||
var shouldAnimateMovement =
|
||||
val shouldAnimateMovement =
|
||||
!currentBounds.equals(targetRect) && offset.x != 0f && offset.y != 0f
|
||||
if (shouldAnimateMovement) {
|
||||
targetAnimatorSet?.let { it.cancel() }
|
||||
targetAnimatorSet?.cancel()
|
||||
animateMovement(currentBounds, targetRect, true)
|
||||
}
|
||||
} else {
|
||||
@@ -186,7 +199,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
val currLocation = getCurrLocation()
|
||||
canvas.scale(currentScale, currentScale, currLocation.centerX(), currLocation.centerY())
|
||||
|
||||
sensorRectBounds?.let { canvas.drawOval(currLocation, sensorOutlinePaint) }
|
||||
canvas.drawOval(currLocation, sensorOutlinePaint)
|
||||
fingerprintDrawable.bounds = currLocation.toRect()
|
||||
fingerprintDrawable.draw(canvas)
|
||||
}
|
||||
@@ -234,6 +247,19 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets animation time to 0. This typically happens after an activity recreation, we don't
|
||||
* want to re-animate the progress/success animation with the default timer
|
||||
*/
|
||||
private fun setAnimationTimeToZero() {
|
||||
targetAnimationDuration = 0
|
||||
}
|
||||
|
||||
/** This sets animation timers back to normal, this happens after we have */
|
||||
private fun restoreAnimationTime() {
|
||||
targetAnimationDuration = TARGET_ANIM_DURATION_LONG
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UdfpsEnrollDrawableV2"
|
||||
private const val DEFAULT_STROKE_WIDTH = 3f
|
||||
@@ -242,12 +268,13 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
|
||||
private fun createUdfpsIcon(context: Context): ShapeDrawable {
|
||||
val fpPath = context.resources.getString(R.string.config_udfpsIcon)
|
||||
val drawable = ShapeDrawable(PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)).apply {
|
||||
mutate()
|
||||
paint.style = Paint.Style.STROKE
|
||||
paint.strokeCap = Paint.Cap.ROUND
|
||||
paint.strokeWidth = DEFAULT_STROKE_WIDTH
|
||||
}
|
||||
val drawable =
|
||||
ShapeDrawable(PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)).apply {
|
||||
mutate()
|
||||
paint.style = Paint.Style.STROKE
|
||||
paint.strokeCap = Paint.Cap.ROUND
|
||||
paint.strokeWidth = DEFAULT_STROKE_WIDTH
|
||||
}
|
||||
return drawable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,28 +25,28 @@ import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Process
|
||||
import android.os.VibrationAttributes
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.util.AttributeSet
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.Interpolator
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.graphics.toRectF
|
||||
import com.android.internal.annotations.VisibleForTesting
|
||||
import com.android.settings.R
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* UDFPS enrollment progress bar. This view is responsible for drawing the progress ring and its
|
||||
* fill around the center of the UDFPS sensor.
|
||||
*/
|
||||
class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: AttributeSet?) :
|
||||
class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: AttributeSet?) :
|
||||
Drawable() {
|
||||
private val sensorRect: Rect = Rect()
|
||||
private var rotation: Int = 0
|
||||
private val strokeWidthPx: Float
|
||||
|
||||
@ColorInt private val progressColor: Int
|
||||
@@ -56,7 +56,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
private val backgroundPaint: Paint
|
||||
|
||||
@VisibleForTesting val fillPaint: Paint
|
||||
private val vibrator: Vibrator
|
||||
private var isAccessibilityEnabled: Boolean = false
|
||||
private var afterFirstTouch = false
|
||||
private var remainingSteps = 0
|
||||
@@ -64,22 +63,27 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
private var progress = 0f
|
||||
private var progressAnimator: ValueAnimator? = null
|
||||
private val progressUpdateListener: AnimatorUpdateListener
|
||||
private var showingHelp = false
|
||||
private var fillColorAnimator: ValueAnimator? = null
|
||||
private val fillColorUpdateListener: AnimatorUpdateListener
|
||||
private var backgroundColorAnimator: ValueAnimator? = null
|
||||
private val backgroundColorUpdateListener: AnimatorUpdateListener
|
||||
private var complete = false
|
||||
private var movingTargetFill = 0
|
||||
private var movingTargetFillError = 0
|
||||
private var enrollProgressColor = 0
|
||||
private var enrollProgressHelp = 0
|
||||
private var enrollProgressHelpWithTalkback = 0
|
||||
private val progressBarRadius: Int
|
||||
private var checkMarkDrawable: Drawable
|
||||
private var checkMarkAnimator: ValueAnimator? = null
|
||||
|
||||
private var fillColorAnimationDuration = FILL_COLOR_ANIMATION_DURATION_MS
|
||||
private var animateArcDuration = PROGRESS_ANIMATION_DURATION_MS
|
||||
private var checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS
|
||||
private var checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS
|
||||
|
||||
init {
|
||||
val ta =
|
||||
mContext.obtainStyledAttributes(
|
||||
context.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.BiometricsEnrollView,
|
||||
R.attr.biometricsEnrollStyle,
|
||||
@@ -94,30 +98,33 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
enrollProgressHelpWithTalkback =
|
||||
ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0)
|
||||
ta.recycle()
|
||||
val density = mContext.resources.displayMetrics.densityDpi.toFloat()
|
||||
val density = context.resources.displayMetrics.densityDpi.toFloat()
|
||||
strokeWidthPx = STROKE_WIDTH_DP * (density / DisplayMetrics.DENSITY_DEFAULT)
|
||||
progressColor = enrollProgressColor
|
||||
onFirstBucketFailedColor = movingTargetFillError
|
||||
updateHelpColor()
|
||||
backgroundPaint = Paint().apply {
|
||||
strokeWidth = strokeWidthPx
|
||||
setColor(movingTargetFill)
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
}
|
||||
backgroundPaint =
|
||||
Paint().apply {
|
||||
strokeWidth = strokeWidthPx
|
||||
setColor(movingTargetFill)
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
}
|
||||
|
||||
checkMarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark)!!
|
||||
|
||||
// Progress fill should *not* use the extracted system color.
|
||||
fillPaint = Paint().apply {
|
||||
strokeWidth = strokeWidthPx
|
||||
setColor(progressColor)
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
}
|
||||
vibrator = mContext.getSystemService(Vibrator::class.java)!!
|
||||
fillPaint =
|
||||
Paint().apply {
|
||||
strokeWidth = strokeWidthPx
|
||||
setColor(progressColor)
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
}
|
||||
|
||||
progressBarRadius = mContext.resources.getInteger(R.integer.config_udfpsEnrollProgressBar)
|
||||
progressBarRadius = context.resources.getInteger(R.integer.config_udfpsEnrollProgressBar)
|
||||
|
||||
progressUpdateListener = AnimatorUpdateListener { animation: ValueAnimator ->
|
||||
progress = animation.getAnimatedValue() as Float
|
||||
@@ -134,9 +141,10 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
}
|
||||
|
||||
/** Indicates enrollment progress has occurred. */
|
||||
fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
|
||||
fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) {
|
||||
|
||||
afterFirstTouch = true
|
||||
updateProgress(remaining, totalSteps)
|
||||
updateProgress(remaining, totalSteps, isRecreating)
|
||||
}
|
||||
|
||||
/** Indicates enrollment help has occurred. */
|
||||
@@ -157,18 +165,12 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
|
||||
canvas.save()
|
||||
// This takes the sensors bounding box and expands it by [progressBarRadius] in all directions
|
||||
val sensorProgressRect = Rect(sensorRect)
|
||||
sensorProgressRect.inset(
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
)
|
||||
val sensorProgressRect = getSensorProgressRect()
|
||||
|
||||
// Rotate -90 degrees to make the progress start from the top right and not the bottom
|
||||
// right
|
||||
canvas.rotate(
|
||||
-90f,
|
||||
rotation - 90f,
|
||||
sensorProgressRect.centerX().toFloat(),
|
||||
sensorProgressRect.centerY().toFloat(),
|
||||
)
|
||||
@@ -176,9 +178,9 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
// Draw the background color of the progress circle.
|
||||
canvas.drawArc(
|
||||
sensorProgressRect.toRectF(),
|
||||
0f /* startAngle */,
|
||||
360f /* sweepAngle */,
|
||||
false /* useCenter */,
|
||||
0f, /* startAngle */
|
||||
360f, /* sweepAngle */
|
||||
false, /* useCenter */
|
||||
backgroundPaint,
|
||||
)
|
||||
}
|
||||
@@ -186,13 +188,15 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
// Draw the filled portion of the progress circle.
|
||||
canvas.drawArc(
|
||||
sensorProgressRect.toRectF(),
|
||||
0f /* startAngle */,
|
||||
360f * progress /* sweepAngle */,
|
||||
false /* useCenter */,
|
||||
0f, /* startAngle */
|
||||
360f * progress, /* sweepAngle */
|
||||
false, /* useCenter */
|
||||
fillPaint,
|
||||
)
|
||||
}
|
||||
|
||||
canvas.restore()
|
||||
checkMarkDrawable.draw(canvas)
|
||||
}
|
||||
|
||||
/** Do nothing here, we will control the alpha internally. */
|
||||
@@ -211,6 +215,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
*/
|
||||
fun drawProgressAt(sensorRect: Rect) {
|
||||
this.sensorRect.set(sensorRect)
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
/** Indicates if accessibility is enabled or not. */
|
||||
@@ -228,47 +233,21 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProgress(remainingSteps: Int, totalSteps: Int) {
|
||||
private fun updateProgress(remainingSteps: Int, totalSteps: Int, isRecreating: Boolean) {
|
||||
if (this.remainingSteps == remainingSteps && this.totalSteps == totalSteps) {
|
||||
return
|
||||
}
|
||||
|
||||
// If we are restoring this view from a saved state, set animation duration to 0 to avoid
|
||||
// animating progress that has already occurred.
|
||||
if (isRecreating) {
|
||||
setAnimationTimeToZero()
|
||||
} else {
|
||||
restoreAnimationTime()
|
||||
}
|
||||
|
||||
this.remainingSteps = remainingSteps
|
||||
this.totalSteps = totalSteps
|
||||
if (this.showingHelp) {
|
||||
if (vibrator != null && isAccessibilityEnabled) {
|
||||
vibrator.vibrate(
|
||||
Process.myUid(),
|
||||
mContext.opPackageName,
|
||||
VIBRATE_EFFECT_ERROR,
|
||||
javaClass.getSimpleName() + "::onEnrollmentHelp",
|
||||
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// If the first touch is an error, remainingSteps will be -1 and the callback
|
||||
// doesn't come from onEnrollmentHelp. If we are in the accessibility flow,
|
||||
// we still would like to vibrate.
|
||||
if (vibrator != null) {
|
||||
if (remainingSteps == -1 && isAccessibilityEnabled) {
|
||||
vibrator.vibrate(
|
||||
Process.myUid(),
|
||||
mContext.opPackageName,
|
||||
VIBRATE_EFFECT_ERROR,
|
||||
javaClass.getSimpleName() + "::onFirstTouchError",
|
||||
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES,
|
||||
)
|
||||
} else if (remainingSteps != -1 && !isAccessibilityEnabled) {
|
||||
vibrator.vibrate(
|
||||
Process.myUid(),
|
||||
mContext.opPackageName,
|
||||
SUCCESS_VIBRATION_EFFECT,
|
||||
javaClass.getSimpleName() + "::OnEnrollmentProgress",
|
||||
HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.showingHelp = showingHelp
|
||||
this.remainingSteps = remainingSteps
|
||||
this.totalSteps = totalSteps
|
||||
val targetProgress = (totalSteps - remainingSteps).toFloat().div(max(1, totalSteps))
|
||||
@@ -276,12 +255,69 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
if (progressAnimator != null && progressAnimator!!.isRunning) {
|
||||
progressAnimator!!.cancel()
|
||||
}
|
||||
/** The [progressUpdateListener] will force re-[draw]s to occur depending on the progress. */
|
||||
progressAnimator =
|
||||
ValueAnimator.ofFloat(progress, targetProgress).also {
|
||||
it.setDuration(PROGRESS_ANIMATION_DURATION_MS)
|
||||
it.setDuration(animateArcDuration)
|
||||
it.addUpdateListener(progressUpdateListener)
|
||||
it.start()
|
||||
}
|
||||
if (remainingSteps == 0) {
|
||||
runCompletionAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCompletionAnimation() {
|
||||
checkMarkAnimator?.cancel()
|
||||
|
||||
checkMarkAnimator = ValueAnimator.ofFloat(0f, 1f)
|
||||
checkMarkAnimator?.apply {
|
||||
startDelay = checkmarkAnimationDelayDuration
|
||||
setDuration(checkmarkAnimationDuration)
|
||||
interpolator = OvershootInterpolator()
|
||||
addUpdateListener {
|
||||
val newBounds = getCheckMarkStartBounds()
|
||||
val scale = it.animatedFraction
|
||||
newBounds.set(
|
||||
newBounds.left,
|
||||
newBounds.top,
|
||||
(newBounds.left + (newBounds.width() * scale)).toInt(),
|
||||
(newBounds.top + (newBounds.height() * scale)).toInt(),
|
||||
)
|
||||
checkMarkDrawable.bounds = newBounds
|
||||
checkMarkDrawable.setVisible(true, false)
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the bounds for which the checkmark drawable should be drawn at. It should be drawn
|
||||
* on the arc of the progress bar at the 315 degree mark.
|
||||
*/
|
||||
private fun getCheckMarkStartBounds(): Rect {
|
||||
val progressBounds = getSensorProgressRect()
|
||||
val radius = progressBounds.width() / 2.0
|
||||
|
||||
var x = (cos(Math.toRadians(315.0)) * radius).toInt() + progressBounds.centerX()
|
||||
// Remember to negate this value as sin(>180) will return negative value
|
||||
var y = (-sin(Math.toRadians(315.0)) * radius).toInt() + progressBounds.centerY()
|
||||
// Subtract height|width /2 to make sure we draw in the middle of the arc.
|
||||
x -= (checkMarkDrawable.intrinsicWidth / 2.0).toInt()
|
||||
y -= (checkMarkDrawable.intrinsicHeight / 2.0).toInt()
|
||||
|
||||
return Rect(x, y, x + checkMarkDrawable.intrinsicWidth, y + checkMarkDrawable.intrinsicHeight)
|
||||
}
|
||||
|
||||
private fun getSensorProgressRect(): Rect {
|
||||
val sensorProgressRect = Rect(sensorRect)
|
||||
sensorProgressRect.inset(
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
)
|
||||
return sensorProgressRect
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,7 +330,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
}
|
||||
backgroundColorAnimator =
|
||||
ValueAnimator.ofArgb(backgroundPaint.color, onFirstBucketFailedColor).also {
|
||||
it.setDuration(FILL_COLOR_ANIMATION_DURATION_MS)
|
||||
it.setDuration(fillColorAnimationDuration)
|
||||
it.repeatCount = 1
|
||||
it.repeatMode = ValueAnimator.REVERSE
|
||||
it.interpolator = DEACCEL
|
||||
@@ -315,7 +351,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
@ColorInt val targetColor = helpColor
|
||||
fillColorAnimator =
|
||||
ValueAnimator.ofArgb(fillPaint.color, targetColor).also {
|
||||
it.setDuration(FILL_COLOR_ANIMATION_DURATION_MS)
|
||||
it.setDuration(fillColorAnimationDuration)
|
||||
it.repeatCount = 1
|
||||
it.repeatMode = ValueAnimator.REVERSE
|
||||
it.interpolator = DEACCEL
|
||||
@@ -325,33 +361,32 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
}
|
||||
}
|
||||
|
||||
private fun startCompletionAnimation() {
|
||||
if (complete) {
|
||||
return
|
||||
}
|
||||
complete = true
|
||||
/**
|
||||
* This sets animation time to 0. This typically happens after an activity recreation, we don't
|
||||
* want to re-animate the progress/success animation with the default timer
|
||||
*/
|
||||
private fun setAnimationTimeToZero() {
|
||||
fillColorAnimationDuration = 0
|
||||
animateArcDuration = 0
|
||||
checkmarkAnimationDelayDuration = 0
|
||||
checkmarkAnimationDuration = 0
|
||||
}
|
||||
|
||||
private fun rollBackCompletionAnimation() {
|
||||
if (!complete) {
|
||||
return
|
||||
}
|
||||
complete = false
|
||||
/** This sets animation timers back to normal, this happens after we have */
|
||||
private fun restoreAnimationTime() {
|
||||
fillColorAnimationDuration = FILL_COLOR_ANIMATION_DURATION_MS
|
||||
animateArcDuration = PROGRESS_ANIMATION_DURATION_MS
|
||||
checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS
|
||||
checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS
|
||||
}
|
||||
|
||||
private fun loadResources(context: Context, attrs: AttributeSet?) {}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UdfpsProgressBar"
|
||||
private const val FILL_COLOR_ANIMATION_DURATION_MS = 350L
|
||||
private const val PROGRESS_ANIMATION_DURATION_MS = 400L
|
||||
private const val CHECKMARK_ANIMATION_DELAY_MS = 200L
|
||||
private const val CHECKMARK_ANIMATION_DURATION_MS = 300L
|
||||
private const val STROKE_WIDTH_DP = 12f
|
||||
private val DEACCEL: Interpolator = DecelerateInterpolator()
|
||||
private val VIBRATE_EFFECT_ERROR = VibrationEffect.createWaveform(longArrayOf(0, 5, 55, 60), -1)
|
||||
private val FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY)
|
||||
private val HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)
|
||||
private val SUCCESS_VIBRATION_EFFECT = VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,59 +17,99 @@
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.DisplayInfo
|
||||
import android.view.MotionEvent
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.View.OnHoverListener
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.Acquired
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.OverlayShown
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.PointerDown
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.PointerUp
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsEnrollEvent
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsError
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsHelp
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsProgress
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
import com.android.systemui.biometrics.UdfpsUtils
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
|
||||
import com.android.systemui.biometrics.shared.model.toInt
|
||||
|
||||
/**
|
||||
* View corresponding with fingerprint_v2_udfps_enroll_view.xml. This view is responsible for
|
||||
* drawing the [UdfpsEnrollIconV2] and the [UdfpsEnrollProgressBarDrawableV2].
|
||||
*/
|
||||
class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
|
||||
private lateinit var fingerprintSensorType: FingerprintSensorType
|
||||
private var onHoverListener: OnHoverListener = OnHoverListener { _, _ -> false }
|
||||
private var isAccessibilityEnabled: Boolean = false
|
||||
private lateinit var sensorRect: Rect
|
||||
private val fingerprintIcon: UdfpsEnrollIconV2 = UdfpsEnrollIconV2(mContext, attrs)
|
||||
private val fingerprintProgressDrawable: UdfpsEnrollProgressBarDrawableV2 =
|
||||
UdfpsEnrollProgressBarDrawableV2(mContext, attrs)
|
||||
private var mTotalSteps = -1
|
||||
private var mRemainingSteps = -1
|
||||
private var remainingSteps = -1
|
||||
private val udfpsUtils: UdfpsUtils = UdfpsUtils()
|
||||
private lateinit var touchExplorationAnnouncer: TouchExplorationAnnouncer
|
||||
private var isRecreating = false
|
||||
|
||||
/**
|
||||
* This function computes the center (x,y) location with respect to the parent [FrameLayout] for
|
||||
* the [UdfpsEnrollProgressBarDrawableV2]. It also computes the [Rect] with respect to the parent
|
||||
* [FrameLayout] for the [UdfpsEnrollIconV2].
|
||||
* [FrameLayout] for the [UdfpsEnrollIconV2]. This function will also setup the
|
||||
* [touchExplorationAnnouncer]
|
||||
*/
|
||||
fun setSensorRect(rect: Rect) {
|
||||
fun setSensorRect(rect: Rect, sensorType: FingerprintSensorType) {
|
||||
this.sensorRect = rect
|
||||
|
||||
this.fingerprintSensorType = sensorType
|
||||
findViewById<ImageView?>(R.id.udfps_enroll_animation_fp_progress_view)?.also {
|
||||
it.setImageDrawable(fingerprintProgressDrawable)
|
||||
}
|
||||
findViewById<ImageView>(R.id.udfps_enroll_animation_fp_view)?.also {
|
||||
it.setImageDrawable(fingerprintIcon)
|
||||
}
|
||||
|
||||
val rotation = display.rotation
|
||||
var displayInfo = DisplayInfo()
|
||||
context.display.getDisplayInfo(displayInfo)
|
||||
val scaleFactor = udfpsUtils.getScaleFactor(displayInfo)
|
||||
val overlayParams =
|
||||
UdfpsOverlayParams(
|
||||
sensorRect,
|
||||
fingerprintProgressDrawable.bounds,
|
||||
displayInfo.naturalWidth,
|
||||
displayInfo.naturalHeight,
|
||||
scaleFactor,
|
||||
rotation,
|
||||
sensorType.toInt(),
|
||||
)
|
||||
val parentView = parent as ViewGroup
|
||||
val coords = parentView.getLocationOnScreen()
|
||||
val parentLeft = coords[0]
|
||||
val parentTop = coords[1]
|
||||
val sensorRectOffset = Rect(sensorRect)
|
||||
// If the view has been rotated, we need to translate the sensor coordinates
|
||||
// to the new rotated view.
|
||||
when (rotation) {
|
||||
Surface.ROTATION_90,
|
||||
Surface.ROTATION_270 -> {
|
||||
sensorRectOffset.set(
|
||||
sensorRectOffset.top,
|
||||
sensorRectOffset.left,
|
||||
sensorRectOffset.bottom,
|
||||
sensorRectOffset.right,
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// Translate the sensor position into UdfpsEnrollView's view space.
|
||||
sensorRectOffset.offset(-parentLeft, -parentTop)
|
||||
|
||||
fingerprintIcon.drawSensorRectAt(sensorRectOffset)
|
||||
fingerprintProgressDrawable.drawProgressAt(sensorRectOffset)
|
||||
|
||||
touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils)
|
||||
}
|
||||
|
||||
/** Updates the current enrollment stage. */
|
||||
@@ -78,15 +118,17 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
|
||||
}
|
||||
|
||||
/** Receive enroll progress event */
|
||||
fun onUdfpsEvent(event: UdfpsEnrollEvent) {
|
||||
fun onUdfpsEvent(event: FingerEnrollState) {
|
||||
when (event) {
|
||||
is UdfpsProgress -> onEnrollmentProgress(event.remainingSteps, event.totalSteps)
|
||||
is Acquired -> onAcquired(event.acquiredGood)
|
||||
is UdfpsHelp -> onEnrollmentHelp()
|
||||
is PointerDown -> onPointerDown()
|
||||
is PointerUp -> onPointerUp()
|
||||
OverlayShown -> overlayShown()
|
||||
is UdfpsError -> udfpsError(event.errMsgId, event.errString)
|
||||
is FingerEnrollState.EnrollProgress ->
|
||||
onEnrollmentProgress(event.remainingSteps, event.totalStepsRequired)
|
||||
is FingerEnrollState.Acquired -> onAcquired(event.acquiredGood)
|
||||
is FingerEnrollState.EnrollHelp -> onEnrollmentHelp()
|
||||
is FingerEnrollState.PointerDown -> onPointerDown()
|
||||
is FingerEnrollState.PointerUp -> onPointerUp()
|
||||
is FingerEnrollState.OverlayShown -> overlayShown()
|
||||
is FingerEnrollState.EnrollError ->
|
||||
throw IllegalArgumentException("$TAG should not handle udfps error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,9 +136,37 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
|
||||
fun setAccessibilityEnabled(enabled: Boolean) {
|
||||
this.isAccessibilityEnabled = enabled
|
||||
fingerprintProgressDrawable.setAccessibilityEnabled(enabled)
|
||||
if (enabled) {
|
||||
addHoverListener()
|
||||
} else {
|
||||
clearHoverListener()
|
||||
}
|
||||
}
|
||||
|
||||
private fun udfpsError(errMsgId: Int, errString: String) {}
|
||||
/**
|
||||
* Sends a touch exploration event to the [onHoverListener] this should only be used for
|
||||
* debugging.
|
||||
*/
|
||||
fun sendDebugTouchExplorationEvent(motionEvent: MotionEvent) {
|
||||
touchExplorationAnnouncer.onTouch(motionEvent)
|
||||
}
|
||||
|
||||
/** Sets the addHoverListener, this should happen when talkback is enabled. */
|
||||
private fun addHoverListener() {
|
||||
onHoverListener = OnHoverListener { _: View, event: MotionEvent ->
|
||||
sendDebugTouchExplorationEvent(event)
|
||||
false
|
||||
}
|
||||
this.setOnHoverListener(onHoverListener)
|
||||
}
|
||||
|
||||
/** Clears the hover listener if one was set. */
|
||||
private fun clearHoverListener() {
|
||||
val listener = OnHoverListener { _, _ -> false }
|
||||
this.setOnHoverListener(listener)
|
||||
onHoverListener = listener
|
||||
}
|
||||
|
||||
private fun overlayShown() {
|
||||
Log.e(TAG, "Implement overlayShown")
|
||||
@@ -115,7 +185,7 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
|
||||
|
||||
/** Receive onAcquired event */
|
||||
private fun onAcquired(isAcquiredGood: Boolean) {
|
||||
val animateIfLastStepGood = isAcquiredGood && mRemainingSteps <= 2 && mRemainingSteps >= 0
|
||||
val animateIfLastStepGood = isAcquiredGood && remainingSteps <= 2 && remainingSteps >= 0
|
||||
if (animateIfLastStepGood) fingerprintProgressDrawable.onLastStepAcquired()
|
||||
}
|
||||
|
||||
@@ -129,6 +199,52 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
|
||||
fingerprintIcon.startDrawing()
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
// Because the layout has changed, we need to recompute all locations.
|
||||
if (this::sensorRect.isInitialized && this::fingerprintSensorType.isInitialized) {
|
||||
setSensorRect(sensorRect, fingerprintSensorType)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is responsible for announcing touch events that are outside of the sensort rect
|
||||
* area. Generally, if a touch is to the left of the sensor, the accessibility announcement will
|
||||
* be something like "move right"
|
||||
*/
|
||||
private class TouchExplorationAnnouncer(
|
||||
val context: Context,
|
||||
val view: View,
|
||||
val overlayParams: UdfpsOverlayParams,
|
||||
val udfpsUtils: UdfpsUtils,
|
||||
) {
|
||||
/** Will announce accessibility event for touches outside of the sensor rect. */
|
||||
fun onTouch(event: MotionEvent) {
|
||||
val scaledTouch: Point =
|
||||
udfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), event, overlayParams)
|
||||
if (udfpsUtils.isWithinSensorArea(event.getPointerId(0), event, overlayParams)) {
|
||||
return
|
||||
}
|
||||
val theStr: String =
|
||||
udfpsUtils.onTouchOutsideOfSensorArea(
|
||||
true /*touchExplorationEnabled*/,
|
||||
context,
|
||||
scaledTouch.x,
|
||||
scaledTouch.y,
|
||||
overlayParams,
|
||||
)
|
||||
if (theStr != null) {
|
||||
view.announceForAccessibility(theStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Indicates we should should restore the views saved state. */
|
||||
fun onEnrollProgressSaved(it: FingerEnrollState.EnrollProgress) {
|
||||
fingerprintIcon.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true)
|
||||
fingerprintProgressDrawable.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UdfpsEnrollView"
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -63,7 +65,7 @@ class FingerprintEnrollEnrollingViewModel(
|
||||
}
|
||||
|
||||
/** Collects the enrollment flow based on [enrollFlowShouldBeRunning] */
|
||||
val enrollFLow =
|
||||
val enrollFlow =
|
||||
enrollFlowShouldBeRunning.transformLatest {
|
||||
if (it) {
|
||||
fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) }
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Orientatio
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -38,7 +37,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** Models the UI state for [FingerprintEnrollFindSensorV2Fragment]. */
|
||||
/** Models the UI state for fingerprint enroll education */
|
||||
class FingerprintEnrollFindSensorViewModel(
|
||||
private val navigationViewModel: FingerprintNavigationViewModel,
|
||||
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
|
||||
@@ -70,7 +69,7 @@ class FingerprintEnrollFindSensorViewModel(
|
||||
combineTransform(
|
||||
_showSfpsLottie,
|
||||
foldStateInteractor.isFolded,
|
||||
orientationInteractor.rotation,
|
||||
orientationInteractor.rotationFromDefault,
|
||||
) { _, isFolded, rotation ->
|
||||
emit(Pair(isFolded, rotation))
|
||||
}
|
||||
@@ -147,6 +146,7 @@ class FingerprintEnrollFindSensorViewModel(
|
||||
}
|
||||
}
|
||||
is FingerEnrollState.EnrollHelp -> {}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,12 +45,14 @@ import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.Settings
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.util.toFingerprintEnrollOptions
|
||||
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder
|
||||
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel
|
||||
@@ -222,6 +224,13 @@ class FingerprintSettingsV2Fragment :
|
||||
val fingerprintSensorProvider =
|
||||
FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
|
||||
val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher)
|
||||
val fingerprintEnrollStateRepository =
|
||||
FingerprintEnrollInteractorImpl(
|
||||
requireContext().applicationContext,
|
||||
intent.toFingerprintEnrollOptions(),
|
||||
fingerprintManager,
|
||||
Settings,
|
||||
)
|
||||
|
||||
val interactor =
|
||||
FingerprintManagerInteractorImpl(
|
||||
@@ -230,9 +239,7 @@ class FingerprintSettingsV2Fragment :
|
||||
fingerprintManager,
|
||||
fingerprintSensorProvider,
|
||||
GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)),
|
||||
pressToAuthInteractor,
|
||||
Settings,
|
||||
getIntent()
|
||||
fingerprintEnrollStateRepository,
|
||||
)
|
||||
|
||||
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
@@ -27,6 +28,7 @@ import androidx.preference.PreferenceGroup;
|
||||
import androidx.preference.PreferenceGroupAdapter;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.flags.Flags;
|
||||
import com.android.settingslib.widget.theme.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -68,14 +70,47 @@ public class RoundCornerPreferenceAdapter extends PreferenceGroupAdapter {
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PreferenceViewHolder holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
updateBackground(holder, position);
|
||||
if (Flags.homepageRevamp()) {
|
||||
updateBackground(holder, position);
|
||||
}
|
||||
}
|
||||
|
||||
protected @DrawableRes int getRoundCornerDrawableRes(int position, boolean isSelected) {
|
||||
int CornerType = mRoundCornerMappingList.get(position);
|
||||
|
||||
if ((CornerType & ROUND_CORNER_CENTER) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (((CornerType & ROUND_CORNER_TOP) != 0) && ((CornerType & ROUND_CORNER_BOTTOM) == 0)) {
|
||||
// the first
|
||||
return isSelected ? R.drawable.settingslib_round_background_top_selected
|
||||
: R.drawable.settingslib_round_background_top;
|
||||
} else if (((CornerType & ROUND_CORNER_BOTTOM) != 0)
|
||||
&& ((CornerType & ROUND_CORNER_TOP) == 0)) {
|
||||
// the last
|
||||
return isSelected ? R.drawable.settingslib_round_background_bottom_selected
|
||||
: R.drawable.settingslib_round_background_bottom;
|
||||
} else if (((CornerType & ROUND_CORNER_TOP) != 0)
|
||||
&& ((CornerType & ROUND_CORNER_BOTTOM) != 0)) {
|
||||
// the only one preference
|
||||
return isSelected ? R.drawable.settingslib_round_background_selected
|
||||
: R.drawable.settingslib_round_background;
|
||||
} else {
|
||||
// in the center
|
||||
return isSelected ? R.drawable.settingslib_round_background_center_selected
|
||||
: R.drawable.settingslib_round_background_center;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
private void updatePreferences() {
|
||||
mRoundCornerMappingList = new ArrayList<>();
|
||||
mappingPreferenceGroup(mRoundCornerMappingList, mPreferenceGroup);
|
||||
if (Flags.homepageRevamp()) {
|
||||
mRoundCornerMappingList = new ArrayList<>();
|
||||
mappingPreferenceGroup(mRoundCornerMappingList, mPreferenceGroup);
|
||||
}
|
||||
}
|
||||
|
||||
private void mappingPreferenceGroup(List<Integer> visibleList, PreferenceGroup group) {
|
||||
int groupSize = group.getPreferenceCount();
|
||||
int firstVisible = 0;
|
||||
@@ -125,27 +160,9 @@ public class RoundCornerPreferenceAdapter extends PreferenceGroupAdapter {
|
||||
|
||||
/** handle roundCorner background */
|
||||
private void updateBackground(PreferenceViewHolder holder, int position) {
|
||||
int CornerType = mRoundCornerMappingList.get(position);
|
||||
|
||||
if ((CornerType & ROUND_CORNER_CENTER) == 0) {
|
||||
return;
|
||||
}
|
||||
@DrawableRes int backgroundRes = getRoundCornerDrawableRes(position, false /* isSelected*/);
|
||||
|
||||
View v = holder.itemView;
|
||||
if (((CornerType & ROUND_CORNER_TOP) != 0) && ((CornerType & ROUND_CORNER_BOTTOM) == 0)) {
|
||||
// the first
|
||||
v.setBackgroundResource(R.drawable.settingslib_round_background_top);
|
||||
} else if (((CornerType & ROUND_CORNER_BOTTOM) != 0)
|
||||
&& ((CornerType & ROUND_CORNER_TOP) == 0)) {
|
||||
// the last
|
||||
v.setBackgroundResource(R.drawable.settingslib_round_background_bottom);
|
||||
} else if (((CornerType & ROUND_CORNER_TOP) != 0)
|
||||
&& ((CornerType & ROUND_CORNER_BOTTOM) != 0)) {
|
||||
// the only one preference
|
||||
v.setBackgroundResource(R.drawable.settingslib_round_background);
|
||||
} else {
|
||||
// in the center
|
||||
v.setBackgroundResource(R.drawable.settingslib_round_background_center);
|
||||
}
|
||||
v.setBackgroundResource(backgroundRes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,17 +96,17 @@ public class BluetoothLeAudioModePreferenceController
|
||||
return;
|
||||
}
|
||||
|
||||
String currentValue;
|
||||
if (mBluetoothAdapter.isLeAudioBroadcastSourceSupported()
|
||||
== BluetoothStatusCodes.FEATURE_SUPPORTED) {
|
||||
SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, "broadcast");
|
||||
currentValue = "broadcast";
|
||||
} else if (mBluetoothAdapter.isLeAudioSupported()
|
||||
== BluetoothStatusCodes.FEATURE_SUPPORTED) {
|
||||
SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, "unicast");
|
||||
currentValue = "unicast";
|
||||
} else {
|
||||
SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, "disabled");
|
||||
currentValue = "disabled";
|
||||
}
|
||||
|
||||
final String currentValue = SystemProperties.get(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY);
|
||||
int index = 0;
|
||||
for (int i = 0; i < mListValues.length; i++) {
|
||||
if (TextUtils.equals(currentValue, mListValues[i])) {
|
||||
@@ -118,6 +118,11 @@ public class BluetoothLeAudioModePreferenceController
|
||||
final ListPreference listPreference = (ListPreference) preference;
|
||||
listPreference.setValue(mListValues[index]);
|
||||
listPreference.setSummary(mListSummaries[index]);
|
||||
|
||||
if (!mBluetoothAdapter.isEnabled()) {
|
||||
listPreference.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -406,7 +406,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements
|
||||
|
||||
private void initSearchBarView() {
|
||||
if (Flags.homepageRevamp()) {
|
||||
Toolbar toolbar = findViewById(R.id.search_action_bar_unified);
|
||||
View toolbar = findViewById(R.id.search_action_bar);
|
||||
FeatureFactory.getFeatureFactory().getSearchFeatureProvider()
|
||||
.initSearchToolbar(this /* activity */, toolbar,
|
||||
SettingsEnums.SETTINGS_HOMEPAGE);
|
||||
@@ -469,6 +469,10 @@ public class SettingsHomepageActivity extends FragmentActivity implements
|
||||
window.setStatusBarColor(color);
|
||||
// Update content background.
|
||||
findViewById(android.R.id.content).setBackgroundColor(color);
|
||||
if (Flags.homepageRevamp()) {
|
||||
//Update search bar background
|
||||
findViewById(R.id.app_bar_container).setBackgroundColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
private void showSuggestionFragment(boolean scrollNeeded) {
|
||||
|
||||
@@ -109,21 +109,21 @@ fun Context.getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
|
||||
// to users so they should never be returned.
|
||||
SubscriptionUtil.isSubscriptionVisible(subscriptionManager, this, subInfo)
|
||||
}
|
||||
// Multiple subscriptions in a group should only have one representative.
|
||||
// It should be the current active primary subscription if any, or any primary subscription.
|
||||
val groupUuidToSelectedIdMap = visibleList
|
||||
.groupBy { it.groupUuid }
|
||||
.mapValues { (_, subInfos) ->
|
||||
subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX }
|
||||
.ifEmpty { subInfos }
|
||||
.minOf { it.subscriptionId }
|
||||
}
|
||||
|
||||
return visibleList
|
||||
.filter { subInfo ->
|
||||
val groupUuid = subInfo.groupUuid ?: return@filter true
|
||||
groupUuidToSelectedIdMap[groupUuid] == subInfo.subscriptionId
|
||||
.groupBy { it.groupUuid }
|
||||
.flatMap { (groupUuid, subInfos) ->
|
||||
if (groupUuid == null) {
|
||||
subInfos
|
||||
} else {
|
||||
// Multiple subscriptions in a group should only have one representative.
|
||||
// It should be the current active primary subscription if any, or the primary
|
||||
// subscription with minimum subscription id.
|
||||
subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX }
|
||||
.ifEmpty { subInfos.sortedBy { it.subscriptionId } }
|
||||
.take(1)
|
||||
}
|
||||
}
|
||||
.sortedBy { it.subscriptionId }
|
||||
// Matching the sorting order in SubscriptionManagerService.getAvailableSubscriptionInfoList
|
||||
.sortedWith(compareBy({ it.simSlotIndex }, { it.subscriptionId }))
|
||||
.also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") }
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.app.Flags;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -55,6 +56,11 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
|
||||
return mKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return Flags.modesUi();
|
||||
}
|
||||
|
||||
// Called by the parent Fragment onStart, which means it will happen before resume.
|
||||
public void updateZenMode(@NonNull Preference preference, @NonNull ZenMode zenMode) {
|
||||
mZenMode = zenMode;
|
||||
|
||||
@@ -155,8 +155,8 @@ class ZenMode {
|
||||
int iconResIdFromType = switch (mRule.getType()) {
|
||||
case AutomaticZenRule.TYPE_UNKNOWN -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_OTHER -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_SCHEDULE_TIME -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_SCHEDULE_TIME -> R.drawable.ic_modes_time;
|
||||
case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR -> R.drawable.ic_modes_event;
|
||||
case AutomaticZenRule.TYPE_BEDTIME -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_DRIVING -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_IMMERSIVE -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
|
||||
@@ -41,6 +41,7 @@ public class ZenModeFragment extends ZenModeFragmentBase {
|
||||
// TODO: fill in with all the elements of this page. Each should be an instance of
|
||||
// {@link AbstractZenModePreferenceController}.
|
||||
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
|
||||
prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend));
|
||||
return prefControllers;
|
||||
}
|
||||
|
||||
@@ -55,19 +56,6 @@ public class ZenModeFragment extends ZenModeFragmentBase {
|
||||
return;
|
||||
}
|
||||
getActivity().setTitle(azr.getName());
|
||||
|
||||
// TODO: b/308819292 - implement the real screen!
|
||||
final PreferenceScreen screen = getPreferenceScreen();
|
||||
if (screen == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Preference tmpPref = screen.findPreference("zen_mode_test");
|
||||
if (tmpPref == null) {
|
||||
return;
|
||||
}
|
||||
tmpPref.setTitle(azr.getTriggerDescription());
|
||||
tmpPref.setSummary("active?: " + mode.isActive());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.notification.modes;
|
||||
|
||||
import android.app.Flags;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.widget.EntityHeaderController;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ZenModeHeaderController extends AbstractZenModePreferenceController {
|
||||
|
||||
private final DashboardFragment mFragment;
|
||||
private EntityHeaderController mHeaderController;
|
||||
|
||||
ZenModeHeaderController(
|
||||
@NonNull Context context,
|
||||
@NonNull String key,
|
||||
@NonNull DashboardFragment fragment,
|
||||
@Nullable ZenModesBackend backend) {
|
||||
super(context, key, backend);
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return Flags.modesApi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(Preference preference) {
|
||||
if (getAZR() == null || mFragment == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHeaderController == null) {
|
||||
final LayoutPreference pref = (LayoutPreference) preference;
|
||||
mHeaderController = EntityHeaderController.newInstance(
|
||||
mFragment.getActivity(),
|
||||
mFragment,
|
||||
pref.findViewById(R.id.entity_header));
|
||||
}
|
||||
Drawable icon = null;
|
||||
try {
|
||||
icon = getMode().getIcon(mContext).get(200, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
// no icon
|
||||
}
|
||||
mHeaderController.setIcon(icon)
|
||||
.setLabel(getAZR().getName())
|
||||
.done(false /* rebindActions */);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,10 @@ import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.notification.zen.ZenModeSettings;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Preference representing a single mode item on the modes aggregator page. Clicking on this
|
||||
* preference leads to an individual mode's configuration page.
|
||||
@@ -36,10 +40,8 @@ public class ZenModeListPreference extends RestrictedPreference {
|
||||
ZenModeListPreference(Context context, ZenMode zenMode) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mZenMode = zenMode;
|
||||
setTitle(mZenMode.getRule().getName());
|
||||
setSummary((mZenMode.isActive() ? "ACTIVE" : "inactive") + ": "
|
||||
+ mZenMode.getRule().getTriggerDescription());
|
||||
setZenMode(zenMode);
|
||||
setKey(zenMode.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,6 +62,16 @@ public class ZenModeListPreference extends RestrictedPreference {
|
||||
.setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION)
|
||||
.launch();
|
||||
}
|
||||
}
|
||||
|
||||
public void setZenMode(ZenMode zenMode) {
|
||||
mZenMode = zenMode;
|
||||
setTitle(mZenMode.getRule().getName());
|
||||
setSummary(mZenMode.getRule().getTriggerDescription());
|
||||
try {
|
||||
setIcon(mZenMode.getIcon(mContext).get(200, TimeUnit.MILLISECONDS));
|
||||
} catch (Exception e) {
|
||||
// no icon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.android.settings.R;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -77,7 +78,15 @@ class ZenModesBackend {
|
||||
isRuleActive(ruleId, currentConfig)));
|
||||
}
|
||||
|
||||
// TODO: b/331429435 - Sort modes.
|
||||
modes.sort((l, r) -> {
|
||||
if (l.isManualDnd()) {
|
||||
return -1;
|
||||
} else if (r.isManualDnd()) {
|
||||
return 1;
|
||||
}
|
||||
return l.getRule().getName().compareTo(r.getRule().getName());
|
||||
});
|
||||
|
||||
return modes;
|
||||
}
|
||||
|
||||
@@ -105,7 +114,6 @@ class ZenModesBackend {
|
||||
.setZenPolicy(ZenAdapters.notificationPolicyToZenPolicy(
|
||||
mNotificationManager.getNotificationPolicy()))
|
||||
.setDeviceEffects(null)
|
||||
.setTriggerDescription(mContext.getString(R.string.zen_mode_settings_summary))
|
||||
.setManualInvocationAllowed(true)
|
||||
.setConfigurationActivity(null) // No further settings
|
||||
.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.app.Flags;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -24,14 +26,20 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settingslib.search.SearchIndexableRaw;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Controller for the PreferenceCategory on the modes aggregator page ({@link ZenModesListFragment})
|
||||
* containing links to each individual mode. This is a central controller that populates and updates
|
||||
* all the preferences that then lead to a mode configuration page.
|
||||
*/
|
||||
public class ZenModesListPreferenceController extends AbstractPreferenceController {
|
||||
public class ZenModesListPreferenceController extends BasePreferenceController {
|
||||
protected static final String KEY = "zen_modes_list";
|
||||
|
||||
@Nullable
|
||||
@@ -40,7 +48,7 @@ public class ZenModesListPreferenceController extends AbstractPreferenceControll
|
||||
|
||||
public ZenModesListPreferenceController(Context context, @Nullable Fragment parent,
|
||||
@NonNull ZenModesBackend backend) {
|
||||
super(context);
|
||||
super(context, KEY);
|
||||
mParent = parent;
|
||||
mBackend = backend;
|
||||
}
|
||||
@@ -51,8 +59,9 @@ public class ZenModesListPreferenceController extends AbstractPreferenceControll
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return Flags.modesUi();
|
||||
@AvailabilityStatus
|
||||
public int getAvailabilityStatus() {
|
||||
return Flags.modesUi() ? AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,16 +74,53 @@ public class ZenModesListPreferenceController extends AbstractPreferenceControll
|
||||
// category for each rule that exists.
|
||||
PreferenceCategory category = (PreferenceCategory) preference;
|
||||
|
||||
// TODO: b/322373473 - This is not the right way to replace these preferences; we should
|
||||
// follow something similar to what
|
||||
// ZenModeAutomaticRulesPreferenceController does to change rules
|
||||
// only as necessary and update them.
|
||||
category.removeAll();
|
||||
Map<String, ZenModeListPreference> originalPreferences = new HashMap<>();
|
||||
for (int i = 0; i < category.getPreferenceCount(); i++) {
|
||||
ZenModeListPreference pref = (ZenModeListPreference) category.getPreference(i);
|
||||
originalPreferences.put(pref.getKey(), pref);
|
||||
}
|
||||
|
||||
// Loop through each rule, either updating the existing rule or creating the rule's
|
||||
// preference
|
||||
for (ZenMode mode : mBackend.getModes()) {
|
||||
Preference pref = new ZenModeListPreference(mContext, mode);
|
||||
category.addPreference(pref);
|
||||
if (originalPreferences.containsKey(mode.getId())) {
|
||||
// existing rule; update its info if it's changed since the last display
|
||||
AutomaticZenRule rule = mode.getRule();
|
||||
originalPreferences.get(mode.getId()).setZenMode(mode);
|
||||
} else {
|
||||
// new rule; create a new ZenRulePreference & add it to the preference category
|
||||
Preference pref = new ZenModeListPreference(mContext, mode);
|
||||
category.addPreference(pref);
|
||||
}
|
||||
|
||||
originalPreferences.remove(mode.getId());
|
||||
}
|
||||
// Remove preferences that no longer have a rule
|
||||
for (String key : originalPreferences.keySet()) {
|
||||
category.removePreferenceRecursively(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Provide search data for the modes, which will allow users to reach the modes page if they
|
||||
// search for a mode name.
|
||||
@Override
|
||||
public void updateDynamicRawDataToIndex(List<SearchIndexableRaw> rawData) {
|
||||
// Don't add anything if flag is off. In theory this preference controller itself shouldn't
|
||||
// be available in that case, but we check anyway to be sure.
|
||||
if (!Flags.modesUi()) {
|
||||
return;
|
||||
}
|
||||
if (mBackend == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Resources res = mContext.getResources();
|
||||
for (ZenMode mode : mBackend.getModes()) {
|
||||
SearchIndexableRaw data = new SearchIndexableRaw(mContext);
|
||||
data.key = mode.getId();
|
||||
data.title = mode.getRule().getName();
|
||||
data.screenTitle = res.getString(R.string.zen_modes_list_title);
|
||||
rawData.add(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +448,7 @@ public class PrivateSpaceMaintainer {
|
||||
private void removeSettingsAllTasks() {
|
||||
List<ActivityManager.AppTask> appTasks = mActivityManager.getAppTasks();
|
||||
for (var appTask : appTasks) {
|
||||
if (!appTask.getTaskInfo().isVisible()) {
|
||||
if (!(appTask.getTaskInfo().isVisible() || appTask.getTaskInfo().isFocused)) {
|
||||
appTask.finishAndRemoveTask();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.android.settings.R;
|
||||
@@ -80,8 +81,9 @@ public interface SearchFeatureProvider {
|
||||
/**
|
||||
* Initializes the search toolbar.
|
||||
*/
|
||||
default void initSearchToolbar(FragmentActivity activity, Toolbar toolbar, int pageId) {
|
||||
if (activity == null || toolbar == null) {
|
||||
default void initSearchToolbar(@NonNull FragmentActivity activity, @Nullable View toolbar,
|
||||
int pageId) {
|
||||
if (toolbar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,11 +100,13 @@ public interface SearchFeatureProvider {
|
||||
//
|
||||
// Need to make the navigation icon non-clickable so that the entire card is clickable
|
||||
// and goes to the search UI. Also set the background to null so there's no ripple.
|
||||
final View navView = toolbar.getNavigationView();
|
||||
navView.setClickable(false);
|
||||
navView.setFocusable(false);
|
||||
navView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
navView.setBackground(null);
|
||||
if (toolbar instanceof Toolbar) {
|
||||
final View navView = ((Toolbar) toolbar).getNavigationView();
|
||||
navView.setClickable(false);
|
||||
navView.setFocusable(false);
|
||||
navView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
navView.setBackground(null);
|
||||
}
|
||||
|
||||
final Context context = activity.getApplicationContext();
|
||||
final Intent intent = buildSearchIntent(context, pageId)
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.runtime.Composable
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import com.android.settingslib.R
|
||||
import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOpsController
|
||||
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
||||
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
|
||||
@@ -116,8 +117,7 @@ class AlarmsAndRemindersAppListModel(
|
||||
controller = AppOpsController(
|
||||
context = context,
|
||||
app = app,
|
||||
op = AppOpsManager.OP_SCHEDULE_EXACT_ALARM,
|
||||
setModeByUid = true,
|
||||
appOps = APP_OPS,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -136,6 +136,11 @@ class AlarmsAndRemindersAppListModel(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val APP_OPS = AppOps(
|
||||
op = AppOpsManager.OP_SCHEDULE_EXACT_ALARM,
|
||||
setModeByUid = true,
|
||||
)
|
||||
|
||||
private const val PERMISSION: String = Manifest.permission.SCHEDULE_EXACT_ALARM
|
||||
|
||||
/** Checks whether [Manifest.permission.SCHEDULE_EXACT_ALARM] is enabled. */
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.app.settings.SettingsEnums
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
|
||||
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
|
||||
@@ -35,9 +36,11 @@ class AllFilesAccessListModel(context: Context) : AppOpPermissionListModel(conte
|
||||
override val pageTitleResId = R.string.manage_external_storage_title
|
||||
override val switchTitleResId = R.string.permit_manage_external_storage
|
||||
override val footerResId = R.string.allow_manage_external_storage_description
|
||||
override val appOp = AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE
|
||||
override val appOps = AppOps(
|
||||
op = AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE,
|
||||
setModeByUid = true,
|
||||
)
|
||||
override val permission = Manifest.permission.MANAGE_EXTERNAL_STORAGE
|
||||
override val setModeByUid = true
|
||||
|
||||
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
|
||||
super.setAllowed(record, newAllowed)
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.app.settings.SettingsEnums
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
|
||||
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
|
||||
@@ -35,7 +36,7 @@ class DisplayOverOtherAppsListModel(context: Context) : AppOpPermissionListModel
|
||||
override val pageTitleResId = R.string.system_alert_window_settings
|
||||
override val switchTitleResId = R.string.permit_draw_overlay
|
||||
override val footerResId = R.string.allow_overlay_description
|
||||
override val appOp = AppOpsManager.OP_SYSTEM_ALERT_WINDOW
|
||||
override val appOps = AppOps(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
|
||||
override val permission = Manifest.permission.SYSTEM_ALERT_WINDOW
|
||||
|
||||
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.Manifest
|
||||
import android.app.AppGlobals
|
||||
import android.app.AppOpsManager
|
||||
import android.app.AppOpsManager.MODE_DEFAULT
|
||||
import android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.Process
|
||||
@@ -28,6 +27,7 @@ import android.os.UserManager
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOpsController
|
||||
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
@@ -62,12 +62,7 @@ class InstallUnknownAppsListModel(private val context: Context) :
|
||||
override fun transformItem(app: ApplicationInfo) =
|
||||
InstallUnknownAppsRecord(
|
||||
app = app,
|
||||
appOpsController =
|
||||
AppOpsController(
|
||||
context = context,
|
||||
app = app,
|
||||
op = OP_REQUEST_INSTALL_PACKAGES,
|
||||
),
|
||||
appOpsController = AppOpsController(context = context, app = app, appOps = APP_OPS),
|
||||
)
|
||||
|
||||
override fun filter(
|
||||
@@ -92,6 +87,8 @@ class InstallUnknownAppsListModel(private val context: Context) :
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val APP_OPS = AppOps(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES)
|
||||
|
||||
private fun isChangeable(
|
||||
record: InstallUnknownAppsRecord,
|
||||
potentialPackageNames: Set<String>,
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.app.settings.SettingsEnums
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
|
||||
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
|
||||
@@ -35,9 +36,11 @@ class LongBackgroundTasksAppsListModel(context: Context) : AppOpPermissionListMo
|
||||
override val pageTitleResId = R.string.long_background_tasks_title
|
||||
override val switchTitleResId = R.string.long_background_tasks_switch_title
|
||||
override val footerResId = R.string.long_background_tasks_footer_title
|
||||
override val appOp = AppOpsManager.OP_RUN_USER_INITIATED_JOBS
|
||||
override val appOps = AppOps(
|
||||
op = AppOpsManager.OP_RUN_USER_INITIATED_JOBS,
|
||||
setModeByUid = true,
|
||||
)
|
||||
override val permission = Manifest.permission.RUN_USER_INITIATED_JOBS
|
||||
override val setModeByUid = true
|
||||
|
||||
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
|
||||
super.setAllowed(record, newAllowed)
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.app.settings.SettingsEnums
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
|
||||
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
|
||||
@@ -35,9 +36,11 @@ class MediaManagementAppsListModel(context: Context) : AppOpPermissionListModel(
|
||||
override val pageTitleResId = R.string.media_management_apps_title
|
||||
override val switchTitleResId = R.string.media_management_apps_toggle_label
|
||||
override val footerResId = R.string.media_management_apps_description
|
||||
override val appOp = AppOpsManager.OP_MANAGE_MEDIA
|
||||
override val appOps = AppOps(
|
||||
op = AppOpsManager.OP_MANAGE_MEDIA,
|
||||
setModeByUid = true,
|
||||
)
|
||||
override val permission = Manifest.permission.MANAGE_MEDIA
|
||||
override val setModeByUid = true
|
||||
|
||||
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
|
||||
super.setAllowed(record, newAllowed)
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.content.Context
|
||||
import com.android.media.flags.Flags;
|
||||
import com.android.settings.R
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
|
||||
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
|
||||
@@ -38,9 +39,11 @@ class MediaRoutingControlAppsListModel(context: Context) : AppOpPermissionListMo
|
||||
override val pageTitleResId = R.string.media_routing_control_title
|
||||
override val switchTitleResId = R.string.allow_media_routing_control
|
||||
override val footerResId = R.string.allow_media_routing_description
|
||||
override val appOp = AppOpsManager.OP_MEDIA_ROUTING_CONTROL
|
||||
override val appOps = AppOps(
|
||||
op = AppOpsManager.OP_MEDIA_ROUTING_CONTROL,
|
||||
setModeByUid = true,
|
||||
)
|
||||
override val permission = Manifest.permission.MEDIA_ROUTING_CONTROL
|
||||
override val setModeByUid = true
|
||||
private val roleManager = context.getSystemService(RoleManager::class.java)
|
||||
|
||||
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.app.settings.SettingsEnums
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
|
||||
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
|
||||
@@ -35,7 +36,7 @@ class ModifySystemSettingsListModel(context: Context) : AppOpPermissionListModel
|
||||
override val pageTitleResId = R.string.write_system_settings
|
||||
override val switchTitleResId = R.string.permit_write_settings
|
||||
override val footerResId = R.string.write_settings_description
|
||||
override val appOp = AppOpsManager.OP_WRITE_SETTINGS
|
||||
override val appOps = AppOps(AppOpsManager.OP_WRITE_SETTINGS)
|
||||
override val permission = Manifest.permission.WRITE_SETTINGS
|
||||
|
||||
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.settings.spa.app.specialaccess
|
||||
|
||||
import android.app.AppOpsManager
|
||||
import android.app.AppOpsManager.OP_PICTURE_IN_PICTURE
|
||||
import android.content.Context
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.ApplicationInfo
|
||||
@@ -28,6 +27,7 @@ import android.util.Log
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOpsController
|
||||
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
||||
import com.android.settingslib.spaprivileged.model.app.installed
|
||||
@@ -79,12 +79,7 @@ class PictureInPictureListModel(private val context: Context) :
|
||||
PictureInPictureRecord(
|
||||
app = app,
|
||||
isSupport = isSupport,
|
||||
appOpsController =
|
||||
AppOpsController(
|
||||
context = context,
|
||||
app = app,
|
||||
op = OP_PICTURE_IN_PICTURE,
|
||||
),
|
||||
appOpsController = AppOpsController(context = context, app = app, appOps = APP_OPS),
|
||||
)
|
||||
|
||||
override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<PictureInPictureRecord>>) =
|
||||
@@ -131,6 +126,8 @@ class PictureInPictureListModel(private val context: Context) :
|
||||
companion object {
|
||||
private const val TAG = "PictureInPictureListModel"
|
||||
|
||||
private val APP_OPS = AppOps(AppOpsManager.OP_PICTURE_IN_PICTURE)
|
||||
|
||||
private fun PackageInfo.supportsPictureInPicture() =
|
||||
activities?.any(ActivityInfo::supportsPictureInPicture) ?: false
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ import android.Manifest
|
||||
import android.app.AppOpsManager
|
||||
import android.app.settings.SettingsEnums
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
|
||||
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
|
||||
@@ -35,9 +35,11 @@ class TurnScreenOnAppsListModel(context: Context) : AppOpPermissionListModel(con
|
||||
override val pageTitleResId = com.android.settingslib.R.string.turn_screen_on_title
|
||||
override val switchTitleResId = com.android.settingslib.R.string.allow_turn_screen_on
|
||||
override val footerResId = com.android.settingslib.R.string.allow_turn_screen_on_description
|
||||
override val appOp = AppOpsManager.OP_TURN_SCREEN_ON
|
||||
override val appOps = AppOps(
|
||||
op = AppOpsManager.OP_TURN_SCREEN_ON,
|
||||
setModeByUid = true,
|
||||
)
|
||||
override val permission = Manifest.permission.TURN_SCREEN_ON
|
||||
override val setModeByUid = true
|
||||
|
||||
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
|
||||
super.setAllowed(record, newAllowed)
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.Manifest
|
||||
import android.app.AppOpsManager
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
|
||||
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
|
||||
|
||||
@@ -32,7 +33,9 @@ class UseFullScreenIntentListModel(context: Context) : AppOpPermissionListModel(
|
||||
override val pageTitleResId = R.string.full_screen_intent_title
|
||||
override val switchTitleResId = R.string.permit_full_screen_intent
|
||||
override val footerResId = R.string.footer_description_full_screen_intent
|
||||
override val appOp = AppOpsManager.OP_USE_FULL_SCREEN_INTENT
|
||||
override val appOps = AppOps(
|
||||
op = AppOpsManager.OP_USE_FULL_SCREEN_INTENT,
|
||||
setModeByUid = true,
|
||||
)
|
||||
override val permission = Manifest.permission.USE_FULL_SCREEN_INTENT
|
||||
override val setModeByUid = true
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.app.AppOpsManager
|
||||
import android.app.AppOpsManager.MODE_IGNORED
|
||||
import android.content.Context
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
|
||||
import com.android.settingslib.spaprivileged.model.app.PackageManagers
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
|
||||
@@ -39,11 +40,13 @@ class WifiControlAppListModel(
|
||||
override val switchTitleResId = R.string.change_wifi_state_app_detail_switch
|
||||
override val footerResId = R.string.change_wifi_state_app_detail_summary
|
||||
|
||||
override val appOp = AppOpsManager.OP_CHANGE_WIFI_STATE
|
||||
override val appOps = AppOps(
|
||||
op = AppOpsManager.OP_CHANGE_WIFI_STATE,
|
||||
modeForNotAllowed = MODE_IGNORED,
|
||||
)
|
||||
override val permission = Manifest.permission.CHANGE_WIFI_STATE
|
||||
|
||||
/** NETWORK_SETTINGS permission trumps CHANGE_WIFI_CONFIG. */
|
||||
override val broaderPermission = Manifest.permission.NETWORK_SETTINGS
|
||||
override val permissionHasAppOpFlag = false
|
||||
override val modeForNotAllowed = MODE_IGNORED
|
||||
}
|
||||
|
||||
@@ -25,35 +25,32 @@ import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
import androidx.preference.PreferenceGroupAdapter;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.window.embedding.ActivityEmbeddingController;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.core.RoundCornerPreferenceAdapter;
|
||||
import com.android.settings.flags.Flags;
|
||||
import com.android.settings.homepage.SettingsHomepageActivity;
|
||||
|
||||
/**
|
||||
* Adapter for highlighting top level preferences
|
||||
*/
|
||||
public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapter implements
|
||||
public class HighlightableTopLevelPreferenceAdapter extends RoundCornerPreferenceAdapter implements
|
||||
SettingsHomepageActivity.HomepageLoadedListener {
|
||||
|
||||
private static final String TAG = "HighlightableTopLevelAdapter";
|
||||
|
||||
static final long DELAY_HIGHLIGHT_DURATION_MILLIS = 100L;
|
||||
private static final int RES_NORMAL_BACKGROUND =
|
||||
Flags.homepageRevamp()
|
||||
? R.drawable.homepage_selectable_item_background_v2
|
||||
: R.drawable.homepage_selectable_item_background;
|
||||
R.drawable.homepage_selectable_item_background;
|
||||
private static final int RES_HIGHLIGHTED_BACKGROUND =
|
||||
Flags.homepageRevamp()
|
||||
? R.drawable.homepage_highlighted_item_background_v2
|
||||
: R.drawable.homepage_highlighted_item_background;
|
||||
R.drawable.homepage_highlighted_item_background;
|
||||
|
||||
private final int mTitleColorNormal;
|
||||
private final int mTitleColorHighlight;
|
||||
@@ -101,7 +98,7 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt
|
||||
@VisibleForTesting
|
||||
void updateBackground(PreferenceViewHolder holder, int position) {
|
||||
if (!isHighlightNeeded()) {
|
||||
removeHighlightBackground(holder);
|
||||
removeHighlightBackground(holder, position);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,9 +106,9 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt
|
||||
&& mHighlightKey != null
|
||||
&& TextUtils.equals(mHighlightKey, getItem(position).getKey())) {
|
||||
// This position should be highlighted.
|
||||
addHighlightBackground(holder);
|
||||
addHighlightBackground(holder, position);
|
||||
} else {
|
||||
removeHighlightBackground(holder);
|
||||
removeHighlightBackground(holder, position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,31 +223,41 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt
|
||||
// De-highlight the existing preference view holder at an early stage
|
||||
final PreferenceViewHolder holder = mViewHolders.get(position);
|
||||
if (holder != null) {
|
||||
removeHighlightBackground(holder);
|
||||
removeHighlightBackground(holder, position);
|
||||
}
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
}
|
||||
|
||||
private void addHighlightBackground(PreferenceViewHolder holder) {
|
||||
private void addHighlightBackground(PreferenceViewHolder holder, int position) {
|
||||
final View v = holder.itemView;
|
||||
v.setBackgroundResource(RES_HIGHLIGHTED_BACKGROUND);
|
||||
((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorHighlight);
|
||||
((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorHighlight);
|
||||
final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable();
|
||||
if (drawable != null) {
|
||||
drawable.setTint(mIconColorHighlight);
|
||||
if (Flags.homepageRevamp()) {
|
||||
@DrawableRes int bgRes = getRoundCornerDrawableRes(position, true /*isSelected*/);
|
||||
v.setBackgroundResource(bgRes);
|
||||
} else {
|
||||
v.setBackgroundResource(RES_HIGHLIGHTED_BACKGROUND);
|
||||
((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorHighlight);
|
||||
((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorHighlight);
|
||||
final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable();
|
||||
if (drawable != null) {
|
||||
drawable.setTint(mIconColorHighlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeHighlightBackground(PreferenceViewHolder holder) {
|
||||
private void removeHighlightBackground(PreferenceViewHolder holder, int position) {
|
||||
final View v = holder.itemView;
|
||||
v.setBackgroundResource(RES_NORMAL_BACKGROUND);
|
||||
((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorNormal);
|
||||
((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorNormal);
|
||||
final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable();
|
||||
if (drawable != null) {
|
||||
drawable.setTint(mIconColorNormal);
|
||||
if (Flags.homepageRevamp()) {
|
||||
@DrawableRes int bgRes = getRoundCornerDrawableRes(position, false /*isSelected*/);
|
||||
v.setBackgroundResource(bgRes);
|
||||
} else {
|
||||
v.setBackgroundResource(RES_NORMAL_BACKGROUND);
|
||||
((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorNormal);
|
||||
((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorNormal);
|
||||
final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable();
|
||||
if (drawable != null) {
|
||||
drawable.setTint(mIconColorNormal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ import static java.util.Collections.singletonList;
|
||||
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.accessibilityservice.AccessibilityShortcutInfo;
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -49,7 +47,6 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.internal.accessibility.util.AccessibilityUtils;
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.testutils.XmlTestUtils;
|
||||
@@ -333,12 +330,6 @@ public class AccessibilitySettingsTest {
|
||||
AccessibilitySettingsContentObserver.class).isTrue();
|
||||
assertUriObserversContainsClazz(Settings.Secure.ACCESSIBILITY_QS_TARGETS,
|
||||
AccessibilitySettingsContentObserver.class).isFalse();
|
||||
List<BroadcastReceiver> broadcastReceivers =
|
||||
shadowOf((Application) ApplicationProvider.getApplicationContext())
|
||||
.getRegisteredReceivers()
|
||||
.stream().map(wrapper -> wrapper.broadcastReceiver).toList();
|
||||
assertThat(broadcastReceivers.stream().anyMatch(
|
||||
broadcastReceiver -> broadcastReceiver instanceof PackageMonitor)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -352,12 +343,6 @@ public class AccessibilitySettingsTest {
|
||||
AccessibilitySettingsContentObserver.class).isTrue();
|
||||
assertUriObserversContainsClazz(Settings.Secure.ACCESSIBILITY_QS_TARGETS,
|
||||
AccessibilitySettingsContentObserver.class).isTrue();
|
||||
List<BroadcastReceiver> broadcastReceivers =
|
||||
shadowOf((Application) ApplicationProvider.getApplicationContext())
|
||||
.getRegisteredReceivers()
|
||||
.stream().map(wrapper -> wrapper.broadcastReceiver).toList();
|
||||
assertThat(broadcastReceivers.stream().anyMatch(
|
||||
broadcastReceiver -> broadcastReceiver instanceof PackageMonitor)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -372,12 +357,6 @@ public class AccessibilitySettingsTest {
|
||||
AccessibilitySettingsContentObserver.class).isFalse();
|
||||
assertUriObserversContainsClazz(Settings.Secure.ACCESSIBILITY_QS_TARGETS,
|
||||
AccessibilitySettingsContentObserver.class).isFalse();
|
||||
List<BroadcastReceiver> broadcastReceivers =
|
||||
shadowOf((Application) ApplicationProvider.getApplicationContext())
|
||||
.getRegisteredReceivers()
|
||||
.stream().map(wrapper -> wrapper.broadcastReceiver).toList();
|
||||
assertThat(broadcastReceivers.stream().anyMatch(
|
||||
broadcastReceiver -> broadcastReceiver instanceof PackageMonitor)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -105,4 +105,16 @@ public class BluetoothLeAudioModePreferenceControllerTest {
|
||||
assertThat(SystemProperties.get(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[0])
|
||||
.equals(mController.mNewMode)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBluetoothTurnOff_shouldNotChangeLeAudioMode() {
|
||||
SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[1]);
|
||||
when(mBluetoothAdapter.isEnabled())
|
||||
.thenReturn(false);
|
||||
|
||||
mController.updateState(mPreference);
|
||||
final String mode = SystemProperties
|
||||
.get(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[0]);
|
||||
assertThat(mode.equals(mListValues[1])).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,13 +142,11 @@ public class ZenModesBackendTest {
|
||||
.setType(AutomaticZenRule.TYPE_OTHER)
|
||||
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
|
||||
.setZenPolicy(ZenAdapters.notificationPolicyToZenPolicy(dndPolicy))
|
||||
.setTriggerDescription(
|
||||
mContext.getString(R.string.zen_mode_settings_summary))
|
||||
.setManualInvocationAllowed(true)
|
||||
.build(),
|
||||
false),
|
||||
new ZenMode("rule1", ZEN_RULE, false),
|
||||
new ZenMode("rule2", rule2, false))
|
||||
new ZenMode("rule2", rule2, false),
|
||||
new ZenMode("rule1", ZEN_RULE, false))
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@@ -167,8 +165,6 @@ public class ZenModesBackendTest {
|
||||
.setType(AutomaticZenRule.TYPE_OTHER)
|
||||
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
|
||||
.setZenPolicy(ZenAdapters.notificationPolicyToZenPolicy(dndPolicy))
|
||||
.setTriggerDescription(
|
||||
mContext.getString(R.string.zen_mode_settings_summary))
|
||||
.setManualInvocationAllowed(true)
|
||||
.build(), false));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.notification.modes;
|
||||
|
||||
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.app.Flags;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.platform.test.annotations.DisableFlags;
|
||||
import android.platform.test.annotations.EnableFlags;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
import android.service.notification.ZenPolicy;
|
||||
|
||||
import com.android.settingslib.search.SearchIndexableRaw;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ZenModesListPreferenceControllerTest {
|
||||
private static final String TEST_MODE_ID = "test_mode";
|
||||
private static final String TEST_MODE_NAME = "Test Mode";
|
||||
private static final ZenMode TEST_MODE = new ZenMode(
|
||||
TEST_MODE_ID,
|
||||
new AutomaticZenRule.Builder(TEST_MODE_NAME, Uri.parse("test_uri"))
|
||||
.setType(AutomaticZenRule.TYPE_BEDTIME)
|
||||
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
|
||||
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
|
||||
.build(),
|
||||
false);
|
||||
|
||||
private static final ZenMode TEST_MANUAL_MODE = ZenMode.manualDndMode(
|
||||
new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
|
||||
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
|
||||
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
|
||||
.build(),
|
||||
false);
|
||||
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
|
||||
SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@Mock
|
||||
private ZenModesBackend mBackend;
|
||||
|
||||
private ZenModesListPreferenceController mPrefController;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = RuntimeEnvironment.application;
|
||||
|
||||
mPrefController = new ZenModesListPreferenceController(mContext, null, mBackend);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisableFlags(Flags.FLAG_MODES_UI)
|
||||
public void testModesUiOff_notAvailableAndNoSearchData() {
|
||||
// There exist modes
|
||||
when(mBackend.getModes()).thenReturn(List.of(TEST_MANUAL_MODE, TEST_MODE));
|
||||
|
||||
assertThat(mPrefController.isAvailable()).isFalse();
|
||||
List<SearchIndexableRaw> data = new ArrayList<>();
|
||||
mPrefController.updateDynamicRawDataToIndex(data);
|
||||
assertThat(data).isEmpty(); // despite existence of modes
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public void testUpdateDynamicRawDataToIndex_empty() {
|
||||
// Case of no modes.
|
||||
when(mBackend.getModes()).thenReturn(new ArrayList<>());
|
||||
|
||||
List<SearchIndexableRaw> data = new ArrayList<>();
|
||||
mPrefController.updateDynamicRawDataToIndex(data);
|
||||
assertThat(data).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public void testUpdateDynamicRawDataToIndex_oneMode() {
|
||||
// One mode present, confirm it's the correct one
|
||||
when(mBackend.getModes()).thenReturn(List.of(TEST_MODE));
|
||||
|
||||
List<SearchIndexableRaw> data = new ArrayList<>();
|
||||
mPrefController.updateDynamicRawDataToIndex(data);
|
||||
assertThat(data).hasSize(1);
|
||||
|
||||
SearchIndexableRaw item = data.get(0);
|
||||
assertThat(item.key).isEqualTo(TEST_MODE_ID);
|
||||
assertThat(item.title).isEqualTo(TEST_MODE_NAME);
|
||||
|
||||
// Changing mode data so there's a different one mode doesn't keep any previous data
|
||||
// (and setting that state up in the caller)
|
||||
when(mBackend.getModes()).thenReturn(List.of(TEST_MANUAL_MODE));
|
||||
List<SearchIndexableRaw> newData = new ArrayList<>();
|
||||
mPrefController.updateDynamicRawDataToIndex(newData);
|
||||
assertThat(newData).hasSize(1);
|
||||
|
||||
SearchIndexableRaw newItem = newData.get(0);
|
||||
assertThat(newItem.key).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
|
||||
assertThat(newItem.title).isEqualTo("Do Not Disturb"); // set above
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public void testUpdateDynamicRawDataToIndex_multipleModes() {
|
||||
when(mBackend.getModes()).thenReturn(List.of(TEST_MANUAL_MODE, TEST_MODE));
|
||||
|
||||
List<SearchIndexableRaw> data = new ArrayList<>();
|
||||
mPrefController.updateDynamicRawDataToIndex(data);
|
||||
assertThat(data).hasSize(2);
|
||||
|
||||
// Should keep the order presented by getModes()
|
||||
SearchIndexableRaw item0 = data.get(0);
|
||||
assertThat(item0.key).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
|
||||
assertThat(item0.title).isEqualTo("Do Not Disturb"); // set above
|
||||
|
||||
SearchIndexableRaw item1 = data.get(1);
|
||||
assertThat(item1.key).isEqualTo(TEST_MODE_ID);
|
||||
assertThat(item1.title).isEqualTo(TEST_MODE_NAME);
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,7 @@ class Injector(step: FingerprintNavigationStep.UiStep) {
|
||||
object : OrientationInteractor {
|
||||
override val orientation: Flow<Int> = flowOf(Configuration.ORIENTATION_LANDSCAPE)
|
||||
override val rotation: Flow<Int> = flowOf(Surface.ROTATION_0)
|
||||
override val rotationFromDefault: Flow<Int> = rotation
|
||||
|
||||
override fun getRotationFromDefault(rotation: Int): Int = rotation
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@ package com.android.settings.tests.screenshot.biometrics.fingerprint.fragment
|
||||
*/
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
|
||||
import com.android.settings.tests.screenshot.biometrics.fingerprint.Injector
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.RfpsEnrollFindSensorFragment
|
||||
import com.android.settings.tests.screenshot.biometrics.fingerprint.Injector.Companion.BiometricFragmentScreenShotRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -28,10 +26,7 @@ import platform.test.screenshot.FragmentScreenshotTestRule
|
||||
import platform.test.screenshot.ViewScreenshotTestRule.Mode
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class FingerprintEnrollFindSensorScreenshotTest {
|
||||
private val injector: Injector =
|
||||
Injector(FingerprintNavigationStep.Education(Injector.interactor.sensorProp))
|
||||
|
||||
class RfpsEnrollFindSensorScreenshotTest {
|
||||
@Rule @JvmField var rule: FragmentScreenshotTestRule = BiometricFragmentScreenShotRule()
|
||||
|
||||
@Test
|
||||
@@ -39,7 +34,7 @@ class FingerprintEnrollFindSensorScreenshotTest {
|
||||
rule.screenshotTest(
|
||||
"fp_enroll_find_sensor",
|
||||
Mode.MatchSize,
|
||||
FingerprintEnrollFindSensorV2Fragment(injector.fingerprintSensor.sensorType, injector.factory),
|
||||
RfpsEnrollFindSensorFragment(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -66,10 +66,11 @@ class SubscriptionRepositoryTest {
|
||||
@Test
|
||||
fun isSubscriptionEnabledFlow_enabled() = runBlocking {
|
||||
mockSubscriptionManager.stub {
|
||||
on { isSubscriptionEnabled(SUB_ID_1) } doReturn true
|
||||
on { isSubscriptionEnabled(SUB_ID_IN_SLOT_0) } doReturn true
|
||||
}
|
||||
|
||||
val isEnabled = repository.isSubscriptionEnabledFlow(SUB_ID_1).firstWithTimeoutOrNull()
|
||||
val isEnabled =
|
||||
repository.isSubscriptionEnabledFlow(SUB_ID_IN_SLOT_0).firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(isEnabled).isTrue()
|
||||
}
|
||||
@@ -94,21 +95,24 @@ class SubscriptionRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getSelectableSubscriptionInfoList_sortedBySubId() {
|
||||
fun getSelectableSubscriptionInfoList_sortedBySimSlotIndex() {
|
||||
mockSubscriptionManager.stub {
|
||||
on { getAvailableSubscriptionInfoList() } doReturn listOf(
|
||||
SubscriptionInfo.Builder().apply {
|
||||
setId(SUB_ID_2)
|
||||
setSimSlotIndex(SIM_SLOT_INDEX_0)
|
||||
setId(SUB_ID_IN_SLOT_0)
|
||||
}.build(),
|
||||
SubscriptionInfo.Builder().apply {
|
||||
setId(SUB_ID_1)
|
||||
setSimSlotIndex(SIM_SLOT_INDEX_1)
|
||||
setId(SUB_ID_IN_SLOT_1)
|
||||
}.build(),
|
||||
)
|
||||
}
|
||||
|
||||
val subInfos = context.getSelectableSubscriptionInfoList()
|
||||
|
||||
assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_1, SUB_ID_2).inOrder()
|
||||
assertThat(subInfos.map { it.simSlotIndex })
|
||||
.containsExactly(SIM_SLOT_INDEX_0, SIM_SLOT_INDEX_1).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -116,20 +120,21 @@ class SubscriptionRepositoryTest {
|
||||
mockSubscriptionManager.stub {
|
||||
on { getAvailableSubscriptionInfoList() } doReturn listOf(
|
||||
SubscriptionInfo.Builder().apply {
|
||||
setId(SUB_ID_1)
|
||||
setSimSlotIndex(SubscriptionManager.INVALID_SIM_SLOT_INDEX)
|
||||
setId(SUB_ID_3_NOT_IN_SLOT)
|
||||
setGroupUuid(GROUP_UUID)
|
||||
}.build(),
|
||||
SubscriptionInfo.Builder().apply {
|
||||
setId(SUB_ID_2)
|
||||
setSimSlotIndex(SIM_SLOT_INDEX_0)
|
||||
setId(SUB_ID_IN_SLOT_0)
|
||||
setGroupUuid(GROUP_UUID)
|
||||
setSimSlotIndex(SIM_SLOT_INDEX)
|
||||
}.build(),
|
||||
)
|
||||
}
|
||||
|
||||
val subInfos = context.getSelectableSubscriptionInfoList()
|
||||
|
||||
assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_2)
|
||||
assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_IN_SLOT_0)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -137,11 +142,11 @@ class SubscriptionRepositoryTest {
|
||||
mockSubscriptionManager.stub {
|
||||
on { getAvailableSubscriptionInfoList() } doReturn listOf(
|
||||
SubscriptionInfo.Builder().apply {
|
||||
setId(SUB_ID_2)
|
||||
setId(SUB_ID_4_NOT_IN_SLOT)
|
||||
setGroupUuid(GROUP_UUID)
|
||||
}.build(),
|
||||
SubscriptionInfo.Builder().apply {
|
||||
setId(SUB_ID_1)
|
||||
setId(SUB_ID_3_NOT_IN_SLOT)
|
||||
setGroupUuid(GROUP_UUID)
|
||||
}.build(),
|
||||
)
|
||||
@@ -149,16 +154,16 @@ class SubscriptionRepositoryTest {
|
||||
|
||||
val subInfos = context.getSelectableSubscriptionInfoList()
|
||||
|
||||
assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_1)
|
||||
assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_3_NOT_IN_SLOT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun phoneNumberFlow() = runBlocking {
|
||||
mockSubscriptionManager.stub {
|
||||
on { getPhoneNumber(SUB_ID_1) } doReturn NUMBER_1
|
||||
on { getPhoneNumber(SUB_ID_IN_SLOT_1) } doReturn NUMBER_1
|
||||
}
|
||||
val subInfo = SubscriptionInfo.Builder().apply {
|
||||
setId(SUB_ID_1)
|
||||
setId(SUB_ID_IN_SLOT_1)
|
||||
setMcc(MCC)
|
||||
}.build()
|
||||
|
||||
@@ -168,10 +173,13 @@ class SubscriptionRepositoryTest {
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val SUB_ID_1 = 1
|
||||
const val SUB_ID_2 = 2
|
||||
const val SIM_SLOT_INDEX_0 = 0
|
||||
const val SUB_ID_IN_SLOT_0 = 2
|
||||
const val SIM_SLOT_INDEX_1 = 1
|
||||
const val SUB_ID_IN_SLOT_1 = 1
|
||||
const val SUB_ID_3_NOT_IN_SLOT = 3
|
||||
const val SUB_ID_4_NOT_IN_SLOT = 4
|
||||
val GROUP_UUID = UUID.randomUUID().toString()
|
||||
const val SIM_SLOT_INDEX = 1
|
||||
const val NUMBER_1 = "000000001"
|
||||
const val MCC = "310"
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ class WifiControlAppListModelTest {
|
||||
private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
|
||||
var setAllowedCalledWith: Boolean? = null
|
||||
|
||||
override val mode = flowOf(fakeMode)
|
||||
override val modeFlow = flowOf(fakeMode)
|
||||
|
||||
override fun setAllowed(allowed: Boolean) {
|
||||
setAllowedCalledWith = allowed
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -37,8 +38,12 @@ class AllFilesAccessTest {
|
||||
assertThat(listModel.pageTitleResId).isEqualTo(R.string.manage_external_storage_title)
|
||||
assertThat(listModel.switchTitleResId).isEqualTo(R.string.permit_manage_external_storage)
|
||||
assertThat(listModel.footerResId).isEqualTo(R.string.allow_manage_external_storage_description)
|
||||
assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE)
|
||||
assertThat(listModel.appOps).isEqualTo(
|
||||
AppOps(
|
||||
op = AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE,
|
||||
setModeByUid = true,
|
||||
)
|
||||
)
|
||||
assertThat(listModel.permission).isEqualTo(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
|
||||
assertThat(listModel.setModeByUid).isTrue()
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@@ -37,8 +38,12 @@ class LongBackgroundTasksAppsTest {
|
||||
assertThat(listModel.pageTitleResId).isEqualTo(R.string.long_background_tasks_title)
|
||||
assertThat(listModel.switchTitleResId).isEqualTo(R.string.long_background_tasks_switch_title)
|
||||
assertThat(listModel.footerResId).isEqualTo(R.string.long_background_tasks_footer_title)
|
||||
assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_RUN_USER_INITIATED_JOBS)
|
||||
assertThat(listModel.appOps).isEqualTo(
|
||||
AppOps(
|
||||
op = AppOpsManager.OP_RUN_USER_INITIATED_JOBS,
|
||||
setModeByUid = true,
|
||||
)
|
||||
)
|
||||
assertThat(listModel.permission).isEqualTo(Manifest.permission.RUN_USER_INITIATED_JOBS)
|
||||
assertThat(listModel.setModeByUid).isTrue()
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -37,8 +38,12 @@ class MediaManagementAppsTest {
|
||||
assertThat(listModel.pageTitleResId).isEqualTo(R.string.media_management_apps_title)
|
||||
assertThat(listModel.switchTitleResId).isEqualTo(R.string.media_management_apps_toggle_label)
|
||||
assertThat(listModel.footerResId).isEqualTo(R.string.media_management_apps_description)
|
||||
assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_MANAGE_MEDIA)
|
||||
assertThat(listModel.appOps).isEqualTo(
|
||||
AppOps(
|
||||
op = AppOpsManager.OP_MANAGE_MEDIA,
|
||||
setModeByUid = true,
|
||||
)
|
||||
)
|
||||
assertThat(listModel.permission).isEqualTo(Manifest.permission.MANAGE_MEDIA)
|
||||
assertThat(listModel.setModeByUid).isTrue()
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.media.flags.Flags
|
||||
import com.android.settings.R
|
||||
import com.android.settings.testutils.FakeFeatureFactory
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.model.app.IAppOpsController
|
||||
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -74,9 +75,13 @@ class MediaRoutingControlTest {
|
||||
assertThat(listModel.pageTitleResId).isEqualTo(R.string.media_routing_control_title)
|
||||
assertThat(listModel.switchTitleResId).isEqualTo(R.string.allow_media_routing_control)
|
||||
assertThat(listModel.footerResId).isEqualTo(R.string.allow_media_routing_description)
|
||||
assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_MEDIA_ROUTING_CONTROL)
|
||||
assertThat(listModel.appOps).isEqualTo(
|
||||
AppOps(
|
||||
op = AppOpsManager.OP_MEDIA_ROUTING_CONTROL,
|
||||
setModeByUid = true,
|
||||
)
|
||||
)
|
||||
assertThat(listModel.permission).isEqualTo(Manifest.permission.MEDIA_ROUTING_CONTROL)
|
||||
assertThat(listModel.setModeByUid).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -223,13 +228,13 @@ class MediaRoutingControlTest {
|
||||
|
||||
private class FakeAppOpsController(fakeMode: Int) : IAppOpsController {
|
||||
|
||||
override val mode = MutableStateFlow(fakeMode)
|
||||
override val modeFlow = MutableStateFlow(fakeMode)
|
||||
|
||||
override fun setAllowed(allowed: Boolean) {
|
||||
mode.value = if (allowed) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED
|
||||
modeFlow.value = if (allowed) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED
|
||||
}
|
||||
|
||||
override fun getMode(): Int = mode.value
|
||||
override fun getMode(): Int = modeFlow.value
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.os.DeadSystemRuntimeException
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOpsController
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.flow.first
|
||||
@@ -179,7 +180,7 @@ class PictureInPictureTest {
|
||||
appOpsController = AppOpsController(
|
||||
context = context,
|
||||
app = PICTURE_IN_PICTURE_APP,
|
||||
op = AppOpsManager.OP_PICTURE_IN_PICTURE,
|
||||
appOps = AppOps(AppOpsManager.OP_PICTURE_IN_PICTURE),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.app.AppOpsManager
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOps
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -35,8 +36,12 @@ class TurnScreenOnAppsTest {
|
||||
assertThat(listModel.pageTitleResId).isEqualTo(com.android.settingslib.R.string.turn_screen_on_title)
|
||||
assertThat(listModel.switchTitleResId).isEqualTo(com.android.settingslib.R.string.allow_turn_screen_on)
|
||||
assertThat(listModel.footerResId).isEqualTo(com.android.settingslib.R.string.allow_turn_screen_on_description)
|
||||
assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_TURN_SCREEN_ON)
|
||||
assertThat(listModel.appOps).isEqualTo(
|
||||
AppOps(
|
||||
op = AppOpsManager.OP_TURN_SCREEN_ON,
|
||||
setModeByUid = true,
|
||||
)
|
||||
)
|
||||
assertThat(listModel.permission).isEqualTo(Manifest.permission.TURN_SCREEN_ON)
|
||||
assertThat(listModel.setModeByUid).isTrue()
|
||||
}
|
||||
}
|
||||
@@ -21,18 +21,16 @@ android_test {
|
||||
static_libs: [
|
||||
"aconfig_settings_flags_lib",
|
||||
"androidx.arch.core_core-testing",
|
||||
"androidx.test.core",
|
||||
"androidx.lifecycle_lifecycle-runtime-testing",
|
||||
"androidx.test.espresso.core",
|
||||
"androidx.test.rules",
|
||||
"androidx.test.ext.junit",
|
||||
"androidx.preference_preference",
|
||||
"androidx.test.rules",
|
||||
"flag-junit",
|
||||
"mockito-target-minus-junit4",
|
||||
"platform-test-annotations",
|
||||
"platform-test-rules",
|
||||
"truth",
|
||||
"kotlinx_coroutines_test",
|
||||
"flag-junit",
|
||||
"Settings-testutils2",
|
||||
"MediaDrmSettingsFlagsLib",
|
||||
// Don't add SettingsLib libraries here - you can use them directly as they are in the
|
||||
|
||||
@@ -33,8 +33,8 @@ import android.os.Handler
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.Default
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
@@ -82,10 +82,6 @@ class FingerprintManagerInteractorTest {
|
||||
@Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
|
||||
|
||||
private var testScope = TestScope(backgroundDispatcher)
|
||||
private var pressToAuthInteractor =
|
||||
object : PressToAuthInteractor {
|
||||
override val isEnabled = flowOf(false)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
@@ -113,9 +109,12 @@ class FingerprintManagerInteractorTest {
|
||||
fingerprintManager,
|
||||
fingerprintSensorRepository,
|
||||
gateKeeperPasswordProvider,
|
||||
pressToAuthInteractor,
|
||||
Default,
|
||||
Intent(),
|
||||
FingerprintEnrollInteractorImpl(
|
||||
context,
|
||||
FingerprintEnrollOptions.Builder().build(),
|
||||
fingerprintManager,
|
||||
Default,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,8 @@ class FingerprintEnrollFindSensorViewModelV2Test {
|
||||
orientationInteractor =
|
||||
object : OrientationInteractor {
|
||||
override val orientation: Flow<Int> = flowOf(Configuration.ORIENTATION_LANDSCAPE)
|
||||
override val rotation: Flow<Int> = flowOf(Surface.ROTATION_0)
|
||||
override val rotation: Flow<Int> = flowOf(Surface.ROTATION_0)
|
||||
override val rotationFromDefault: Flow<Int> = flowOf(Surface.ROTATION_0)
|
||||
override fun getRotationFromDefault(rotation: Int): Int = rotation
|
||||
}
|
||||
underTest =
|
||||
|
||||
@@ -48,6 +48,7 @@ import android.telephony.TelephonyManager;
|
||||
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.testing.TestLifecycleOwner;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
@@ -60,7 +61,6 @@ import com.android.settings.network.telephony.TelephonyConstants.TelephonyManage
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
@@ -113,6 +113,7 @@ public class EnabledNetworkModePreferenceControllerTest {
|
||||
doReturn(mPersistableBundle).when(mCarrierConfigCache).getConfig();
|
||||
doReturn(mPersistableBundle).when(mCarrierConfigCache).getConfigForSubId(SUB_ID);
|
||||
mPersistableBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
|
||||
mPersistableBundle.putBoolean(CarrierConfigManager.KEY_PREFER_3G_VISIBILITY_BOOL, true);
|
||||
mPreference = new ListPreference(mContext);
|
||||
mController = new EnabledNetworkModePreferenceController(mContext, KEY);
|
||||
mockAllowedNetworkTypes(ALLOWED_ALL_NETWORK_TYPE);
|
||||
@@ -347,7 +348,6 @@ public class EnabledNetworkModePreferenceControllerTest {
|
||||
|
||||
@UiThreadTest
|
||||
@Test
|
||||
@Ignore("b/337418033")
|
||||
public void updateState_updateByNetworkMode() {
|
||||
mockEnabledNetworkMode(TelephonyManagerConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA);
|
||||
|
||||
@@ -379,7 +379,6 @@ public class EnabledNetworkModePreferenceControllerTest {
|
||||
|
||||
@UiThreadTest
|
||||
@Test
|
||||
@Ignore("b/337418033")
|
||||
public void onPreferenceChange_updateSuccess() {
|
||||
mockEnabledNetworkMode(TelephonyManagerConstants.NETWORK_MODE_LTE_GSM_WCDMA);
|
||||
doReturn(true).when(mTelephonyManager).setPreferredNetworkTypeBitmask(
|
||||
@@ -387,6 +386,7 @@ public class EnabledNetworkModePreferenceControllerTest {
|
||||
TelephonyManagerConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA));
|
||||
|
||||
mController.updateState(mPreference);
|
||||
mController.onViewCreated(new TestLifecycleOwner());
|
||||
mController.onPreferenceChange(mPreference,
|
||||
String.valueOf(TelephonyManagerConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA));
|
||||
|
||||
@@ -396,13 +396,13 @@ public class EnabledNetworkModePreferenceControllerTest {
|
||||
|
||||
@UiThreadTest
|
||||
@Test
|
||||
@Ignore("b/337418033")
|
||||
public void onPreferenceChange_updateFail() {
|
||||
mockEnabledNetworkMode(TelephonyManagerConstants.NETWORK_MODE_LTE_GSM_WCDMA);
|
||||
doReturn(false).when(mTelephonyManager).setPreferredNetworkTypeBitmask(
|
||||
getRafFromNetworkType(TelephonyManagerConstants.NETWORK_MODE_LTE_GSM_WCDMA));
|
||||
|
||||
mController.updateState(mPreference);
|
||||
mController.onViewCreated(new TestLifecycleOwner());
|
||||
mController.onPreferenceChange(mPreference,
|
||||
String.valueOf(TelephonyManagerConstants.NETWORK_MODE_LTE_GSM_WCDMA));
|
||||
|
||||
@@ -412,7 +412,6 @@ public class EnabledNetworkModePreferenceControllerTest {
|
||||
|
||||
@UiThreadTest
|
||||
@Test
|
||||
@Ignore("b/337418033")
|
||||
public void preferredNetworkModeNotification_preferenceUpdates() {
|
||||
|
||||
final PreferenceManager preferenceManager = new PreferenceManager(mContext);
|
||||
|
||||
@@ -38,7 +38,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
@@ -66,6 +65,7 @@ public class CdmaSystemSelectPreferenceControllerTest {
|
||||
mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
|
||||
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID);
|
||||
when(mTelephonyManager.getPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_CDMA);
|
||||
|
||||
mPreference = new ListPreference(mContext);
|
||||
mController = new CdmaSystemSelectPreferenceController(mContext, "mobile_data");
|
||||
@@ -100,7 +100,6 @@ public class CdmaSystemSelectPreferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("b/337417544")
|
||||
public void updateState_stateHome_displayHome() {
|
||||
doReturn(TelephonyManager.CDMA_ROAMING_MODE_HOME).when(
|
||||
mTelephonyManager).getCdmaRoamingMode();
|
||||
@@ -112,7 +111,6 @@ public class CdmaSystemSelectPreferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("b/337417897")
|
||||
public void updateState_LteGSMWcdma_disabled() {
|
||||
doReturn(TelephonyManager.CDMA_ROAMING_MODE_HOME).when(
|
||||
mTelephonyManager).getCdmaRoamingMode();
|
||||
@@ -126,7 +124,6 @@ public class CdmaSystemSelectPreferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("b/337417917")
|
||||
public void updateState_stateOther_resetToDefault() {
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.CDMA_ROAMING_MODE,
|
||||
|
||||
Reference in New Issue
Block a user