diff --git a/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml b/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml
index 86768d6b43f..669c282a2aa 100644
--- a/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml
+++ b/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml
@@ -14,87 +14,65 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-
+
-
-
-
+ >
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/res/layout/data_usage_summary_preference.xml b/res/layout/data_usage_summary_preference.xml
index 4cbd9581865..e678b36779f 100644
--- a/res/layout/data_usage_summary_preference.xml
+++ b/res/layout/data_usage_summary_preference.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:minHeight="172dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
@@ -37,7 +38,6 @@
android:id="@+id/usage_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingTop="12dp"
android:orientation="horizontal">
@@ -27,65 +26,57 @@
+ android:layout_weight="5"
+ >
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
diff --git a/res/layout/fingerprint_v2_udfps_enroll_view.xml b/res/layout/fingerprint_v2_udfps_enroll_view.xml
index 20df6e138ea..9002eca8fd1 100644
--- a/res/layout/fingerprint_v2_udfps_enroll_view.xml
+++ b/res/layout/fingerprint_v2_udfps_enroll_view.xml
@@ -21,6 +21,8 @@
android:id="@+id/udfps_animation_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
android:orientation="vertical">
Fingerprint Unlock for private space
Face Unlock for private space
+
+ Face & Fingerprint Unlock for private space
+
+ Set up Fingerprint Unlock for private space
+
+ Use your fingerprint to unlock your private space or verify it\u2019s you, like when you sign in to apps or approve a purchase
+
+ Your private space can be unlocked when you don\u2019t intend to, like if someone holds up your phone to your finger.
+
+ Use your fingerprint to unlock your private space or to approve purchases
+
+ Set up Face Unlock for private space
+
+ Use your face to unlock your private space or verify it\u2019s you, like when you sign in to apps or approve a purchase
+
+ Looking at the phone can unlock private space even when you don\u2019t intend to. Your private space can also be unlocked by someone who looks a lot like you, like an identical sibling, or if someone holds the device up to your face.
+
+ Using your face to unlock your private space may be less secure than a strong pattern, PIN, or password
+
+ To unlock private space, your eyes must be open. For best results, take off sunglasses.
+
+ Use your face to unlock your private space.\n\nKeep in mind:\nYou can only have one face set up at a time. To add another face, delete the current one.\n\nLooking at the phone can unlock it when you don\u2019t intend to.\n\nYour private space can be unlocked by someone else if your device is held up to your face.\n\nYour private space can be unlocked by someone who looks a lot like you, like an identical sibling.
Ways to unlock
@@ -11406,7 +11428,7 @@
This device
- Audio sharing
+ Sharing audio
Unavailable during calls
@@ -11896,6 +11918,10 @@
Turning on %1$s …
Mobile network
+
+ IMEI (primary)
+
+ MEID (primary)
Phone number
diff --git a/res/xml/private_space_biometric_settings.xml b/res/xml/private_space_biometric_settings.xml
index 6135b5ff7dd..38ec09abcae 100644
--- a/res/xml/private_space_biometric_settings.xml
+++ b/res/xml/private_space_biometric_settings.xml
@@ -17,7 +17,7 @@
+ Log.d(TAG, "onUdfpsPointerDown failed to send, due to $error")
+ }
+ }
+
+ override fun onUdfpsPointerUp(sensorId: Int) {
+ trySend(FingerEnrollState.PointerUp(sensorId)).onFailure { error ->
+ Log.d(TAG, "onUdfpsPointerUp failed to send, due to $error")
+ }
+ }
+
+ override fun onUdfpsOverlayShown() {
+ trySend(FingerEnrollState.OverlayShown).onFailure { error ->
+ Log.d(TAG, "OverlayShown failed to send, due to $error")
+ }
+ }
+
+ override fun onAcquired(isAcquiredGood: Boolean) {
+ trySend(FingerEnrollState.Acquired(isAcquiredGood)).onFailure { error ->
+ Log.d(TAG, "Acquired failed to send, due to $error")
+ }
+ }
}
val cancellationSignal = CancellationSignal()
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt
index f9276e63ddf..3ecf3121158 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt
@@ -17,17 +17,12 @@
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
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 */
@@ -35,7 +30,9 @@ interface OrientationInteractor {
/** A flow that contains the information about the orientation changing */
val orientation: Flow
/**
- * A flow that contains the rotation info
+ * This indicates the surface rotation that hte view is currently in. For instance its possible to
+ * rotate a view to 90 degrees but for it to still be portrait mode. In this case, this flow
+ * should emit that we are in rotation 0 (SurfaceView.Rotation_0)
*/
val rotation: Flow
/**
@@ -50,8 +47,7 @@ interface OrientationInteractor {
fun getRotationFromDefault(rotation: Int): Int
}
-class OrientationInteractorImpl(private val context: Context, activityScope: CoroutineScope) :
- OrientationInteractor {
+class OrientationInteractorImpl(private val context: Context) : OrientationInteractor {
override val orientation: Flow = callbackFlow {
val orientationEventListener =
@@ -62,9 +58,12 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor
}
orientationEventListener.enable()
awaitClose { orientationEventListener.disable() }
- }.shareIn(activityScope, SharingStarted.Eagerly, replay = 1)
+ }
- override val rotation: Flow = orientation.transform { emit(context.display!!.rotation) }
+ override val rotation: Flow =
+ orientation.transform {
+ emit(context.display!!.rotation)
+ }
override val rotationFromDefault: Flow = rotation.map { getRotationFromDefault(it) }
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/UdfpsEnrollInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/UdfpsEnrollInteractor.kt
index ec09ffd0011..107d1f65459 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/UdfpsEnrollInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/UdfpsEnrollInteractor.kt
@@ -17,6 +17,7 @@
package com.android.settings.biometrics.fingerprint2.domain.interactor
import android.graphics.PointF
+import android.util.Log
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
@@ -69,6 +70,7 @@ class UdfpsEnrollInteractorImpl(
override fun onEnrollmentStep(stepsRemaining: Int, totalStep: Int) {
val index = (totalStep - stepsRemaining) % guidedEnrollmentPoints.size
+ Log.e("JRM", "guided enroll step $index")
_guidedEnrollment.update { guidedEnrollmentPoints[index] }
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt
index 24a9a86f339..e087304d4f0 100644
--- a/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt
@@ -47,10 +47,10 @@ sealed class FingerEnrollState {
data class Acquired(val acquiredGood: Boolean) : FingerEnrollState()
/** Indicates a pointer down event has occurred */
- data object PointerDown : FingerEnrollState()
+ data class PointerDown(val fingerId: Int) : FingerEnrollState()
/** Indicates a pointer up event has occurred */
- data object PointerUp : FingerEnrollState()
+ data class PointerUp(val fingerId: Int) : FingerEnrollState()
/** Indicates the overlay has shown */
data object OverlayShown : FingerEnrollState()
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
index 6d353a42a62..d25fcd06e36 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
@@ -72,6 +72,7 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enroll
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
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsLastStepViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
@@ -126,6 +127,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
private lateinit var fingerprintEnrollConfirmationViewModel:
FingerprintEnrollConfirmationViewModel
+ private lateinit var udfpsLastStepViewModel: UdfpsLastStepViewModel
private lateinit var udfpsViewModel: UdfpsViewModel
private lateinit var enrollStageInteractor: EnrollStageInteractor
private val coroutineDispatcher = Dispatchers.Default
@@ -320,7 +322,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
foldStateInteractor = FoldStateInteractorImpl(context)
foldStateInteractor.onConfigurationChange(resources.configuration)
- orientationInteractor = OrientationInteractorImpl(context, lifecycleScope)
+ orientationInteractor = OrientationInteractorImpl(context)
vibrationInteractor =
VibrationInteractorImpl(context.getSystemService(Vibrator::class.java)!!, context)
@@ -373,11 +375,15 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
fingerprintEnrollEnrollingViewModel,
navigationViewModel,
orientationInteractor,
+ fingerprintManagerInteractor,
),
)[RFPSViewModel::class.java]
enrollStageInteractor = EnrollStageInteractorImpl()
+ udfpsLastStepViewModel =
+ UdfpsLastStepViewModel(fingerprintEnrollEnrollingViewModel, vibrationInteractor)
+
udfpsViewModel =
ViewModelProvider(
this,
@@ -393,6 +399,9 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
backgroundViewModel,
fingerprintSensorRepo,
udfpsEnrollInteractor,
+ fingerprintManagerInteractor,
+ udfpsLastStepViewModel,
+ accessibilityInteractor,
),
)[UdfpsViewModel::class.java]
@@ -456,7 +465,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
step.exitTransition.toAnimation(),
)
.setReorderingAllowed(true)
- .add(R.id.fragment_container_view, theClass::class.java, null)
+ .replace(R.id.fragment_container_view, theClass::class.java, null)
.commit()
navigationViewModel.update(
FingerprintAction.TRANSITION_FINISHED,
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
index 06450814103..8abcf1a3a93 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
@@ -20,15 +20,18 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
+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.ui.enrollment.viewmodel.FingerprintAction
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Enrollment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
@@ -41,6 +44,7 @@ class RFPSViewModel(
private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
private val navigationViewModel: FingerprintNavigationViewModel,
orientationInteractor: OrientationInteractor,
+ private val fingerprintManager: FingerprintManagerInteractor,
) : ViewModel() {
private val _textViewIsVisible = MutableStateFlow(false)
@@ -52,7 +56,16 @@ class RFPSViewModel(
/** Indicates if the icon should be animating or not */
val shouldAnimateIcon = _shouldAnimateIcon
- private var enrollFlow: Flow = fingerprintEnrollViewModel.enrollFlow
+ private var enrollFlow: Flow =
+ fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
+ fingerprintEnrollViewModel.enrollFlow
+ ) { props, enroll ->
+ if (props.sensorType == FingerprintSensorType.REAR) {
+ enroll
+ } else {
+ null
+ }
+ }
/**
* Enroll progress message with a replay of size 1 allowing for new subscribers to get the most
@@ -149,6 +162,7 @@ class RFPSViewModel(
private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
private val navigationViewModel: FingerprintNavigationViewModel,
private val orientationInteractor: OrientationInteractor,
+ private val fingerprintManager: FingerprintManagerInteractor,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@@ -157,6 +171,7 @@ class RFPSViewModel(
fingerprintEnrollEnrollingViewModel,
navigationViewModel,
orientationInteractor,
+ fingerprintManager,
)
as T
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt
index a2e52329dd8..4588a07db63 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt
@@ -21,8 +21,10 @@ import android.util.Log
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_HOVER_MOVE
import android.view.View
+import android.view.ViewGroup
import android.view.WindowManager
-import android.widget.TextView
+import android.widget.Button
+import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
@@ -40,7 +42,6 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enroll
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.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
import com.google.android.setupdesign.GlifLayout
import kotlinx.coroutines.launch
@@ -71,10 +72,8 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
val fragment = this
lottie = view.findViewById(R.id.illustration_lottie)!!
udfpsEnrollView = view.findViewById(R.id.udfps_animation_view)!!
- val titleTextView = view.findViewById(R.id.title)!!
- val descriptionTextView = view.findViewById(R.id.description)!!
+ val glifLayout: GlifLayout = view.findViewById(R.id.glif_layout)!!
- val glifLayout = view.findViewById(R.id.dummy_glif_layout)!!
val backgroundColor = glifLayout.backgroundBaseColor
val window = requireActivity().window
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
@@ -83,6 +82,10 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
window.statusBarColor = color
view.setBackgroundColor(color)
+ view.findViewById(R.id.skip)?.apply {
+ setOnClickListener { viewModel.negativeButtonClicked() }
+ }
+
udfpsEnrollView.setFinishAnimationCompleted { viewModel.finishedSuccessfully() }
viewLifecycleOwner.lifecycleScope.launch {
@@ -92,32 +95,41 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
udfpsEnrollView.setSensorRect(sensor.sensorBounds, sensor.sensorType)
}
}
- viewLifecycleOwner.lifecycleScope.launch {
- viewModel.headerText.collect { titleTextView.setText(it.toResource()) }
- }
- viewLifecycleOwner.lifecycleScope.launch {
- viewModel.descriptionText.collect {
- if (it != null) {
- it.toResource()?.let { text -> descriptionTextView.setText(text) }
- } else {
- descriptionTextView.text = ""
+ launch { viewModel.overlayShown.collect { udfpsEnrollView.overlayShown() } }
+ launch { viewModel.headerText.collect { glifLayout.setHeaderText(it.toResource()) } }
+ launch {
+ viewModel.userInteractedWithSensor.collect {
+ if (!it) {
+ glifLayout.setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title)
+ glifLayout.setDescriptionText(R.string.security_settings_udfps_enroll_start_message)
}
}
}
- viewLifecycleOwner.lifecycleScope.launch {
+
+ launch {
+ viewModel.descriptionText.collect {
+ if (it != null) {
+ it.toResource()?.let { text -> glifLayout.setDescriptionText(text) }
+ } else {
+ glifLayout.descriptionText = ""
+ }
+ }
+ }
+ launch {
viewModel.shouldShowLottie.collect {
lottie.visibility = if (it) View.VISIBLE else View.GONE
}
}
- viewLifecycleOwner.lifecycleScope.launch {
+ launch {
viewModel.lottie.collect { lottieModel ->
if (lottie.visibility == View.GONE) {
return@collect
}
val resource = lottieModel.toResource()
if (resource != null) {
+ glifLayout.descriptionTextView.visibility = View.GONE
LottieCompositionFactory.fromRawRes(requireContext(), resource).addListener { comp ->
comp?.let { composition ->
lottie.setComposition(composition)
@@ -126,27 +138,24 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
}
}
} else {
+ glifLayout.descriptionTextView.visibility = View.VISIBLE
lottie.visibility = View.INVISIBLE
}
}
}
-
- viewLifecycleOwner.lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.DESTROYED) { viewModel.stopEnrollment() }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
+ launch {
viewModel.accessibilityEnabled.collect { enabled ->
udfpsEnrollView.setAccessibilityEnabled(enabled)
}
}
- viewLifecycleOwner.lifecycleScope.launch {
+ launch {
viewModel.enrollState.collect {
Log.d(TAG, "EnrollEvent $it")
if (it is FingerEnrollState.EnrollError) {
try {
FingerprintErrorDialog.showInstance(it, fragment)
+ viewModel.errorDialogShown(it)
} catch (exception: Exception) {
Log.e(TAG, "Exception occurred $exception")
}
@@ -156,19 +165,40 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
}
}
- viewLifecycleOwner.lifecycleScope.launch {
- viewModel.progressSaved.collect { udfpsEnrollView.onEnrollProgressSaved(it) }
+ launch { viewModel.progressSaved.collect { udfpsEnrollView.onEnrollProgressSaved(it) } }
+
+ launch { viewModel.guidedEnrollment.collect { udfpsEnrollView.updateGuidedEnrollment(it) } }
+ launch {
+ viewModel.guidedEnrollmentSaved.collect { udfpsEnrollView.onGuidedPointSaved(it) }
}
- viewLifecycleOwner.lifecycleScope.launch {
- viewModel.guidedEnrollment.collect {
- glifLayout.post { udfpsEnrollView.updateGuidedEnrollment(it) }
+ launch { viewModel.shouldDrawIcon.collect { udfpsEnrollView.shouldDrawIcon(it) } }
+
+ // Hack to get the final step of enroll progress to animate.
+ launch {
+ viewModel.udfpsLastStepViewModel.shouldAnimateCompletion.collect {
+ Log.d(TAG, "Sending fake last enroll event $it")
+ udfpsEnrollView.onUdfpsEvent(it)
}
}
- viewLifecycleOwner.lifecycleScope.launch {
- viewModel.guidedEnrollmentSaved.collect {
- glifLayout.post { udfpsEnrollView.onGuidedPointSaved(it) }
- }
+ }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.DESTROYED) { viewModel.stopEnrollment() }
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ viewModel.isLandscape.collect {
+ if (it) {
+ changeViewToLandscape()
+ }
+ }
+ }
+ viewLifecycleOwner.lifecycleScope.launch {
+ viewModel.isReverseLandscape.collect {
+ if (it) {
+ changeViewToReverseLandscape()
}
}
}
@@ -183,12 +213,70 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
viewModel.readyForEnrollment()
}
+ private fun changeViewToReverseLandscape() {
+ Log.d(TAG, "changeViewToReverseLandscape")
+ val glifContainer = requireView().findViewById(R.id.glif_layout)!!
+ val headerView =
+ glifContainer.findViewById(
+ com.google.android.setupdesign.R.id.sud_landscape_header_area
+ )
+ // The landscape_header_area nad landscape_content_area should have the same parent
+ val parent = headerView!!.parent as ViewGroup
+ val sudContentFrame =
+ glifContainer.findViewById(
+ com.google.android.setupdesign.R.id.sud_landscape_content_area
+ )!!
+ val udfpsContainer = requireView().findViewById(R.id.layout_container)!!
+
+ parent.removeView(headerView)
+ parent.removeView(sudContentFrame)
+ parent.addView(sudContentFrame)
+ parent.addView(headerView)
+
+ unclipSubviewsFromParent(udfpsContainer)
+ udfpsEnrollView.requestLayout()
+ }
+
+ private fun changeViewToLandscape() {
+ Log.d(TAG, "changeViewToLandscape")
+
+ val glifContainer = requireView().findViewById(R.id.glif_layout)!!
+ val headerView =
+ glifContainer.findViewById(
+ com.google.android.setupdesign.R.id.sud_landscape_header_area
+ )
+ // The landscape_header_area nad landscape_content_area should have the same parent
+ val parent = headerView!!.parent as ViewGroup
+ val sudContentFrame =
+ glifContainer.findViewById(
+ com.google.android.setupdesign.R.id.sud_landscape_content_area
+ )!!
+
+ parent.removeView(headerView)
+ parent.removeView(sudContentFrame)
+ parent.addView(headerView)
+ parent.addView(sudContentFrame)
+
+ val udfpsContainer = requireView().findViewById(R.id.layout_container)!!
+ unclipSubviewsFromParent(udfpsContainer)
+ udfpsEnrollView.requestLayout()
+ }
+
+ private fun unclipSubviewsFromParent(view: View) {
+ var currParent = view.parent
+ while (currParent is ViewGroup) {
+ currParent.clipChildren = false
+ currParent.clipToPadding = false
+ currParent = currParent.parent
+ }
+ }
+
private fun HeaderText.toResource(): Int {
return when (this.enrollStageModel) {
EnrollStageModel.Center,
- EnrollStageModel.Guided,
- EnrollStageModel.Fingertip,
- EnrollStageModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title
+ EnrollStageModel.Guided -> R.string.security_settings_fingerprint_enroll_repeat_title
+ EnrollStageModel.Fingertip -> R.string.security_settings_udfps_enroll_fingertip_title
+ EnrollStageModel.Unknown -> R.string.security_settings_fingerprint_enroll_udfps_title
EnrollStageModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
EnrollStageModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
}
@@ -218,6 +306,5 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
companion object {
private const val TAG = "UDFPSEnrollFragment"
- private val navStep = FingerprintNavigationStep.Enrollment::class
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/model/UdfpsSensorLocation.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/model/UdfpsSensorLocation.kt
new file mode 100644
index 00000000000..6eb29f9ba6a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/model/UdfpsSensorLocation.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.model
+
+import android.graphics.PointF
+import com.android.systemui.biometrics.shared.model.FingerprintSensor
+
+data class UdfpsSensorLocation(val sensorLocation: FingerprintSensor, val offset: PointF?)
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsLastStepViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsLastStepViewModel.kt
new file mode 100644
index 00000000000..6a45ec4fcc2
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsLastStepViewModel.kt
@@ -0,0 +1,102 @@
+/*
+ * 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
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintVibrationEffects
+import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+/**
+ * This class is responsible for determining if we should animate the last step of an enrollment. It
+ * seems to be due to poor hardware implementation that the last step of a UDFPS enrollment takes
+ * much longer then normal (likely due to to saving the whole enrollment/or some kind of
+ * verification)
+ *
+ * Because of this, we will use the information of if a fingerprint was acquired(good) + enrollment
+ * has reached the last step
+ */
+class UdfpsLastStepViewModel(
+ private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
+ private val vibrationInteractor: VibrationInteractor,
+) : ViewModel() {
+
+ private val lastStep: MutableStateFlow = MutableStateFlow(null)
+ private val stepSize: MutableStateFlow = MutableStateFlow(null)
+
+ init {
+ viewModelScope.launch {
+ val steps =
+ fingerprintEnrollEnrollingViewModel.enrollFlow
+ .filterIsInstance()
+ .distinctUntilChanged()
+ .take(2)
+ .toList()
+ stepSize.update { steps[0].remainingSteps - steps[1].remainingSteps }
+ lastStep.update { FingerEnrollState.EnrollProgress(0, steps[0].totalStepsRequired) }
+ }
+ }
+
+ private val enrollProgress =
+ fingerprintEnrollEnrollingViewModel.enrollFlow.filterIsInstance<
+ FingerEnrollState.EnrollProgress
+ >()
+
+ /** Indicates if we should animate the completion of udfps enrollment. */
+ val shouldAnimateCompletion =
+ enrollProgress
+ .transform { it ->
+ if (it.remainingSteps == stepSize.value) {
+ fingerprintEnrollEnrollingViewModel.enrollFlow
+ .filterIsInstance()
+ .filter { acquired -> acquired.acquiredGood }
+ .collect {
+ vibrationInteractor.vibrate(
+ FingerprintVibrationEffects.UdfpsSuccess,
+ "UdfpsLastStepViewModel",
+ )
+
+ emit(lastStep.value)
+ }
+ }
+ }
+ .filterNotNull()
+
+ class UdfpsLastStepViewModelFactory(
+ private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
+ private val vibrationInteractor: VibrationInteractor,
+ ) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return UdfpsLastStepViewModel(fingerprintEnrollEnrollingViewModel, vibrationInteractor) as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt
index a22f680b641..508084e5916 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt
@@ -22,9 +22,10 @@ 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.model.EnrollStageModel
+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.AccessibilityInteractor
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
@@ -32,6 +33,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Fingerprin
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
+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.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText
@@ -41,6 +43,7 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Fing
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
@@ -51,6 +54,8 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */
@@ -66,13 +71,25 @@ class UdfpsViewModel(
backgroundViewModel: BackgroundViewModel,
sensorRepository: FingerprintSensorRepository,
udfpsEnrollInteractor: UdfpsEnrollInteractor,
+ fingerprintManager: FingerprintManagerInteractor,
+ val udfpsLastStepViewModel: UdfpsLastStepViewModel,
+ accessibilityInteractor: AccessibilityInteractor,
) : ViewModel() {
private val isSetupWizard = flowOf(false)
private var shouldResetErollment = false
private var _enrollState: Flow =
- fingerprintEnrollEnrollingViewModel.enrollFlow
+ fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
+ fingerprintEnrollEnrollingViewModel.enrollFlow
+ ) { props, enroll ->
+ if (props.sensorType.isUdfps()) {
+ enroll
+ } else {
+ null
+ }
+ }
+
/** The current state of the enrollment. */
var enrollState: Flow =
combine(fingerprintEnrollEnrollingViewModel.enrollFlowShouldBeRunning, _enrollState) {
@@ -86,48 +103,59 @@ class UdfpsViewModel(
}
.filterNotNull()
+ /** Indicates that overlay has been shown */
+ val overlayShown =
+ enrollState
+ .filterIsInstance()
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 1)
+
+ private var _userInteractedWithSensor = MutableStateFlow(false)
+
+ /**
+ * This indicates whether the user has interacted with the sensor or not. This indicates if we are
+ * in the initial state of the UI.
+ */
+ val userInteractedWithSensor: Flow =
+ enrollState.transform {
+ val interactiveMessage =
+ when (it) {
+ is FingerEnrollState.Acquired,
+ is FingerEnrollState.EnrollError,
+ is FingerEnrollState.EnrollHelp,
+ is FingerEnrollState.EnrollProgress,
+ is FingerEnrollState.PointerDown,
+ is FingerEnrollState.PointerUp -> true
+ else -> false
+ }
+ val hasPreviouslyInteracted = _userInteractedWithSensor.value or interactiveMessage
+ _userInteractedWithSensor.update { hasPreviouslyInteracted }
+ emit(hasPreviouslyInteracted)
+ }
+
/**
* 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 indicates at which point the UI should offset the fingerprint sensor icon for guided
- * enrollment.
- */
+ /** Indicates a step of guided enrollment, the ui should animate the icon to the new location. */
val guidedEnrollment: Flow =
- udfpsEnrollInteractor.guidedEnrollmentOffset.distinctUntilChanged()
+ udfpsEnrollInteractor.guidedEnrollmentOffset
+ .distinctUntilChanged()
+ .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)
- /** The saved version of [guidedEnrollment] */
- val guidedEnrollmentSaved: Flow =
- guidedEnrollment.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
+ private var _lastOrientation: Int? = null
+
+ /** In case of rotations we should ensure the UI does not re-animate the last state. */
+ private val shouldReplayLastEvent =
+ orientationInteractor.rotation.transform {
+ if (_lastOrientation != null && _lastOrientation!! != it) {
+ emit(true)
+ } else {
+ emit(false)
+ }
+ _lastOrientation = it
+ }
/**
* This is the saved progress, this is for when views are recreated and need saved state for the
@@ -136,8 +164,32 @@ class UdfpsViewModel(
var progressSaved: Flow =
enrollState
.filterIsInstance()
- .filterNotNull()
- .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
+ .combineTransform(shouldReplayLastEvent) { enroll, shouldReplay ->
+ if (shouldReplay) {
+ emit(enroll)
+ }
+ }
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 1)
+
+ /** Indicates if accessibility is enabled */
+ val accessibilityEnabled =
+ accessibilityInteractor.isAccessibilityEnabled.shareIn(
+ this.viewModelScope,
+ SharingStarted.Eagerly,
+ replay = 1,
+ )
+
+ /** Indicates if we are in reverse landscape orientation. */
+ val isReverseLandscape =
+ orientationInteractor.rotation
+ .transform { emit(it == Surface.ROTATION_270) }
+ .distinctUntilChanged()
+
+ /** Indicates if we are in the landscape orientation */
+ val isLandscape =
+ orientationInteractor.rotation
+ .transform { emit(it == Surface.ROTATION_90) }
+ .distinctUntilChanged()
/** This sends touch exploration events only used for debugging purposes. */
val touchExplorationDebug: Flow =
@@ -170,6 +222,18 @@ class UdfpsViewModel(
.filterNotNull()
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
+ /** The saved version of [guidedEnrollment] */
+ val guidedEnrollmentSaved: Flow =
+ combineTransform(guidedEnrollment, shouldReplayLastEvent, enrollStage) {
+ point,
+ shouldReplay,
+ stage ->
+ if (shouldReplay && stage is EnrollStageModel.Guided) {
+ emit(point)
+ }
+ }
+ .shareIn(viewModelScope, SharingStarted.Eagerly, 1)
+
init {
viewModelScope.launch {
enrollState
@@ -200,7 +264,7 @@ class UdfpsViewModel(
}
viewModelScope.launch {
- backgroundViewModel.background.filter { true }.collect { didGoToBackground() }
+ backgroundViewModel.background.filter { it }.collect { didGoToBackground() }
}
}
@@ -210,20 +274,12 @@ class UdfpsViewModel(
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
- }
+ ) { currDisplayDensity, defaultDisplayDensity, fontScale ->
+ if (fontScale > 1.0f) {
+ false
+ } else {
+ defaultDisplayDensity == currDisplayDensity
+ }
}
.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
@@ -234,6 +290,18 @@ class UdfpsViewModel(
}
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
+ /** Indicates if we should or shold not draw the fingerprint icon */
+ val shouldDrawIcon: Flow =
+ enrollState.transform { state ->
+ when (state) {
+ is FingerEnrollState.EnrollProgress,
+ is FingerEnrollState.EnrollError,
+ is FingerEnrollState.PointerUp -> emit(true)
+ is FingerEnrollState.PointerDown -> emit(false)
+ else -> {}
+ }
+ }
+
private val shouldClearDescriptionText = enrollStage.map { it is EnrollStageModel.Unknown }
/** The description text for UDFPS enrollment */
@@ -267,12 +335,12 @@ class UdfpsViewModel(
/** Indicates the negative button has been clicked */
fun negativeButtonClicked() {
- doReset()
navigationViewModel.update(
FingerprintAction.NEGATIVE_BUTTON_PRESSED,
navStep,
"$TAG#negativeButtonClicked",
)
+ doReset()
}
/** Indicates that an enrollment was completed */
@@ -282,7 +350,7 @@ class UdfpsViewModel(
}
/** Indicates that the application went to the background. */
- private fun didGoToBackground() {
+ fun didGoToBackground() {
navigationViewModel.update(
FingerprintAction.DID_GO_TO_BACKGROUND,
navStep,
@@ -293,11 +361,7 @@ class UdfpsViewModel(
private fun doReset() {
_enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow
- progressSaved =
- enrollState
- .filterIsInstance()
- .filterNotNull()
- .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
+ _userInteractedWithSensor.update { false }
}
/** The lottie that should be shown for UDFPS Enrollment */
@@ -320,6 +384,15 @@ class UdfpsViewModel(
vibrationInteractor.vibrate(vibrationEvent, "UdfpsEnrollFragment")
}
+ /** Indicates an error sent by the HAL has been acknowledged by the user */
+ fun errorDialogShown(it: FingerEnrollState.EnrollError) {
+ navigationViewModel.update(
+ FingerprintAction.USER_CLICKED_FINISH,
+ navStep,
+ "${TAG}#userClickedStopEnrollingDialog",
+ )
+ }
+
class UdfpsEnrollmentFactory(
private val vibrationInteractor: VibrationInteractor,
private val displayDensityInteractor: DisplayDensityInteractor,
@@ -332,6 +405,9 @@ class UdfpsViewModel(
private val backgroundViewModel: BackgroundViewModel,
private val sensorRepository: FingerprintSensorRepository,
private val udfpsEnrollInteractor: UdfpsEnrollInteractor,
+ private val fingerprintManager: FingerprintManagerInteractor,
+ private val udfpsLastStepViewModel: UdfpsLastStepViewModel,
+ private val accessibilityInteractor: AccessibilityInteractor,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@@ -348,6 +424,9 @@ class UdfpsViewModel(
backgroundViewModel,
sensorRepository,
udfpsEnrollInteractor,
+ fingerprintManager,
+ udfpsLastStepViewModel,
+ accessibilityInteractor,
)
as T
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt
index c209c55cb76..1eec046aff8 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt
@@ -36,8 +36,8 @@ import android.util.PathParser
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.core.animation.addListener
import androidx.core.graphics.toRect
-import androidx.core.graphics.toRectF
import com.android.settings.R
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import kotlin.math.sin
/**
@@ -45,7 +45,6 @@ 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
@@ -55,7 +54,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
@ColorInt private var movingTargetFill = 0
private var currentScale = 1.0f
private var alpha = 0
- private var guidedEnrollmentOffset: PointF? = null
+ private var stopDrawing = false
/**
* This is the physical location of the sensor. This rect will be updated by [drawSensorRectAt]
@@ -63,15 +62,12 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
private val sensorRectBounds: Rect = Rect()
/**
- * The following values are used to describe where the icon should be drawn. [currX] and [currY]
- * are changed based on the current guided enrollment step which is given by the
- * [UdfpsEnrollHelperV2]
+ * The following values are used to describe where the icon should be drawn. [sensorLeftOffset]
+ * and [sensorTopOffset] are changed based on the current guided enrollment step which is given by
+ * the [UdfpsEnrollHelperV2]
*/
- private var currX = 0f
- private var currY = 0f
-
- private var sensorWidth = 0f
- private var sensorHeight = 0f
+ private var sensorLeftOffset = 0f
+ private var sensorTopOffset = 0f
init {
fingerprintDrawable = createUdfpsIcon(context)
@@ -131,29 +127,35 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
* The [sensorRect] coordinates for the sensor area. The [sensorRect] should be the coordinates
* with respect to its root frameview
*/
- fun drawSensorRectAt(sensorRect: Rect) {
- Log.e(TAG, "UdfpsEnrollIcon#drawSensorRect($sensorRect)")
+ fun drawSensorRectAt(overlayParams: UdfpsOverlayParams) {
+ Log.e(TAG, "UdfpsEnrollIcon#drawSensorRect(${overlayParams.sensorBounds})")
+ val sensorRect = overlayParams.sensorBounds
sensorRectBounds.set(sensorRect)
fingerprintDrawable.bounds = sensorRect
movingTargetFpIcon.bounds = sensorRect
- currX = sensorRect.left.toFloat()
- currY = sensorRect.top.toFloat()
- sensorWidth = (sensorRect.right - sensorRect.left).toFloat()
- sensorHeight = (sensorRect.bottom - sensorRect.top).toFloat()
+
+ // End existing animation if we get an update of the sensor rect.
+ targetAnimatorSet?.end()
+
invalidateSelf()
}
/** Stop drawing the fingerprint icon. */
fun stopDrawing() {
- alpha = 0
+ stopDrawing = true
+ invalidateSelf()
}
/** Resume drawing the fingerprint icon */
fun startDrawing() {
- alpha = 255
+ stopDrawing = false
+ invalidateSelf()
}
override fun draw(canvas: Canvas) {
+ if (stopDrawing) {
+ return
+ }
movingTargetFpIcon.alpha = alpha
fingerprintDrawable.setAlpha(alpha)
sensorOutlinePaint.setAlpha(alpha)
@@ -165,23 +167,28 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
fingerprintDrawable.draw(canvas)
}
- private fun getCurrLocation(): RectF =
- RectF(currX, currY, currX + sensorWidth, currY + sensorHeight)
+ private fun getCurrLocation(): RectF {
+ val x = sensorRectBounds.left + sensorLeftOffset
+ val y = sensorRectBounds.top + sensorTopOffset
+ return RectF(x, y, x + sensorRectBounds.width(), y + sensorRectBounds.height())
+ }
- private fun animateMovement(currentBounds: Rect, offsetRect: RectF, scaleMovement: Boolean) {
- if (currentBounds.equals(offsetRect)) {
+ private fun animateMovement(leftOffset: Float, topOffset: Float, scaleMovement: Boolean) {
+ if (leftOffset == sensorLeftOffset && topOffset == sensorTopOffset) {
return
}
- val xAnimator = ValueAnimator.ofFloat(currentBounds.left.toFloat(), offsetRect.left)
+ val currLocation = getCurrLocation()
+
+ val xAnimator = ValueAnimator.ofFloat(currLocation.left - sensorRectBounds.left, leftOffset)
xAnimator.addUpdateListener {
- currX = it.animatedValue as Float
+ sensorLeftOffset = it.animatedValue as Float
invalidateSelf()
}
- val yAnimator = ValueAnimator.ofFloat(currentBounds.top.toFloat(), offsetRect.top)
+ val yAnimator = ValueAnimator.ofFloat(currLocation.top - sensorRectBounds.top, topOffset)
yAnimator.addUpdateListener {
- currY = it.animatedValue as Float
+ sensorTopOffset = it.animatedValue as Float
invalidateSelf()
}
val animators = mutableListOf(xAnimator, yAnimator)
@@ -199,6 +206,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
animators.add(scaleAnimator)
}
+ targetAnimatorSet?.cancel()
targetAnimatorSet = AnimatorSet()
targetAnimatorSet?.let {
@@ -209,51 +217,14 @@ 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
- }
-
/**
* Indicates a change to guided enrollment has occurred. Also indicates if we are recreating the
* view, in which case their is no need to animate the icon to whatever position it was in.
*/
- fun updateGuidedEnrollment(point: PointF, isRecreating: Boolean) {
- guidedEnrollmentOffset = point
- if (isRecreating) {
- setAnimationTimeToZero()
- } else {
- restoreAnimationTime()
- }
-
- val currentBounds = getCurrLocation().toRect()
- val offset = guidedEnrollmentOffset
- if (offset?.x != 0f && offset?.y != 0f) {
- val targetRect = Rect(sensorRectBounds).toRectF()
- // This is the desired location of the sensor rect, the [EnrollHelper]
- // offsets the initial sensor rect by a bit to get the user to move their finger a bit more.
- targetRect.offset(offset!!.x, offset!!.y)
- val shouldAnimateMovement = !currentBounds.equals(targetRect)
- if (shouldAnimateMovement) {
- targetAnimatorSet?.cancel()
- animateMovement(currentBounds, targetRect, true)
- } else {
- // If we are not offsetting the sensor, move it back to its original place
- animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
- }
- } else {
- // If we are not offsetting the sensor, move it back to its original place
- animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
- }
- invalidateSelf()
+ fun updateGuidedEnrollment(point: PointF, isRecreating: Boolean) {
+ val pointIsZero = point.x == 0f && point.y == 0f
+ val shouldAnimateMovement = pointIsZero || !isRecreating
+ animateMovement(point?.x ?: 0f, point?.y ?: 0f, shouldAnimateMovement)
}
companion object {
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt
index bf2f0261112..c3adc87fc7d 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt
@@ -27,16 +27,15 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.util.DisplayMetrics
-import android.util.Log
import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator
import android.view.animation.OvershootInterpolator
import androidx.annotation.ColorInt
-import androidx.core.animation.addListener
import androidx.core.animation.doOnEnd
import androidx.core.graphics.toRectF
import com.android.internal.annotations.VisibleForTesting
import com.android.settings.R
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sin
@@ -145,7 +144,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
/** Indicates enrollment progress has occurred. */
fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) {
-
afterFirstTouch = true
updateProgress(remaining, totalSteps, isRecreating)
}
@@ -216,8 +214,8 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
* Draws the progress with locations [sensorLocationX] [sensorLocationY], note these must be with
* respect to the parent framelayout.
*/
- fun drawProgressAt(sensorRect: Rect) {
- this.sensorRect.set(sensorRect)
+ fun drawProgressAt(overlayParams: UdfpsOverlayParams) {
+ this.sensorRect.set(overlayParams.sensorBounds)
invalidateSelf()
}
@@ -249,8 +247,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
restoreAnimationTime()
}
- this.remainingSteps = remainingSteps
- this.totalSteps = totalSteps
this.remainingSteps = remainingSteps
this.totalSteps = totalSteps
val targetProgress = (totalSteps - remainingSteps).toFloat().div(max(1, totalSteps))
@@ -290,12 +286,8 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
checkMarkDrawable.bounds = newBounds
checkMarkDrawable.setVisible(true, false)
}
- doOnEnd {
- onFinishedCompletionAnimation?.let{
- it()
- }
+ doOnEnd { onFinishedCompletionAnimation?.let { it() } }
- }
start()
}
}
@@ -356,6 +348,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
private fun flashHelpFillColor() {
if (fillColorAnimator != null && fillColorAnimator!!.isRunning) {
fillColorAnimator!!.end()
+ fillColorAnimator = null
}
@ColorInt val targetColor = helpColor
fillColorAnimator =
@@ -375,7 +368,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
* want to re-animate the progress/success animation with the default timer
*/
private fun setAnimationTimeToZero() {
- fillColorAnimationDuration = 0
animateArcDuration = 0
checkmarkAnimationDelayDuration = 0
checkmarkAnimationDuration = 0
@@ -383,7 +375,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
/** 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
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt
index 586408f0c28..d9593c117ad 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt
@@ -70,27 +70,11 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
fun setSensorRect(rect: Rect, sensorType: FingerprintSensorType) {
this.sensorRect = rect
this.fingerprintSensorType = sensorType
- findViewById(R.id.udfps_enroll_animation_fp_progress_view)?.also {
- it.setImageDrawable(fingerprintProgressDrawable)
- }
- findViewById(R.id.udfps_enroll_animation_fp_view)?.also {
- it.setImageDrawable(fingerprintIcon)
- }
- val rotation = display.rotation
var displayInfo = DisplayInfo()
context.display.getDisplayInfo(displayInfo)
+ val rotation = displayInfo.rotation
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]
@@ -99,22 +83,44 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
// 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 -> {
+ Surface.ROTATION_90 -> {
sensorRectOffset.set(
sensorRectOffset.top,
sensorRectOffset.left,
sensorRectOffset.bottom,
sensorRectOffset.right,
)
+ sensorRectOffset.offset(-parentLeft, -parentTop)
}
- else -> {}
- }
- // Translate the sensor position into UdfpsEnrollView's view space.
- sensorRectOffset.offset(-parentLeft, -parentTop)
+ // When the view is rotated 270 degrees, 0,0 is the top corner left
+ Surface.ROTATION_270 -> {
+ sensorRectOffset.set(
+ (displayInfo.naturalHeight - sensorRectOffset.bottom) - parentLeft,
+ sensorRectOffset.left - parentTop,
+ (displayInfo.naturalHeight - sensorRectOffset.top) - parentLeft,
+ sensorRectOffset.right - parentTop,
+ )
+ }
+ else -> {
- fingerprintIcon.drawSensorRectAt(sensorRectOffset)
- fingerprintProgressDrawable.drawProgressAt(sensorRectOffset)
+ sensorRectOffset.offset(-parentLeft, -parentTop)
+ }
+ }
+
+ // Translate the sensor position into UdfpsEnrollView's view space.
+ val overlayParams =
+ UdfpsOverlayParams(
+ sensorRectOffset,
+ fingerprintProgressDrawable.bounds,
+ displayInfo.naturalWidth,
+ displayInfo.naturalHeight,
+ scaleFactor,
+ rotation,
+ sensorType.toInt(),
+ )
+
+ fingerprintIcon.drawSensorRectAt(overlayParams)
+ fingerprintProgressDrawable.drawProgressAt(overlayParams)
touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils)
}
@@ -126,11 +132,8 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
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")
+ // Else ignore
+ else -> {}
}
}
@@ -145,7 +148,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
}
}
- private fun udfpsError(errMsgId: Int, errString: String) {}
/**
* Sends a touch exploration event to the [onHoverListener] this should only be used for
* debugging.
@@ -170,8 +172,15 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
onHoverListener = listener
}
- private fun overlayShown() {
- Log.e(TAG, "Implement overlayShown")
+ /** Indicates the overlay has been shown */
+ fun overlayShown() {
+ Log.d(TAG, "Showing udfps overlay")
+ findViewById(R.id.udfps_enroll_animation_fp_progress_view)?.also {
+ it.setImageDrawable(fingerprintProgressDrawable)
+ }
+ findViewById(R.id.udfps_enroll_animation_fp_view)?.also {
+ it.setImageDrawable(fingerprintIcon)
+ }
}
/** Receive enroll progress event */
@@ -190,16 +199,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
if (animateIfLastStepGood) fingerprintProgressDrawable.onLastStepAcquired()
}
- /** Receive onPointerDown event */
- private fun onPointerDown() {
- fingerprintIcon.stopDrawing()
- }
-
- /** Receive onPointerUp event */
- private fun onPointerUp() {
- 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.
@@ -261,6 +260,17 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
fingerprintIcon.updateGuidedEnrollment(point, false)
}
+ /** Indicates if the enroll icon should be drawn. */
+ fun shouldDrawIcon(it: Boolean) {
+ post {
+ if (it) {
+ fingerprintIcon.startDrawing()
+ } else {
+ fingerprintIcon.stopDrawing()
+ }
+ }
+ }
+
companion object {
private const val TAG = "UdfpsEnrollView"
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
index 7ddb142c01e..bbe3cfd2775 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
@@ -16,6 +16,7 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
+import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.systemui.biometrics.shared.model.FingerprintSensor
@@ -68,7 +69,8 @@ class FingerprintEnrollEnrollingViewModel(
val enrollFlow =
enrollFlowShouldBeRunning.transformLatest {
if (it) {
- fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) }
+ fingerprintEnrollViewModel.enrollFlow.collect { event ->
+ emit(event) }
}
}
@@ -82,4 +84,8 @@ class FingerprintEnrollEnrollingViewModel(
as T
}
}
+
+ companion object {
+ private val TAG = "FingerprintEnrollEnrollingViewModel"
+ }
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index e0171439198..44915fe2829 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -66,6 +66,7 @@ import java.util.List;
public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment {
public static final String KEY_DEVICE_ADDRESS = "device_address";
private static final String TAG = "BTDeviceDetailsFrg";
+ private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
@VisibleForTesting
static int EDIT_DEVICE_NAME_ITEM_ID = Menu.FIRST;
@@ -95,11 +96,14 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
LocalBluetoothManager mManager;
@VisibleForTesting
CachedBluetoothDevice mCachedDevice;
+ BluetoothAdapter mBluetoothAdapter;
@Nullable
InputDevice mInputDevice;
private UserManager mUserManager;
+ int mExtraControlViewWidth = 0;
+ boolean mExtraControlUriLoaded = false;
private final BluetoothCallback mBluetoothCallback =
new BluetoothCallback() {
@@ -115,6 +119,16 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
}
};
+ private final BluetoothAdapter.OnMetadataChangedListener mExtraControlMetadataListener =
+ (device, key, value) -> {
+ if (key == METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
+ && mExtraControlViewWidth > 0
+ && !mExtraControlUriLoaded) {
+ Log.i(TAG, "Update extra control UI because of metadata change.");
+ updateExtraControlUri(mExtraControlViewWidth);
+ }
+ };
+
public BluetoothDeviceDetailsFragment() {
super(DISALLOW_CONFIG_BLUETOOTH);
}
@@ -173,6 +187,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
public void onAttach(Context context) {
mDeviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS);
mManager = getLocalBluetoothManager(context);
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mCachedDevice = getCachedDevice(mDeviceAddress);
mUserManager = getUserManager();
@@ -202,12 +217,18 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
: null);
mManager.getEventManager().registerCallback(mBluetoothCallback);
+ mBluetoothAdapter.addOnMetadataChangedListener(
+ mCachedDevice.getDevice(),
+ context.getMainExecutor(),
+ mExtraControlMetadataListener);
}
@Override
public void onDetach() {
super.onDetach();
mManager.getEventManager().unregisterCallback(mBluetoothCallback);
+ mBluetoothAdapter.removeOnMetadataChangedListener(
+ mCachedDevice.getDevice(), mExtraControlMetadataListener);
}
private void updateExtraControlUri(int viewWidth) {
@@ -222,9 +243,9 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
controlUri = Uri.parse(uri + viewWidth);
} catch (NullPointerException exception) {
Log.d(TAG, "unable to parse uri");
- controlUri = null;
}
}
+ mExtraControlUriLoaded |= controlUri != null;
final SlicePreferenceController slicePreferenceController = use(
SlicePreferenceController.class);
slicePreferenceController.setSliceUri(sliceEnabled ? controlUri : null);
@@ -253,7 +274,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
if (view.getWidth() <= 0) {
return;
}
- updateExtraControlUri(view.getWidth() - getPaddingSize());
+ mExtraControlViewWidth = view.getWidth() - getPaddingSize();
+ updateExtraControlUri(mExtraControlViewWidth);
view.getViewTreeObserver().removeOnGlobalLayoutListener(
mOnGlobalLayoutListener);
}
diff --git a/src/com/android/settings/datausage/DataUsageSummaryPreference.java b/src/com/android/settings/datausage/DataUsageSummaryPreference.java
index 93d930c5211..6500501b210 100644
--- a/src/com/android/settings/datausage/DataUsageSummaryPreference.java
+++ b/src/com/android/settings/datausage/DataUsageSummaryPreference.java
@@ -20,6 +20,7 @@ import android.annotation.AttrRes;
import android.content.Context;
import android.graphics.Typeface;
import android.icu.text.MessageFormat;
+import android.telephony.SubscriptionPlan;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
@@ -66,7 +67,7 @@ public class DataUsageSummaryPreference extends Preference {
@Nullable
private Long mCycleEndTimeMs;
/** The time of the last update in standard milliseconds since the epoch */
- private long mSnapshotTimeMs;
+ private long mSnapshotTimeMs = SubscriptionPlan.TIME_UNKNOWN;
/** Name of carrier, or null if not available */
private CharSequence mCarrierName;
private CharSequence mLimitInfoText;
diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java
index b53bf477104..9e08664c901 100644
--- a/src/com/android/settings/fuelgauge/BatteryUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryUtils.java
@@ -393,7 +393,7 @@ public class BatteryUtils {
packageName, uid, ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
mode == AppOpsManager.MODE_IGNORED,
ActivityManager.RESTRICTION_REASON_USER,
- "settings", 0);
+ "settings", ActivityManager.RESTRICTION_SOURCE_USER, 0L);
}
// Control whether app could run jobs in the background
mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName, mode);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
index 37d2fceece2..4acaeeaf5bb 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
@@ -767,6 +767,10 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
}
private class BatteryChartAccessibilityNodeProvider extends AccessibilityNodeProvider {
+ private static final int UNDEFINED = Integer.MIN_VALUE;
+
+ private int mAccessibilityFocusNodeViewId = UNDEFINED;
+
@Override
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) {
@@ -794,6 +798,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
R.string.battery_usage_time_info_and_battery_level,
slotTimeInfo,
batteryLevelInfo));
+ childInfo.setAccessibilityFocused(virtualViewId == mAccessibilityFocusNodeViewId);
final Rect bounds = new Rect();
getBoundsOnScreen(bounds, true);
@@ -815,10 +820,14 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
return true;
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+ mAccessibilityFocusNodeViewId = virtualViewId;
return sendAccessibilityEvent(
virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+ if (mAccessibilityFocusNodeViewId == virtualViewId) {
+ mAccessibilityFocusNodeViewId = UNDEFINED;
+ }
return sendAccessibilityEvent(
virtualViewId,
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
diff --git a/src/com/android/settings/network/UiccSlotUtil.java b/src/com/android/settings/network/UiccSlotUtil.java
index df23f122e83..f15808f4ef6 100644
--- a/src/com/android/settings/network/UiccSlotUtil.java
+++ b/src/com/android/settings/network/UiccSlotUtil.java
@@ -329,38 +329,6 @@ public class UiccSlotUtil {
return INVALID_PHYSICAL_SLOT_ID;
}
- // Device | |Slot |
- // Working| |Mapping|
- // State |Type |Mode |Friendly name
- //--------------------------------------------------------------------------
- // Single |SIM pSIM [RIL 0] |1 |pSIM active
- // Single |SIM MEP Port #0 [RIL0] |2 |eSIM Port0 active
- // Single |SIM MEP Port #1 [RIL0] |2.1 |eSIM Port1 active
- // DSDS |pSIM [RIL 0] + MEP Port #0 [RIL 1] |3 |pSIM+Port0
- // DSDS |pSIM [RIL 0] + MEP Port #1 [RIL 1] |3.1 |pSIM+Port1
- // DSDS |MEP Port #0 [RIL 0] + MEP Port #1 [RIL1]|3.2 |Dual-Ports-A
- // DSDS |MEP Port #1 [RIL 0] + MEP Port #0 [RIL1]|4 |Dual-Ports-B
- //
- // The rules are:
- // 1. pSIM's logical slots always is [RIL 0].
- // 2. assign the new active port to the same stack that will be de-activated
- // For example: mode#3->mode#4
- // 3. Add an eSIM carrier or enable eSIM carrier. The cases are at the below.
- // 1) 1 => 2 / 2.1 / 3 / 3.1
- // 2) 2 => 1 / 3 / 3.2
- // 3) 2.1 => 3.1 / 4
- // 4) 3 => 4
- // 5) 3.1 => 3.2
- // Note:
- // 1) 2 <=> 2.1 blocked by LPA (reason: existing active port in SS so just re-use)
- // 2) 3 <=> 3.1 blocked by LPA (reason: if pSIM+an active port, re-use the active port)
- // 4. pSIM insertion or enabling
- // 1) 2 => 1 / 3
- // 2) 2.1 => 1 / 3.1
- // 3) 3.2 => 3 / 3.1
- // 4) 4 => 3 / 3.1
-
-
@VisibleForTesting
static Collection prepareUiccSlotMappings(
Collection uiccSlotMappings, boolean isPsim, int physicalSlotId,
diff --git a/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceController.kt b/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceController.kt
index e1346819f7d..a6fb7ba8f56 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceController.kt
+++ b/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceController.kt
@@ -17,7 +17,6 @@
package com.android.settings.network.telephony
import android.content.Context
-import android.os.UserManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
@@ -41,6 +40,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+
/**
* Preference controller for "IMEI"
*/
@@ -123,17 +123,21 @@ class MobileNetworkImeiPreferenceController(context: Context, key: String) :
ImeiInfoDialogFragment.show(fragment, simSlot, preference.title.toString())
return true
}
+
private fun getImei(): String {
val phoneType = getPhoneType()
return if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) mTelephonyManager.meid?: String()
else mTelephonyManager.imei?: String()
}
+
private fun getTitleForGsmPhone(): String {
- return mContext.getString(R.string.status_imei)
+ return mContext.getString(
+ if (isPrimaryImei()) R.string.imei_primary else R.string.status_imei)
}
private fun getTitleForCdmaPhone(): String {
- return mContext.getString(R.string.status_meid_number)
+ return mContext.getString(
+ if (isPrimaryImei()) R.string.meid_primary else R.string.status_meid_number)
}
private fun getTitle(): String {
@@ -142,6 +146,28 @@ class MobileNetworkImeiPreferenceController(context: Context, key: String) :
else getTitleForGsmPhone()
}
+ /**
+ * As per GSMA specification TS37, below Primary IMEI requirements are mandatory to support
+ *
+ * TS37_2.2_REQ_5
+ * TS37_2.2_REQ_8 (Attached the document has description about this test cases)
+ */
+ protected fun isPrimaryImei(): Boolean {
+ val imei = getImei()
+ var primaryImei = String()
+
+ try {
+ primaryImei = mTelephonyManager.primaryImei
+ } catch (exception: Exception) {
+ Log.e(TAG, "PrimaryImei not available. $exception")
+ }
+ return primaryImei == imei && isMultiSim()
+ }
+
+ private fun isMultiSim(): Boolean {
+ return mTelephonyManager.activeModemCount > 1
+ }
+
fun getPhoneType(): Int {
return mTelephonyManager.currentPhoneType
}
diff --git a/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java b/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java
index b5e5dc7295a..587f6409dda 100644
--- a/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java
+++ b/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java
@@ -16,9 +16,10 @@
package com.android.settings.notification.modes;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
+import android.service.notification.ZenPolicy;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -26,11 +27,17 @@ import androidx.preference.Preference;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.google.common.base.Preconditions;
+
+import java.util.function.Function;
+
/**
* Base class for any preference controllers pertaining to any single Zen mode.
*/
abstract class AbstractZenModePreferenceController extends AbstractPreferenceController {
+ private static final String TAG = "AbstractZenModePreferenceController";
+
@Nullable
protected ZenModesBackend mBackend;
@@ -38,7 +45,7 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
private ZenMode mZenMode;
@NonNull
- final String mKey;
+ private final String mKey;
// ZenModesBackend should only be passed in if the preference controller may set the user's
// policy for this zen mode. Otherwise, if the preference controller is essentially read-only
@@ -67,20 +74,56 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
updateState(preference);
}
- @Nullable
- public ZenMode getMode() {
- return mZenMode;
+ @Override
+ public final void updateState(Preference preference) {
+ super.updateState(preference);
+ if (mZenMode != null) {
+ updateState(preference, mZenMode);
+ }
}
- @Nullable
- public AutomaticZenRule getAZR() {
- if (mZenMode == null || mZenMode.getRule() == null) {
+ abstract void updateState(Preference preference, @NonNull ZenMode zenMode);
+
+ @Override
+ public final CharSequence getSummary() {
+ if (mZenMode != null) {
+ return getSummary(mZenMode);
+ } else {
return null;
}
- return mZenMode.getRule();
}
- /** Implementations of this class should override
- * {@link AbstractPreferenceController#updateState(Preference)} to specify what should
- * happen when the preference is updated */
+ @Nullable
+ protected CharSequence getSummary(@NonNull ZenMode zenMode) {
+ return null;
+ }
+
+ /**
+ * Subclasses should call this method (or a more specific one, like {@link #savePolicy} from
+ * their {@code onPreferenceChange()} or similar, in order to apply changes to the mode being
+ * edited (e.g. {@code saveMode(mode -> { mode.setX(value); return mode; } }.
+ *
+ * @param updater Function to update the {@link ZenMode}. Modifying and returning the same
+ * instance is ok.
+ */
+ protected final boolean saveMode(Function updater) {
+ Preconditions.checkState(mBackend != null);
+ ZenMode mode = mZenMode;
+ if (mode == null) {
+ Log.wtf(TAG, "Cannot save mode, it hasn't been loaded (" + getClass() + ")");
+ return false;
+ }
+ mode = updater.apply(mode);
+ mBackend.updateMode(mode);
+ return true;
+ }
+
+ protected final boolean savePolicy(Function updater) {
+ return saveMode(mode -> {
+ ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder(mode.getPolicy());
+ policyBuilder = updater.apply(policyBuilder);
+ mode.setPolicy(policyBuilder.build());
+ return mode;
+ });
+ }
}
diff --git a/src/com/android/settings/notification/modes/ZenMode.java b/src/com/android/settings/notification/modes/ZenMode.java
index bf10cd42409..058799b6225 100644
--- a/src/com/android/settings/notification/modes/ZenMode.java
+++ b/src/com/android/settings/notification/modes/ZenMode.java
@@ -26,6 +26,7 @@ import android.app.AutomaticZenRule;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
import android.util.Log;
@@ -150,14 +151,6 @@ class ZenMode {
}
}
- /**
- * Use sparingly. If you're updating a policy field, use
- * {@link #setPolicy(android.service.notification.ZenPolicy)} instead.
- */
- public void setAzr(@NonNull AutomaticZenRule newRule) {
- mRule = newRule;
- }
-
/**
* Updates the {@link ZenPolicy} of the associated {@link AutomaticZenRule} based on the
* supplied policy. In some cases this involves conversions, so that the following call
@@ -204,6 +197,13 @@ class ZenMode {
mRule.setZenPolicy(policy);
}
+ @NonNull
+ public ZenDeviceEffects getDeviceEffects() {
+ return mRule.getDeviceEffects() != null
+ ? mRule.getDeviceEffects()
+ : new ZenDeviceEffects.Builder().build();
+ }
+
public boolean canBeDeleted() {
return !mIsManualDnd;
}
diff --git a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
index 1d1d7505944..746af44155b 100644
--- a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
@@ -20,12 +20,15 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I
import android.content.Context;
import android.os.Bundle;
+
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
+
import com.android.settings.core.SubSettingLauncher;
-public class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceController {
+class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceController {
- private ZenModeSummaryHelper mSummaryHelper;
+ private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeCallsLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
@@ -34,16 +37,15 @@ public class ZenModeCallsLinkPreferenceController extends AbstractZenModePrefere
}
@Override
- public void updateState(Preference preference) {
- super.updateState(preference);
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, getMode().getId());
+ bundle.putString(MODE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeCallsFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
- preference.setSummary(mSummaryHelper.getCallsSettingSummary(getMode()));
+ preference.setSummary(mSummaryHelper.getCallsSettingSummary(zenMode));
}
}
\ No newline at end of file
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java
index f69d0d63fac..bca7b559a92 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java
@@ -16,16 +16,10 @@
package com.android.settings.notification.modes;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
-
-import android.app.AutomaticZenRule;
import android.content.Context;
import android.service.notification.ZenDeviceEffects;
-import android.service.notification.ZenPolicy;
+
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -38,9 +32,9 @@ public class ZenModeDisplayEffectPreferenceController extends AbstractZenModePre
}
@Override
- public void updateState(Preference preference) {
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
TwoStatePreference pref = (TwoStatePreference) preference;
- ZenDeviceEffects effects = getMode().getRule().getDeviceEffects();
+ ZenDeviceEffects effects = zenMode.getRule().getDeviceEffects();
if (effects == null) {
pref.setChecked(false);
} else {
@@ -62,33 +56,27 @@ public class ZenModeDisplayEffectPreferenceController extends AbstractZenModePre
}
@Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
+ public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
final boolean allow = (Boolean) newValue;
-
- ZenDeviceEffects currEffects = getMode().getRule().getDeviceEffects();
- ZenDeviceEffects.Builder updatedEffects = currEffects == null
- ? new ZenDeviceEffects.Builder()
- : new ZenDeviceEffects.Builder(getMode().getRule().getDeviceEffects());
- switch (getPreferenceKey()) {
- case "effect_greyscale":
- updatedEffects.setShouldDisplayGrayscale(allow);
- break;
- case "effect_aod":
- updatedEffects.setShouldSuppressAmbientDisplay(allow);
- break;
- case "effect_wallpaper":
- updatedEffects.setShouldDimWallpaper(allow);
- break;
- case "effect_dark_theme":
- updatedEffects.setShouldUseNightMode(allow);
- break;
- }
- AutomaticZenRule updatedAzr = new AutomaticZenRule.Builder(getMode().getRule())
- .setDeviceEffects(updatedEffects.build())
- .build();
- getMode().setAzr(updatedAzr);
- mBackend.updateMode(getMode());
-
- return true;
+ return saveMode(zenMode -> {
+ ZenDeviceEffects.Builder updatedEffects = new ZenDeviceEffects.Builder(
+ zenMode.getDeviceEffects());
+ switch (getPreferenceKey()) {
+ case "effect_greyscale":
+ updatedEffects.setShouldDisplayGrayscale(allow);
+ break;
+ case "effect_aod":
+ updatedEffects.setShouldSuppressAmbientDisplay(allow);
+ break;
+ case "effect_wallpaper":
+ updatedEffects.setShouldDimWallpaper(allow);
+ break;
+ case "effect_dark_theme":
+ updatedEffects.setShouldUseNightMode(allow);
+ break;
+ }
+ zenMode.getRule().setDeviceEffects(updatedEffects.build());
+ return zenMode;
+ });
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
index 943874a5d48..8720a4b14fb 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
@@ -20,12 +20,15 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I
import android.content.Context;
import android.os.Bundle;
+
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
+
import com.android.settings.core.SubSettingLauncher;
-public class ZenModeDisplayLinkPreferenceController extends AbstractZenModePreferenceController {
+class ZenModeDisplayLinkPreferenceController extends AbstractZenModePreferenceController {
- ZenModeSummaryHelper mSummaryHelper;
+ private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeDisplayLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
@@ -34,10 +37,9 @@ public class ZenModeDisplayLinkPreferenceController extends AbstractZenModePrefe
}
@Override
- public void updateState(Preference preference) {
- super.updateState(preference);
+ void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, getMode().getId());
+ bundle.putString(MODE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeDisplayFragment.class.getName())
@@ -47,7 +49,7 @@ public class ZenModeDisplayLinkPreferenceController extends AbstractZenModePrefe
}
@Override
- public CharSequence getSummary() {
- return mSummaryHelper.getDisplayEffectsSummary(getMode());
+ public CharSequence getSummary(@NonNull ZenMode zenMode) {
+ return mSummaryHelper.getDisplayEffectsSummary(zenMode);
}
}
\ No newline at end of file
diff --git a/src/com/android/settings/notification/modes/ZenModeHeaderController.java b/src/com/android/settings/notification/modes/ZenModeHeaderController.java
index f55c02d7daa..f791519b178 100644
--- a/src/com/android/settings/notification/modes/ZenModeHeaderController.java
+++ b/src/com/android/settings/notification/modes/ZenModeHeaderController.java
@@ -47,9 +47,8 @@ class ZenModeHeaderController extends AbstractZenModePreferenceController {
}
@Override
- public void updateState(Preference preference) {
- ZenMode mode = getMode();
- if (mode == null || mFragment == null) {
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
+ if (mFragment == null) {
return;
}
@@ -62,9 +61,9 @@ class ZenModeHeaderController extends AbstractZenModePreferenceController {
}
FutureUtil.whenDone(
- mode.getIcon(IconLoader.getInstance(mContext)),
+ zenMode.getIcon(IconLoader.getInstance(mContext)),
icon -> mHeaderController.setIcon(icon)
- .setLabel(mode.getRule().getName())
+ .setLabel(zenMode.getRule().getName())
.done(false /* rebindActions */),
mContext.getMainExecutor());
}
diff --git a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
index 8261008aaf4..300ebbc14d9 100644
--- a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
@@ -20,10 +20,13 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I
import android.content.Context;
import android.os.Bundle;
+
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
+
import com.android.settings.core.SubSettingLauncher;
-public class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceController {
+class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeMessagesLinkPreferenceController(Context context, String key,
@@ -33,11 +36,9 @@ public class ZenModeMessagesLinkPreferenceController extends AbstractZenModePref
}
@Override
- public void updateState(Preference preference) {
- super.updateState(preference);
-
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, getMode().getId());
+ bundle.putString(MODE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeMessagesFragment.class.getName())
@@ -46,6 +47,6 @@ public class ZenModeMessagesLinkPreferenceController extends AbstractZenModePref
.toIntent());
preference.setEnabled(true);
- preference.setSummary(mSummaryHelper.getMessagesSettingSummary(getMode().getPolicy()));
+ preference.setSummary(mSummaryHelper.getMessagesSettingSummary(zenMode.getPolicy()));
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
index 9807431fb17..da3b3be1741 100644
--- a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
@@ -20,12 +20,16 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I
import android.content.Context;
import android.os.Bundle;
+
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
+
import com.android.settings.core.SubSettingLauncher;
-public class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePreferenceController {
+class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePreferenceController {
+
+ private final ZenModeSummaryHelper mSummaryBuilder;
- ZenModeSummaryHelper mSummaryBuilder;
public ZenModeNotifVisLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
@@ -33,10 +37,9 @@ public class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePref
}
@Override
- public void updateState(Preference preference) {
- super.updateState(preference);
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, getMode().getId());
+ bundle.putString(MODE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeNotifVisFragment.class.getName())
@@ -46,7 +49,7 @@ public class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePref
}
@Override
- public CharSequence getSummary() {
- return mSummaryBuilder.getBlockedEffectsSummary(getMode());
+ public CharSequence getSummary(@NonNull ZenMode zenMode) {
+ return mSummaryBuilder.getBlockedEffectsSummary(zenMode);
}
}
\ No newline at end of file
diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java
index 9b89a646811..39f0d3cb9dc 100644
--- a/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java
@@ -18,9 +18,12 @@ package com.android.settings.notification.modes;
import android.content.Context;
import android.service.notification.ZenPolicy;
+
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
+
import com.android.settings.widget.DisabledCheckBoxPreference;
public class ZenModeNotifVisPreferenceController extends AbstractZenModePreferenceController
@@ -53,13 +56,13 @@ public class ZenModeNotifVisPreferenceController extends AbstractZenModePreferen
}
@Override
- public void updateState(Preference preference) {
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
- boolean suppressed = !getMode().getPolicy().isVisualEffectAllowed(mEffect, false);
+ boolean suppressed = !zenMode.getPolicy().isVisualEffectAllowed(mEffect, false);
boolean parentSuppressed = false;
if (mParentSuppressedEffects != null) {
for (@ZenPolicy.VisualEffect int parentEffect : mParentSuppressedEffects) {
- if (!getMode().getPolicy().isVisualEffectAllowed(parentEffect, true)) {
+ if (!zenMode.getPolicy().isVisualEffectAllowed(parentEffect, true)) {
parentSuppressed = true;
}
}
@@ -77,15 +80,6 @@ public class ZenModeNotifVisPreferenceController extends AbstractZenModePreferen
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean allowEffect = !((Boolean) newValue);
-
- if (getMode().getPolicy().isVisualEffectAllowed(mEffect, true) != allowEffect) {
- ZenPolicy diffPolicy = new ZenPolicy.Builder()
- .showVisualEffect(mEffect, allowEffect)
- .build();
- getMode().setPolicy(diffPolicy);
- mBackend.updateMode(getMode());
- }
-
- return true;
+ return savePolicy(policy -> policy.showVisualEffect(mEffect, allowEffect));
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index a43f8b056e1..1a00207c232 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -16,20 +16,22 @@
package com.android.settings.notification.modes;
-
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
+
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
+
import com.android.settings.core.SubSettingLauncher;
/**
* Preference with a link and summary about what other sounds can break through the mode
*/
-public class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceController {
+class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceController {
- ZenModeSummaryHelper mSummaryHelper;
+ private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeOtherLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
@@ -38,15 +40,14 @@ public class ZenModeOtherLinkPreferenceController extends AbstractZenModePrefere
}
@Override
- public void updateState(Preference preference) {
- super.updateState(preference);
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, getMode().getId());
+ bundle.putString(MODE_ID, zenMode.getId());
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeOtherFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
- preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(getMode()));
+ preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode));
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java
index e31fa0fa08b..a770164eb63 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java
@@ -23,11 +23,12 @@ import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
import android.content.Context;
-import android.service.notification.ZenPolicy;
+
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
-public class ZenModeOtherPreferenceController extends AbstractZenModePreferenceController
+class ZenModeOtherPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
public ZenModeOtherPreferenceController(Context context, String key,
@@ -36,24 +37,15 @@ public class ZenModeOtherPreferenceController extends AbstractZenModePreferenceC
}
@Override
- public void updateState(Preference preference) {
- super.updateState(preference);
-
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
TwoStatePreference pref = (TwoStatePreference) preference;
- pref.setChecked(getMode().getPolicy().isCategoryAllowed(getCategory(), true));
+ pref.setChecked(zenMode.getPolicy().isCategoryAllowed(getCategory(), true));
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean allow = (Boolean) newValue;
-
- ZenPolicy diffPolicy = new ZenPolicy.Builder()
- .allowCategory(getCategory(), allow)
- .build();
- getMode().setPolicy(diffPolicy);
- mBackend.updateMode(getMode());
-
- return true;
+ return savePolicy(policy -> policy.allowCategory(getCategory(), allow));
}
private int getCategory() {
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
index f12200627ef..55a83d67e3a 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -16,21 +16,22 @@
package com.android.settings.notification.modes;
-
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
+
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
-import com.android.settings.R;
+
import com.android.settings.core.SubSettingLauncher;
/**
* Preference with a link and summary about what calls and messages can break through the mode
*/
-public class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceController {
+class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceController {
- ZenModeSummaryHelper mSummaryHelper;
+ private final ZenModeSummaryHelper mSummaryHelper;
public ZenModePeopleLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
@@ -39,16 +40,15 @@ public class ZenModePeopleLinkPreferenceController extends AbstractZenModePrefer
}
@Override
- public void updateState(Preference preference) {
- super.updateState(preference);
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, getMode().getId());
+ bundle.putString(MODE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModePeopleFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
- preference.setSummary(mSummaryHelper.getPeopleSummary(getMode()));
+ preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode));
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
index a71bbe844b4..16e8858ed4c 100644
--- a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
@@ -36,10 +36,13 @@ import android.provider.Contacts;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.ZenPolicy;
import android.view.View;
+
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
+
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.app.ConversationListSettings;
@@ -59,7 +62,7 @@ import java.util.Map;
* bypass DND for calls or messages, which may be one of the following values: starred contacts, all
* contacts, priority conversations (for messages only), anyone, or no one.
*/
-public class ZenModePrioritySendersPreferenceController
+class ZenModePrioritySendersPreferenceController
extends AbstractZenModePreferenceController {
private final boolean mIsMessages; // if this is false, then this preference is for calls
@@ -124,12 +127,12 @@ public class ZenModePrioritySendersPreferenceController
}
@Override
- public void updateState(Preference preference) {
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
if (mIsMessages) {
updateChannelCounts();
}
- final int currContactsSetting = getPrioritySenders();
- final int currConversationsSetting = getPriorityConversationSenders();
+ final int currContactsSetting = getPrioritySenders(zenMode.getPolicy());
+ final int currConversationsSetting = getPriorityConversationSenders(zenMode.getPolicy());
for (SelectorWithWidgetPreference pref : mSelectorPreferences) {
// for each preference, check whether the current state matches what this state
// would look like if the button were checked.
@@ -173,17 +176,17 @@ public class ZenModePrioritySendersPreferenceController
mNumImportantConversations = numImportantConversations;
}
- private int getPrioritySenders() {
+ private int getPrioritySenders(ZenPolicy policy) {
if (mIsMessages) {
- return getMode().getPolicy().getPriorityMessageSenders();
+ return policy.getPriorityMessageSenders();
} else {
- return getMode().getPolicy().getPriorityCallSenders();
+ return policy.getPriorityCallSenders();
}
}
- private int getPriorityConversationSenders() {
+ private int getPriorityConversationSenders(ZenPolicy policy) {
if (mIsMessages) {
- return getMode().getPolicy().getPriorityConversationSenders();
+ return policy.getPriorityConversationSenders();
}
return CONVERSATION_SENDERS_UNSET;
}
@@ -419,29 +422,31 @@ public class ZenModePrioritySendersPreferenceController
@VisibleForTesting
SelectorWithWidgetPreference.OnClickListener mSelectorClickListener =
new SelectorWithWidgetPreference.OnClickListener() {
- @Override
- public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
- // The settingsToSaveOnClick function takes whether the preference is a
- // checkbox into account to determine whether this selection is checked or unchecked.
- final int[] settingsToSave = settingsToSaveOnClick(preference,
- getPrioritySenders(), getPriorityConversationSenders());
- final int prioritySendersSetting = settingsToSave[0];
- final int priorityConvosSetting = settingsToSave[1];
+ @Override
+ public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
+ savePolicy(policy -> {
+ ZenPolicy previousPolicy = policy.build();
+ // The settingsToSaveOnClick function takes whether the preference is a
+ // checkbox into account to determine whether this selection is checked or
+ // unchecked.
+ final int[] settingsToSave = settingsToSaveOnClick(preference,
+ getPrioritySenders(previousPolicy),
+ getPriorityConversationSenders(previousPolicy));
+ final int prioritySendersSetting = settingsToSave[0];
+ final int priorityConvosSetting = settingsToSave[1];
- ZenPolicy.Builder diffPolicy = new ZenPolicy.Builder();
- if (prioritySendersSetting != PEOPLE_TYPE_UNSET) {
- if (mIsMessages) {
- diffPolicy.allowMessages(prioritySendersSetting);
-
- } else {
- diffPolicy.allowCalls(prioritySendersSetting);
+ if (prioritySendersSetting != PEOPLE_TYPE_UNSET) {
+ if (mIsMessages) {
+ policy.allowMessages(prioritySendersSetting);
+ } else {
+ policy.allowCalls(prioritySendersSetting);
+ }
+ }
+ if (mIsMessages && priorityConvosSetting != CONVERSATION_SENDERS_UNSET) {
+ policy.allowConversations(priorityConvosSetting);
+ }
+ return policy;
+ });
}
- }
- if (mIsMessages && priorityConvosSetting != CONVERSATION_SENDERS_UNSET) {
- diffPolicy.allowConversations(priorityConvosSetting);
- }
- getMode().setPolicy(diffPolicy.build());
- mBackend.updateMode(getMode());
- }
- };
+ };
}
diff --git a/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java
index d6de9c22d3c..75690519478 100644
--- a/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java
@@ -17,20 +17,17 @@
package com.android.settings.notification.modes;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
-import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
-import android.app.settings.SettingsEnums;
import android.content.Context;
-import android.provider.Settings;
-import android.service.notification.ZenPolicy;
-import android.util.Log;
+
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
+
import com.android.settings.R;
-public class ZenModeRepeatCallersPreferenceController extends AbstractZenModePreferenceController
+class ZenModeRepeatCallersPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
private final int mRepeatCallersThreshold;
@@ -43,14 +40,12 @@ public class ZenModeRepeatCallersPreferenceController extends AbstractZenModePre
}
@Override
- public void updateState(Preference preference) {
- super.updateState(preference);
-
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
TwoStatePreference pref = (TwoStatePreference) preference;
boolean anyCallersCanBypassDnd =
- getMode().getPolicy().getPriorityCategoryCalls() == STATE_ALLOW
- && getMode().getPolicy().getPriorityCallSenders() == PEOPLE_TYPE_ANYONE;
+ zenMode.getPolicy().getPriorityCategoryCalls() == STATE_ALLOW
+ && zenMode.getPolicy().getPriorityCallSenders() == PEOPLE_TYPE_ANYONE;
// if any caller can bypass dnd then repeat callers preference is disabled
if (anyCallersCanBypassDnd) {
pref.setEnabled(false);
@@ -58,21 +53,16 @@ public class ZenModeRepeatCallersPreferenceController extends AbstractZenModePre
} else {
pref.setEnabled(true);
pref.setChecked(
- getMode().getPolicy().getPriorityCategoryRepeatCallers() == STATE_ALLOW);
+ zenMode.getPolicy().getPriorityCategoryRepeatCallers() == STATE_ALLOW);
}
setRepeatCallerSummary(preference);
}
@Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
+ public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
final boolean allowRepeatCallers = (Boolean) newValue;
- ZenPolicy diffPolicy = new ZenPolicy.Builder()
- .allowRepeatCallers(allowRepeatCallers)
- .build();
- getMode().setPolicy(diffPolicy);
- mBackend.updateMode(getMode());
- return true;
+ return savePolicy(policy -> policy.allowRepeatCallers(allowRepeatCallers));
}
private void setRepeatCallerSummary(Preference preference) {
diff --git a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
index e1a7cafa786..41a3d20d0a3 100644
--- a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
+++ b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
@@ -30,7 +30,6 @@ import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MESSAGES;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
-import static android.service.notification.ZenPolicy.STATE_DISALLOW;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_AMBIENT;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_BADGE;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT;
@@ -44,7 +43,6 @@ import android.icu.text.MessageFormat;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
-import android.util.SparseArray;
import com.android.settings.R;
import java.util.ArrayList;
@@ -54,10 +52,10 @@ import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
-public class ZenModeSummaryHelper {
+class ZenModeSummaryHelper {
- private Context mContext;
- private ZenModesBackend mBackend;
+ private final Context mContext;
+ private final ZenModesBackend mBackend;
public ZenModeSummaryHelper(Context context, ZenModesBackend backend) {
mContext = context;
diff --git a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
index ff5c1116223..ca8fe0558ba 100644
--- a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
@@ -39,7 +39,7 @@ import java.util.Map;
* 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 BasePreferenceController {
+class ZenModesListPreferenceController extends BasePreferenceController {
protected static final String KEY = "zen_modes_list";
@Nullable
diff --git a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
index d5ce3af8e89..8dbcb14e83b 100644
--- a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
+++ b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
@@ -160,7 +160,8 @@ class PackageInfoPresenter(
context.activityManager.noteAppRestrictionEnabled(
packageName, uid,
ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, true,
- ActivityManager.RESTRICTION_REASON_USER, "settings", 0)
+ ActivityManager.RESTRICTION_REASON_USER, "settings",
+ ActivityManager.RESTRICTION_SOURCE_USER, 0)
}
context.activityManager.forceStopPackageAsUser(packageName, userId)
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
index 545f7733f41..704637f14c7 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
@@ -43,6 +43,7 @@ import org.robolectric.shadows.ShadowAlarmManager;
import java.time.Clock;
import java.util.List;
+import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
/** Tests of {@link BootBroadcastReceiver}. */
@@ -56,6 +57,7 @@ public final class BootBroadcastReceiverTest {
@Before
public void setUp() {
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
mContext = ApplicationProvider.getApplicationContext();
mPeriodicJobManager = PeriodicJobManager.getInstance(mContext);
mShadowAlarmManager = shadowOf(mContext.getSystemService(AlarmManager.class));
@@ -136,7 +138,7 @@ public final class BootBroadcastReceiverTest {
mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
- TimeUnit.MILLISECONDS.sleep(100);
+ TimeUnit.MILLISECONDS.sleep(1000);
assertThat(mDao.getAllAfter(0)).isEmpty();
assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull();
}
@@ -150,7 +152,7 @@ public final class BootBroadcastReceiverTest {
mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
- TimeUnit.MILLISECONDS.sleep(100);
+ TimeUnit.MILLISECONDS.sleep(1000);
assertThat(mDao.getAllAfter(0).size()).isEqualTo(1);
assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull();
}
@@ -168,7 +170,7 @@ public final class BootBroadcastReceiverTest {
Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR));
- TimeUnit.MILLISECONDS.sleep(100);
+ TimeUnit.MILLISECONDS.sleep(1000);
assertThat(mDao.getAllAfter(0).size()).isEqualTo(1);
assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull();
}
@@ -182,7 +184,7 @@ public final class BootBroadcastReceiverTest {
mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIMEZONE_CHANGED));
- TimeUnit.MILLISECONDS.sleep(100);
+ TimeUnit.MILLISECONDS.sleep(1000);
assertThat(mDao.getAllAfter(0)).isEmpty();
assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull();
}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java
index 7424ae6c1f9..05b48480d41 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java
@@ -19,18 +19,16 @@ package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
-import static android.service.notification.ZenPolicy.STATE_UNSET;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_LIGHTS;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_PEEK;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_STATUS_BAR;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -41,12 +39,10 @@ import android.content.res.Resources;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
-import androidx.preference.TwoStatePreference;
-import com.android.settings.notification.zen.ZenModeVisEffectPreferenceController;
+
import com.android.settings.widget.DisabledCheckBoxPreference;
-import com.android.settingslib.core.lifecycle.Lifecycle;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -162,7 +158,7 @@ public final class ZenModeNotifVisPreferenceControllerTest {
assertThat(captor.getValue().getPolicy().getVisualEffectStatusBar())
.isEqualTo(STATE_DISALLOW);
assertThat(captor.getValue().getPolicy().getVisualEffectNotificationList())
- .isEqualTo(STATE_UNSET);
+ .isEqualTo(STATE_DISALLOW); // Untouched
}
@Test
@@ -211,7 +207,7 @@ public final class ZenModeNotifVisPreferenceControllerTest {
assertThat(captor.getValue().getPolicy().getVisualEffectPeek())
.isEqualTo(STATE_ALLOW);
assertThat(captor.getValue().getPolicy().getVisualEffectNotificationList())
- .isEqualTo(STATE_UNSET);
+ .isEqualTo(STATE_DISALLOW); // Untouched
}
@Test
@@ -236,6 +232,6 @@ public final class ZenModeNotifVisPreferenceControllerTest {
assertThat(captor.getValue().getPolicy().getVisualEffectPeek())
.isEqualTo(STATE_DISALLOW);
assertThat(captor.getValue().getPolicy().getVisualEffectNotificationList())
- .isEqualTo(STATE_UNSET);
+ .isEqualTo(STATE_ALLOW); // Untouched
}
}
\ No newline at end of file
diff --git a/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowUtils.java b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowUtils.java
index 5f8c434fc9f..ed03bcc8413 100644
--- a/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowUtils.java
+++ b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowUtils.java
@@ -50,6 +50,7 @@ public class ShadowUtils {
private static ArraySet sResultLinks = new ArraySet<>();
private static boolean sIsBatteryPresent;
private static boolean sIsMultipleBiometricsSupported;
+ private static boolean sIsPrivateProfile;
@Implementation
protected static int enforceSameOwner(Context context, int userId) {
@@ -82,6 +83,7 @@ public class ShadowUtils {
sResultLinks = new ArraySet<>();
sIsBatteryPresent = true;
sIsMultipleBiometricsSupported = false;
+ sIsPrivateProfile = false;
}
public static void setIsDemoUser(boolean isDemoUser) {
@@ -188,4 +190,13 @@ public class ShadowUtils {
public static void setIsMultipleBiometricsSupported(boolean isMultipleBiometricsSupported) {
sIsMultipleBiometricsSupported = isMultipleBiometricsSupported;
}
+
+ @Implementation
+ protected static boolean isPrivateProfile(int userId, Context context) {
+ return sIsPrivateProfile;
+ }
+
+ public static void setIsPrivateProfile(boolean isPrivateProfile) {
+ sIsPrivateProfile = isPrivateProfile;
+ }
}
diff --git a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt
index ebdc504c1a8..652afa06143 100644
--- a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt
+++ b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt
@@ -118,7 +118,12 @@ class Injector(step: FingerprintNavigationStep.UiStep) {
var rfpsIconTouchViewModel = RFPSIconTouchViewModel()
var rfpsViewModel =
- RFPSViewModel(fingerprintEnrollEnrollingViewModel, navigationViewModel, orientationInteractor)
+ RFPSViewModel(
+ fingerprintEnrollEnrollingViewModel,
+ navigationViewModel,
+ orientationInteractor,
+ interactor,
+ )
val fingerprintEnrollConfirmationViewModel =
FingerprintEnrollConfirmationViewModel(navigationViewModel, interactor)
@@ -151,7 +156,8 @@ class Injector(step: FingerprintNavigationStep.UiStep) {
BackgroundViewModel::class.java -> backgroundViewModel
RFPSIconTouchViewModel::class.java -> rfpsIconTouchViewModel
FingerprintEnrollEnrollingViewModel::class.java -> fingerprintEnrollEnrollingViewModel
- FingerprintEnrollConfirmationViewModel::class.java -> fingerprintEnrollConfirmationViewModel
+ FingerprintEnrollConfirmationViewModel::class.java ->
+ fingerprintEnrollConfirmationViewModel
else -> null
}
as T
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceControllerTest.kt
index 2f678464935..1da8fd9d7f4 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceControllerTest.kt
@@ -19,18 +19,16 @@ package com.android.settings.network.telephony
import android.content.Context
import android.telephony.SubscriptionInfo
import android.telephony.TelephonyManager
-import android.telephony.euicc.EuiccManager
import androidx.fragment.app.Fragment
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.internal.telephony.PhoneConstants
+import com.android.settings.R
import com.android.settings.core.BasePreferenceController
import com.android.settings.network.SubscriptionInfoListViewModel
import com.android.settings.network.SubscriptionUtil
-import com.android.settingslib.CustomDialogPreferenceCompat
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import org.junit.After
@@ -62,6 +60,8 @@ class MobileNetworkImeiPreferenceControllerTest {
on { currentPhoneType } doReturn TelephonyManager.PHONE_TYPE_GSM
on { imei } doReturn mockImei
on { meid } doReturn mockImei
+ on { primaryImei } doReturn mockImei
+ on { activeModemCount } doReturn 2
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
@@ -90,7 +90,7 @@ class MobileNetworkImeiPreferenceControllerTest {
}
@Test
- fun refreshData_getPhoneNumber_preferenceSummaryIsExpected() = runBlocking {
+ fun refreshData_getImei_preferenceSummaryIsExpected() = runBlocking {
whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true)
whenever(SubscriptionUtil.getActiveSubscriptions(any())).thenReturn(
listOf(
@@ -110,6 +110,50 @@ class MobileNetworkImeiPreferenceControllerTest {
assertThat(preference.summary).isEqualTo(mockImei)
}
+ @Test
+ fun refreshData_getImeiTitle_showImei() = runBlocking {
+ whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true)
+ whenever(SubscriptionUtil.getActiveSubscriptions(any())).thenReturn(
+ listOf(
+ SUB_INFO_1,
+ SUB_INFO_2
+ )
+ )
+ var mockSubId = 2
+ controller.init(mockFragment, mockSubId)
+ mockImei = "test imei"
+ mockTelephonyManager.stub {
+ on { imei } doReturn mockImei
+ on { primaryImei } doReturn ""
+ }
+
+ controller.refreshData(SUB_INFO_2)
+
+ assertThat(preference.title).isEqualTo(context.getString(R.string.status_imei))
+ }
+
+ @Test
+ fun refreshData_getPrimaryImeiTitle_showPrimaryImei() = runBlocking {
+ whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true)
+ whenever(SubscriptionUtil.getActiveSubscriptions(any())).thenReturn(
+ listOf(
+ SUB_INFO_1,
+ SUB_INFO_2
+ )
+ )
+ var mockSubId = 2
+ controller.init(mockFragment, mockSubId)
+ mockImei = "test imei"
+ mockTelephonyManager.stub {
+ on { imei } doReturn mockImei
+ on { primaryImei } doReturn mockImei
+ }
+
+ controller.refreshData(SUB_INFO_2)
+
+ assertThat(preference.title).isEqualTo(context.getString(R.string.imei_primary))
+ }
+
@Test
fun getAvailabilityStatus_notSimHardwareVisible() {
whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false)