Merge "Enable use_resource_processor for all sysui deps" into main
diff --git a/res/drawable/ic_battery_full.xml b/res/drawable/ic_battery_full.xml
new file mode 100644
index 0000000..8b1321f
--- /dev/null
+++ b/res/drawable/ic_battery_full.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M320,880Q303,880 291.5,868.5Q280,857 280,840L280,200Q280,183 291.5,171.5Q303,160 320,160L400,160L400,80L560,80L560,160L640,160Q657,160 668.5,171.5Q680,183 680,200L680,840Q680,857 668.5,868.5Q657,880 640,880L320,880Z"/>
+</vector>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f8eaaff..646a3ee 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2119,6 +2119,8 @@
<string name="internet_source_mobile_data">Mobile data</string>
<!-- Ethernet summary in Internet source preference [CHAR LIMIT=NONE]-->
<string name="internet_source_ethernet">Ethernet</string>
+ <!-- Hotspot device details battery charging summary [CHAR LIMIT=NONE]-->
+ <string name="hotspot_battery_charging_summary"><xliff:g id="battery_percentage" example="80%">%1$s</xliff:g> \u2011 Charging</string>
<!-- Hotspot device details preference category title in Network details [CHAR LIMIT=NONE]-->
<string name="hotspot_connection_category">Hotspot connection</string>
<!-- Connection strength preference in Hotspot connection preference category [CHAR LIMIT=NONE]-->
@@ -6908,14 +6910,6 @@
For example, they may have alerts just once or every 2 or 15 minutes. [CHAR LIMIT=30] -->
<string name="repeat_title">Repeat</string>
- <!-- Call Manager enable settings title. [CHAR LIMIT=50] -->
- <string name="call_manager_enable_title">Enable Call Manager</string>
- <!-- Call Manager enable settings summary. [CHAR LIMIT=80] -->
- <string name="call_manager_enable_summary">Allow this service to manage how your calls are made.</string>
- <!-- Call Manager settings title. [CHAR LIMIT=50] -->
- <string name="call_manager_title">Call Manager</string>
- <!-- Call Manager settings summary. [CHAR LIMIT=50] -->
- <string name="call_manager_summary"><xliff:g id="app">%1$s</xliff:g></string>
<!-- Cell Broadcast settings title. [CHAR LIMIT=50] -->
<string name="cell_broadcast_settings">Wireless emergency alerts</string>
<!-- Network operators settings title. [CHAR LIMIT=50] -->
diff --git a/res/xml/wifi_network_details_fragment2.xml b/res/xml/wifi_network_details_fragment2.xml
index e3464c2..0062474 100644
--- a/res/xml/wifi_network_details_fragment2.xml
+++ b/res/xml/wifi_network_details_fragment2.xml
@@ -52,6 +52,7 @@
settings:enableCopying="true"/>
<Preference
android:key="hotspot_device_details_battery"
+ android:icon="@drawable/ic_battery_full"
android:title="@string/power_usage_summary_title"
android:selectable="false"
settings:enableCopying="true"/>
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java
index d65e057..155ced5 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java
@@ -30,10 +30,10 @@
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.android.settings.R;
-import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
/** Fingerprint error dialog, will be shown when an error occurs during fingerprint enrollment. */
@@ -95,7 +95,7 @@
return dialog;
}
- public static void showErrorDialog(BiometricEnrollBase host, int errMsgId, boolean isSetup) {
+ public static void showErrorDialog(FragmentActivity host, int errMsgId, boolean isSetup) {
if (host.isFinishing()) {
return;
}
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 d43aeba..31afcb7 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
@@ -20,11 +20,13 @@
import android.app.Activity
import android.content.Intent
import android.content.res.ColorStateList
+import android.content.res.Configuration
import android.graphics.Color
import android.hardware.fingerprint.FingerprintManager
import android.os.Bundle
import android.provider.Settings
import android.util.Log
+import android.view.accessibility.AccessibilityManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
@@ -44,17 +46,21 @@
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Finish
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FoldStateViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.LaunchConfirmDeviceCredential
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.OrientationStateViewModel
import com.android.settings.password.ChooseLockGeneric
import com.android.settings.password.ChooseLockSettingsHelper
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
@@ -72,6 +78,10 @@
class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
+ private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
+ private lateinit var accessibilityViewModel: AccessibilityViewModel
+ private lateinit var foldStateViewModel: FoldStateViewModel
+ private lateinit var orientationStateViewModel: OrientationStateViewModel
private val coroutineDispatcher = Dispatchers.Default
/** Result listener for ChooseLock activity flow. */
@@ -94,6 +104,11 @@
super.onAttachedToWindow()
}
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ foldStateViewModel.onConfigurationChange(newConfig)
+ }
+
@ColorInt
private fun getBackgroundColor(): Int {
val stateList: ColorStateList? =
@@ -178,16 +193,53 @@
)
)[FingerprintEnrollNavigationViewModel::class.java]
+ // Initialize FoldStateViewModel
+ foldStateViewModel =
+ ViewModelProvider(this, FoldStateViewModel.FoldStateViewModelFactory(context))[
+ FoldStateViewModel::class.java]
+ foldStateViewModel.onConfigurationChange(resources.configuration)
+
// Initialize FingerprintViewModel
- ViewModelProvider(
- this,
- FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor, backgroundDispatcher)
- )[FingerprintEnrollViewModel::class.java]
+ fingerprintEnrollViewModel =
+ ViewModelProvider(
+ this,
+ FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
+ interactor,
+ backgroundDispatcher
+ )
+ )[FingerprintEnrollViewModel::class.java]
// Initialize scroll view model
ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
FingerprintScrollViewModel::class.java]
+ // Initialize AccessibilityViewModel
+ accessibilityViewModel =
+ ViewModelProvider(
+ this,
+ AccessibilityViewModel.AccessibilityViewModelFactory(
+ getSystemService(AccessibilityManager::class.java)!!
+ )
+ )[AccessibilityViewModel::class.java]
+
+ // Initialize OrientationViewModel
+ orientationStateViewModel =
+ ViewModelProvider(this, OrientationStateViewModel.OrientationViewModelFactory(context))[
+ OrientationStateViewModel::class.java]
+
+ // Initialize FingerprintEnrollFindSensorViewModel
+ ViewModelProvider(
+ this,
+ FingerprintEnrollFindSensorViewModel.FingerprintEnrollFindSensorViewModelFactory(
+ navigationViewModel,
+ fingerprintEnrollViewModel,
+ gatekeeperViewModel,
+ accessibilityViewModel,
+ foldStateViewModel,
+ orientationStateViewModel
+ )
+ )[FingerprintEnrollFindSensorViewModel::class.java]
+
lifecycleScope.launch {
navigationViewModel.navigationViewModel.filterNotNull().collect {
Log.d(TAG, "navigationStep $it")
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
index 3f615ce..0140d57 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
@@ -19,10 +19,11 @@
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
+import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
/** A fragment that is responsible for enrolling a users fingerprint. */
-class FingerprintEnrollEnrollingV2Fragment : Fragment() {
+class FingerprintEnrollEnrollingV2Fragment : Fragment(R.layout.fingerprint_enroll_enrolling) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
index beb84e9..dcdcccf 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
@@ -17,26 +17,197 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import com.airbnb.lottie.LottieAnimationView
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
+import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
+import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.google.android.setupcompat.template.FooterBarMixin
+import com.google.android.setupcompat.template.FooterButton
+import com.google.android.setupdesign.GlifLayout
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+private const val TAG = "FingerprintEnrollFindSensorV2Fragment"
/**
* A fragment that is used to educate the user about the fingerprint sensor on this device.
*
+ * If the sensor is not a udfps sensor, this fragment listens to fingerprint enrollment for
+ * proceeding to the enroll enrolling.
+ *
* The main goals of this page are
* 1. Inform the user where the fingerprint sensor is on their device
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
* will work.
*/
-class FingerprintEnrollFindSensorV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_find_sensor) {
+class FingerprintEnrollFindSensorV2Fragment : Fragment() {
+ // This is only for non-udfps or non-sfps sensor. For udfps and sfps, we show lottie.
+ private var animation: FingerprintFindSensorAnimation? = null
+
+ private var contentLayoutId: Int = -1
+ private lateinit var viewModel: FingerprintEnrollFindSensorViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- if (savedInstanceState == null) {
- val navigationViewModel =
- ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
+ viewModel =
+ ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::class.java]
+ lifecycleScope.launch {
+ viewModel.sensorType.collect {
+ contentLayoutId =
+ when (it) {
+ FingerprintSensorType.UDFPS_OPTICAL,
+ FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
+ FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
+ else -> R.layout.fingerprint_v2_enroll_find_sensor
+ }
+ }
}
}
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(contentLayoutId, container, false).also { it ->
+ val view = it!! as GlifLayout
+
+ // Set up header and description
+ lifecycleScope.launch { viewModel.sensorType.collect { setTexts(it, view) } }
+
+ // Set up footer bar
+ val footerBarMixin = view.getMixin(FooterBarMixin::class.java)
+ setupSecondaryButton(footerBarMixin)
+ lifecycleScope.launch {
+ viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
+ }
+
+ // Set up lottie or animation
+ lifecycleScope.launch {
+ viewModel.showSfpsLottie.collect { (isFolded, rotation) ->
+ setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation))
+ }
+ }
+ lifecycleScope.launch {
+ viewModel.showUdfpsLottie.collect { isAccessibilityEnabled ->
+ val lottieAnimation =
+ if (isAccessibilityEnabled) R.raw.udfps_edu_a11y_lottie else R.raw.udfps_edu_lottie
+ setupLottie(view, lottieAnimation) { viewModel.proceedToEnrolling() }
+ }
+ }
+ lifecycleScope.launch {
+ viewModel.showRfpsAnimation.collect {
+ animation = view.findViewById(R.id.fingerprint_sensor_location_animation)
+ animation!!.startAnimation()
+ }
+ }
+
+ lifecycleScope.launch {
+ viewModel.showErrorDialog.collect { (errMsgId, isSetup) ->
+ // TODO: Covert error dialog kotlin as well
+ FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup)
+ }
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ animation?.stopAnimation()
+ super.onDestroy()
+ }
+
+ private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) {
+ footerBarMixin.secondaryButton =
+ FooterButton.Builder(requireActivity())
+ .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
+ .setListener {
+ run {
+ // TODO: Show the dialog for suw
+ Log.d(TAG, "onSkipClicked")
+ // TODO: Finish activity in the root activity instead.
+ requireActivity().finish()
+ }
+ }
+ .setButtonType(FooterButton.ButtonType.SKIP)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
+ .build()
+ }
+
+ private fun setupPrimaryButton(footerBarMixin: FooterBarMixin) {
+ footerBarMixin.primaryButton =
+ FooterButton.Builder(requireActivity())
+ .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
+ .setListener {
+ run {
+ Log.d(TAG, "onStartButtonClick")
+ viewModel.proceedToEnrolling()
+ }
+ }
+ .setButtonType(FooterButton.ButtonType.NEXT)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+ .build()
+ }
+
+ private fun setupLottie(
+ view: View,
+ lottieAnimation: Int,
+ lottieClickListener: View.OnClickListener? = null
+ ) {
+ val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie)
+ illustrationLottie?.setAnimation(lottieAnimation)
+ illustrationLottie?.playAnimation()
+ illustrationLottie?.setOnClickListener(lottieClickListener)
+ illustrationLottie?.visibility = View.VISIBLE
+ }
+
+ private fun setTexts(sensorType: FingerprintSensorType, view: GlifLayout) {
+ when (sensorType) {
+ FingerprintSensorType.UDFPS_OPTICAL,
+ FingerprintSensorType.UDFPS_ULTRASONIC -> {
+ view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title)
+ view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message)
+ }
+ FingerprintSensorType.POWER_BUTTON -> {
+ view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
+ view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message)
+ }
+ else -> {
+ view.setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title)
+ view.setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message)
+ }
+ }
+ }
+
+ private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int {
+ val animation: Int
+ when (rotation) {
+ Surface.ROTATION_90 ->
+ animation =
+ (if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_left
+ else R.raw.fingerprint_edu_lottie_portrait_top_left)
+ Surface.ROTATION_180 ->
+ animation =
+ (if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_left
+ else R.raw.fingerprint_edu_lottie_landscape_bottom_left)
+ Surface.ROTATION_270 ->
+ animation =
+ (if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_right
+ else R.raw.fingerprint_edu_lottie_portrait_bottom_right)
+ else ->
+ animation =
+ (if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_right
+ else R.raw.fingerprint_edu_lottie_landscape_top_right)
+ }
+ return animation
+ }
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
index 03c7a5f..dbf6d12 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
@@ -180,7 +180,10 @@
scrollView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
// Next button responsible for starting the next fragment.
val onNextButtonClick: View.OnClickListener =
- View.OnClickListener { Log.d(TAG, "OnNextClicked") }
+ View.OnClickListener {
+ Log.d(TAG, "OnNextClicked")
+ navigationViewModel.nextStep()
+ }
val layout: GlifLayout = requireActivity().requireViewById(R.id.setup_wizard_layout)
footerBarMixin = layout.getMixin(FooterBarMixin::class.java)
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/AccessibilityViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/AccessibilityViewModel.kt
new file mode 100644
index 0000000..a86ad5d
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/AccessibilityViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
+
+import android.view.accessibility.AccessibilityManager
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Represents all of the information on accessibility state. */
+class AccessibilityViewModel(accessibilityManager: AccessibilityManager) : ViewModel() {
+ /** A flow that contains whether or not accessibility is enabled */
+ val isAccessibilityEnabled: Flow<Boolean> =
+ callbackFlow {
+ val listener =
+ AccessibilityManager.AccessibilityStateChangeListener { enabled -> trySend(enabled) }
+ accessibilityManager.addAccessibilityStateChangeListener(listener)
+
+ // This clause will be called when no one is listening to the flow
+ awaitClose { accessibilityManager.removeAccessibilityStateChangeListener(listener) }
+ }
+ .stateIn(
+ viewModelScope, // This is going to tied to the view model scope
+ SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener
+ false
+ )
+
+ class AccessibilityViewModelFactory(private val accessibilityManager: AccessibilityManager) :
+ ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(
+ modelClass: Class<T>,
+ ): T {
+ return AccessibilityViewModel(accessibilityManager) as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
new file mode 100644
index 0000000..dbf6b33
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
+
+import android.hardware.fingerprint.FingerprintManager
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+/** Models the UI state for [FingerprintEnrollFindSensorV2Fragment]. */
+class FingerprintEnrollFindSensorViewModel(
+ private val navigationViewModel: FingerprintEnrollNavigationViewModel,
+ private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+ private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+ accessibilityViewModel: AccessibilityViewModel,
+ foldStateViewModel: FoldStateViewModel,
+ orientationStateViewModel: OrientationStateViewModel
+) : ViewModel() {
+ /** Represents the stream of sensor type. */
+ val sensorType: Flow<FingerprintSensorType> =
+ fingerprintEnrollViewModel.sensorType.filterWhenEducationIsShown()
+ private val _isUdfps: Flow<Boolean> =
+ sensorType.map {
+ it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC
+ }
+ private val _isSfps: Flow<Boolean> = sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
+ private val _isRearSfps: Flow<Boolean> =
+ combineTransform(_isSfps, _isUdfps) { v1, v2 -> !v1 && !v2 }
+
+ /** Represents the stream of showing primary button. */
+ val showPrimaryButton: Flow<Boolean> = _isUdfps.transform { if (it) emit(true) }
+
+ /** Represents the stream of showing sfps lottie, Pair(isFolded, rotation). */
+ val showSfpsLottie: Flow<Pair<Boolean, Int>> =
+ combineTransform(
+ _isSfps,
+ foldStateViewModel.isFolded,
+ orientationStateViewModel.rotation,
+ ) { isSfps, isFolded, rotation ->
+ if (isSfps) emit(Pair(isFolded, rotation))
+ }
+
+ /** Represents the stream of showing udfps lottie. */
+ val showUdfpsLottie: Flow<Boolean> =
+ combineTransform(
+ _isUdfps,
+ accessibilityViewModel.isAccessibilityEnabled,
+ ) { isUdfps, isAccessibilityEnabled ->
+ if (isUdfps) emit(isAccessibilityEnabled)
+ }
+
+ /** Represents the stream of showing rfps animation. */
+ val showRfpsAnimation: Flow<Boolean> = _isRearSfps.transform { if (it) emit(true) }
+
+ private val _showErrorDialog: MutableStateFlow<Pair<Int, Boolean>?> = MutableStateFlow(null)
+ /** Represents the stream of showing error dialog. */
+ val showErrorDialog = _showErrorDialog.filterNotNull()
+
+ init {
+ // Start or end enroll flow
+ viewModelScope.launch {
+ combine(
+ fingerprintEnrollViewModel.sensorType,
+ gatekeeperViewModel.hasValidGatekeeperInfo,
+ gatekeeperViewModel.gatekeeperInfo,
+ navigationViewModel.navigationViewModel
+ ) { sensorType, hasValidGatekeeperInfo, gatekeeperInfo, navigationViewModel ->
+ val shouldStartEnroll =
+ navigationViewModel.currStep == Education &&
+ sensorType != FingerprintSensorType.UDFPS_OPTICAL &&
+ sensorType != FingerprintSensorType.UDFPS_ULTRASONIC &&
+ hasValidGatekeeperInfo
+ if (shouldStartEnroll) (gatekeeperInfo as GatekeeperInfo.GatekeeperPasswordInfo).token
+ else null
+ }
+ .collect { token ->
+ if (token != null) {
+ fingerprintEnrollViewModel.startEnroll(token, EnrollReason.FindSensor)
+ } else {
+ fingerprintEnrollViewModel.stopEnroll()
+ }
+ }
+ }
+
+ // Enroll progress flow
+ viewModelScope.launch {
+ combine(
+ navigationViewModel.enrollType,
+ fingerprintEnrollViewModel.enrollFlow.filterNotNull()
+ ) { enrollType, enrollFlow ->
+ Pair(enrollType, enrollFlow)
+ }
+ .collect { (enrollType, enrollFlow) ->
+ when (enrollFlow) {
+ // TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding to
+ // Enrolling page. Otherwise Enrolling page will receive the EnrollError.
+ is FingerEnrollStateViewModel.EnrollProgress -> proceedToEnrolling()
+ is FingerEnrollStateViewModel.EnrollError -> {
+ val errMsgId = enrollFlow.errMsgId
+ if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
+ proceedToEnrolling()
+ } else {
+ _showErrorDialog.update { Pair(errMsgId, enrollType == SetupWizard) }
+ }
+ }
+ is FingerEnrollStateViewModel.EnrollHelp -> {}
+ }
+ }
+ }
+ }
+
+ /** Proceed to EnrollEnrolling page. */
+ fun proceedToEnrolling() {
+ navigationViewModel.nextStep()
+ }
+
+ // TODO: If we decide to remove previous fragment from activity, then we don't need to check
+ // whether education is shown for the flows that are subscribed by
+ // [FingerprintEnrollFindSensorV2Fragment].
+ private fun <T> Flow<T>.filterWhenEducationIsShown() =
+ combineTransform(navigationViewModel.navigationViewModel) { value, navigationViewModel ->
+ if (navigationViewModel.currStep == Education) {
+ emit(value)
+ }
+ }
+
+ class FingerprintEnrollFindSensorViewModelFactory(
+ private val navigationViewModel: FingerprintEnrollNavigationViewModel,
+ private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+ private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+ private val accessibilityViewModel: AccessibilityViewModel,
+ private val foldStateViewModel: FoldStateViewModel,
+ private val orientationStateViewModel: OrientationStateViewModel
+ ) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ return FingerprintEnrollFindSensorViewModel(
+ navigationViewModel,
+ fingerprintEnrollViewModel,
+ gatekeeperViewModel,
+ accessibilityViewModel,
+ foldStateViewModel,
+ orientationStateViewModel
+ )
+ as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FoldStateViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FoldStateViewModel.kt
new file mode 100644
index 0000000..a4c7ff2
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FoldStateViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
+
+import android.content.Context
+import android.content.res.Configuration
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
+import com.android.systemui.unfold.updates.FoldProvider
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+
+/** Represents all of the information on fold state. */
+class FoldStateViewModel(context: Context) : ViewModel() {
+
+ private val screenSizeFoldProvider = ScreenSizeFoldProvider(context)
+
+ /** A flow that contains the fold state info */
+ val isFolded: Flow<Boolean> = callbackFlow {
+ val foldStateListener =
+ object : FoldProvider.FoldCallback {
+ override fun onFoldUpdated(isFolded: Boolean) {
+ trySend(isFolded)
+ }
+ }
+ screenSizeFoldProvider.registerCallback(foldStateListener, context.mainExecutor)
+ awaitClose { screenSizeFoldProvider.unregisterCallback(foldStateListener) }
+ }
+
+ fun onConfigurationChange(newConfig: Configuration) {
+ screenSizeFoldProvider.onConfigurationChange(newConfig)
+ }
+
+ class FoldStateViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(
+ modelClass: Class<T>,
+ ): T {
+ return FoldStateViewModel(context) as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/OrientationStateViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/OrientationStateViewModel.kt
new file mode 100644
index 0000000..2e5f734
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/OrientationStateViewModel.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
+
+import android.content.Context
+import android.view.OrientationEventListener
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.internal.R
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Represents all of the information on orientation state and rotation state. */
+class OrientationStateViewModel(private val context: Context) : ViewModel() {
+
+ /** A flow that contains the orientation info */
+ val orientation: Flow<Int> = callbackFlow {
+ val orientationEventListener =
+ object : OrientationEventListener(context) {
+ override fun onOrientationChanged(orientation: Int) {
+ trySend(orientation)
+ }
+ }
+ orientationEventListener.enable()
+ awaitClose { orientationEventListener.disable() }
+ }
+
+ /** A flow that contains the rotation info */
+ val rotation: Flow<Int> =
+ callbackFlow {
+ val orientationEventListener =
+ object : OrientationEventListener(context) {
+ override fun onOrientationChanged(orientation: Int) {
+ trySend(getRotationFromDefault(context.display!!.rotation))
+ }
+ }
+ orientationEventListener.enable()
+ awaitClose { orientationEventListener.disable() }
+ }
+ .stateIn(
+ viewModelScope, // This is going to tied to the view model scope
+ SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener
+ context.display!!.rotation
+ )
+
+ fun getRotationFromDefault(rotation: Int): Int {
+ val isReverseDefaultRotation =
+ context.resources.getBoolean(R.bool.config_reverseDefaultRotation)
+ return if (isReverseDefaultRotation) {
+ (rotation + 1) % 4
+ } else {
+ rotation
+ }
+ }
+
+ class OrientationViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(
+ modelClass: Class<T>,
+ ): T {
+ return OrientationStateViewModel(context) as T
+ }
+ }
+}
diff --git a/src/com/android/settings/datausage/BillingCyclePreference.kt b/src/com/android/settings/datausage/BillingCyclePreference.kt
index 05066be..619f7e9 100644
--- a/src/com/android/settings/datausage/BillingCyclePreference.kt
+++ b/src/com/android/settings/datausage/BillingCyclePreference.kt
@@ -49,6 +49,7 @@
this.subId = subId
summary = null
updateEnabled()
+ intent = intent
}
override fun onAttached() {
diff --git a/src/com/android/settings/datausage/DataUsageBaseFragment.java b/src/com/android/settings/datausage/DataUsageBaseFragment.java
index eee3228..5ddbab8 100644
--- a/src/com/android/settings/datausage/DataUsageBaseFragment.java
+++ b/src/com/android/settings/datausage/DataUsageBaseFragment.java
@@ -15,23 +15,13 @@
package com.android.settings.datausage;
import android.content.Context;
-import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
import android.os.Bundle;
-import android.os.INetworkManagementService;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.util.Log;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.NetworkPolicyEditor;
public abstract class DataUsageBaseFragment extends DashboardFragment {
- private static final String TAG = "DataUsageBase";
- private static final String ETHERNET = "ethernet";
protected final TemplatePreference.NetworkServices services =
new TemplatePreference.NetworkServices();
@@ -41,16 +31,10 @@
super.onCreate(icicle);
Context context = getContext();
- services.mNetworkService = INetworkManagementService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
services.mPolicyManager = (NetworkPolicyManager) context
.getSystemService(Context.NETWORK_POLICY_SERVICE);
services.mPolicyEditor = new NetworkPolicyEditor(services.mPolicyManager);
-
- services.mTelephonyManager = context.getSystemService(TelephonyManager.class);
- services.mSubscriptionManager = SubscriptionManager.from(context);
- services.mUserManager = UserManager.get(context);
}
@Override
@@ -58,33 +42,4 @@
super.onResume();
services.mPolicyEditor.read();
}
-
- protected boolean isAdmin() {
- return services.mUserManager.isAdminUser();
- }
-
- protected boolean isMobileDataAvailable(int subId) {
- return services.mSubscriptionManager.getActiveSubscriptionInfo(subId) != null;
- }
-
- protected boolean isNetworkPolicyModifiable(NetworkPolicy policy, int subId) {
- return policy != null && isBandwidthControlEnabled() && services.mUserManager.isAdminUser()
- && isDataEnabled(subId);
- }
-
- private boolean isDataEnabled(int subId) {
- if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- return true;
- }
- return services.mTelephonyManager.getDataEnabled(subId);
- }
-
- protected boolean isBandwidthControlEnabled() {
- try {
- return services.mNetworkService.isBandwidthControlEnabled();
- } catch (RemoteException e) {
- Log.w(TAG, "problem talking with INetworkManagementService: ", e);
- return false;
- }
- }
}
diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java
index 39287c19..15a5603 100644
--- a/src/com/android/settings/datausage/DataUsageList.java
+++ b/src/com/android/settings/datausage/DataUsageList.java
@@ -32,7 +32,6 @@
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ImageView;
import android.widget.Spinner;
import androidx.annotation.NonNull;
@@ -46,6 +45,7 @@
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.datausage.CycleAdapter.SpinnerInterface;
+import com.android.settings.datausage.lib.BillingCycleRepository;
import com.android.settings.network.MobileDataEnabledListener;
import com.android.settings.network.MobileNetworkRepository;
import com.android.settings.widget.LoadingViewController;
@@ -108,6 +108,7 @@
private MobileNetworkRepository mMobileNetworkRepository;
private SubscriptionInfoEntity mSubscriptionInfoEntity;
private DataUsageListAppsController mDataUsageListAppsController;
+ private BillingCycleRepository mBillingCycleRepository;
@Override
public int getMetricsCategory() {
@@ -125,7 +126,8 @@
}
final Activity activity = getActivity();
- if (!isBandwidthControlEnabled()) {
+ mBillingCycleRepository = createBillingCycleRepository();
+ if (!mBillingCycleRepository.isBandwidthControlEnabled()) {
Log.w(TAG, "No bandwidth control; leaving");
activity.finish();
return;
@@ -146,6 +148,12 @@
mDataUsageListAppsController.init(mTemplate);
}
+ @VisibleForTesting
+ @NonNull
+ BillingCycleRepository createBillingCycleRepository() {
+ return new BillingCycleRepository(requireContext());
+ }
+
@Override
public void onViewCreated(@NonNull View v, Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
@@ -286,10 +294,9 @@
final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate);
final View configureButton = mHeader.findViewById(R.id.filter_settings);
//SUB SELECT
- if (isNetworkPolicyModifiable(policy, mSubId) && isMobileDataAvailable(mSubId)) {
+ if (policy != null && isMobileDataAvailable()) {
mChart.setNetworkPolicy(policy);
configureButton.setVisibility(View.VISIBLE);
- ((ImageView) configureButton).setColorFilter(android.R.color.white);
} else {
// controls are disabled; don't bind warning/limit sweeps
mChart.setNetworkPolicy(null);
@@ -304,6 +311,12 @@
updateSelectedCycle();
}
+ private boolean isMobileDataAvailable() {
+ return mBillingCycleRepository.isModifiable(mSubId)
+ && SubscriptionManager.from(requireContext())
+ .getActiveSubscriptionInfo(mSubId) != null;
+ }
+
/**
* Updates the chart and detail data when initial loaded or selected cycle changed.
*/
diff --git a/src/com/android/settings/datausage/DataUsageListAppsController.kt b/src/com/android/settings/datausage/DataUsageListAppsController.kt
index cc55e1a..c324407 100644
--- a/src/com/android/settings/datausage/DataUsageListAppsController.kt
+++ b/src/com/android/settings/datausage/DataUsageListAppsController.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.net.NetworkTemplate
import android.os.Bundle
+import androidx.annotation.OpenForTesting
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
@@ -37,7 +38,8 @@
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-class DataUsageListAppsController(context: Context, preferenceKey: String) :
+@OpenForTesting
+open class DataUsageListAppsController(context: Context, preferenceKey: String) :
BasePreferenceController(context, preferenceKey) {
private val uidDetailProvider = UidDetailProvider(context)
@@ -48,7 +50,7 @@
private var cycleData: List<NetworkCycleChartData>? = null
- fun init(template: NetworkTemplate) {
+ open fun init(template: NetworkTemplate) {
this.template = template
repository = AppDataUsageRepository(
context = mContext,
diff --git a/src/com/android/settings/datausage/DataUsageSummary.java b/src/com/android/settings/datausage/DataUsageSummary.java
index 6966611..4f876ab 100644
--- a/src/com/android/settings/datausage/DataUsageSummary.java
+++ b/src/com/android/settings/datausage/DataUsageSummary.java
@@ -103,7 +103,7 @@
mDefaultTemplate = DataUsageUtils.getDefaultTemplate(context, defaultSubId);
mSummaryPreference = findPreference(KEY_STATUS_HEADER);
- if (!hasMobileData || !isAdmin()) {
+ if (!hasMobileData || !UserManager.get(context).isAdminUser()) {
removePreference(KEY_RESTRICT_BACKGROUND);
}
boolean hasWifiRadio = DataUsageUtils.hasWifiRadio(context);
diff --git a/src/com/android/settings/datausage/TemplatePreference.java b/src/com/android/settings/datausage/TemplatePreference.java
index 6182229..8e780db 100644
--- a/src/com/android/settings/datausage/TemplatePreference.java
+++ b/src/com/android/settings/datausage/TemplatePreference.java
@@ -16,10 +16,6 @@
import android.net.NetworkPolicyManager;
import android.net.NetworkTemplate;
-import android.os.INetworkManagementService;
-import android.os.UserManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
import com.android.settingslib.NetworkPolicyEditor;
@@ -29,11 +25,7 @@
void setTemplate(NetworkTemplate template, int subId);
class NetworkServices {
- INetworkManagementService mNetworkService;
NetworkPolicyManager mPolicyManager;
- TelephonyManager mTelephonyManager;
- SubscriptionManager mSubscriptionManager;
- UserManager mUserManager;
NetworkPolicyEditor mPolicyEditor;
}
diff --git a/src/com/android/settings/datausage/lib/BillingCycleRepository.kt b/src/com/android/settings/datausage/lib/BillingCycleRepository.kt
index 779ae4a..bd6aa27 100644
--- a/src/com/android/settings/datausage/lib/BillingCycleRepository.kt
+++ b/src/com/android/settings/datausage/lib/BillingCycleRepository.kt
@@ -21,9 +21,11 @@
import android.os.ServiceManager
import android.telephony.TelephonyManager
import android.util.Log
+import androidx.annotation.OpenForTesting
import com.android.settingslib.spaprivileged.framework.common.userManager
-class BillingCycleRepository(
+@OpenForTesting
+open class BillingCycleRepository @JvmOverloads constructor(
context: Context,
private val networkService: INetworkManagementService =
INetworkManagementService.Stub.asInterface(
@@ -36,7 +38,7 @@
fun isModifiable(subId: Int): Boolean =
isBandwidthControlEnabled() && userManager.isAdminUser && isDataEnabled(subId)
- fun isBandwidthControlEnabled(): Boolean = try {
+ open fun isBandwidthControlEnabled(): Boolean = try {
networkService.isBandwidthControlEnabled
} catch (e: Exception) {
Log.w(TAG, "problem talking with INetworkManagementService: ", e)
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
index fd0f866..4a336eb 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -120,7 +120,7 @@
DatabaseUtils.sendBatteryUsageSlotData(context,
ConvertUtils.convertToBatteryUsageSlotList(batteryDiffDataMap));
if (batteryDiffDataMap.values().stream().anyMatch(data ->
- (!data.getAppDiffEntryList().isEmpty()
+ data != null && (!data.getAppDiffEntryList().isEmpty()
|| !data.getSystemDiffEntryList().isEmpty()))) {
FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider()
.detectSettingsAnomaly(context, /* displayDrain= */ 0);
diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
index 90d96c6..f2c3325 100644
--- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt
+++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
@@ -34,6 +34,7 @@
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox
+import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox
import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
@@ -86,8 +87,13 @@
val context = LocalContext.current
val authTypeOptions = stringArrayResource(R.array.apn_auth_entries).toList()
val apnProtocolOptions = stringArrayResource(R.array.apn_protocol_entries).toList()
+ val bearerOptionsAll = stringArrayResource(R.array.bearer_entries)
+ val bearerOptions = bearerOptionsAll.drop(1).toList()
+ val bearerEmptyVal = bearerOptionsAll[0]
val mvnoTypeOptions = stringArrayResource(R.array.mvno_type_entries).toList()
-
+ val bearerSelectedOptionsState = remember {
+ getBearerSelectedOptionsState(apnData.bearer, apnData.bearerBitmask, context)
+ }
RegularScaffold(
title = stringResource(id = R.string.apn_edit),
) {
@@ -184,6 +190,13 @@
}
}
)
+ SettingsExposedDropdownMenuCheckBox(
+ stringResource(R.string.bearer),
+ bearerOptions,
+ bearerSelectedOptionsState,
+ bearerEmptyVal,
+ apnData.bearerEnabled
+ ) {}
SettingsExposedDropdownMenuBox(
stringResource(R.string.mvno_type),
mvnoTypeOptions,
diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt
index 7f4c297..06d8cfb 100644
--- a/src/com/android/settings/network/apn/ApnStatus.kt
+++ b/src/com/android/settings/network/apn/ApnStatus.kt
@@ -16,8 +16,12 @@
package com.android.settings.network.apn
+import android.content.Context
import android.provider.Telephony
import android.telephony.TelephonyManager
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.snapshots.SnapshotStateList
+import com.android.settings.R
data class ApnData(
val name: String = "",
@@ -65,4 +69,29 @@
var bearerEnabled = true
var mvnoTypeEnabled = true
var mvnoValueEnabled = false
-}
\ No newline at end of file
+}
+
+fun getBearerSelectedOptionsState(
+ bearer: Int,
+ bearerBitmask: Int,
+ context: Context
+): SnapshotStateList<Int> {
+ val bearerValues = context.resources.getStringArray(R.array.bearer_values)
+ val bearerSelectedOptionsState = mutableStateListOf<Int>()
+ if (bearerBitmask != 0) {
+ var i = 1
+ var _bearerBitmask = bearerBitmask
+ while (_bearerBitmask != 0) {
+ if (_bearerBitmask and 1 == 1 && !bearerSelectedOptionsState.contains(i)) {
+ bearerSelectedOptionsState.add(bearerValues.indexOf("$i") - 1)
+ }
+ _bearerBitmask = _bearerBitmask shr 1
+ i++
+ }
+ }
+ if (bearer != 0 && !bearerSelectedOptionsState.contains(bearer)) {
+ // add mBearerInitialVal to bearers
+ bearerSelectedOptionsState.add(bearerValues.indexOf("$bearer") - 1)
+ }
+ return bearerSelectedOptionsState
+}
diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java
index 737d1df..800adb0 100644
--- a/src/com/android/settings/password/ChooseLockPassword.java
+++ b/src/com/android/settings/password/ChooseLockPassword.java
@@ -1034,8 +1034,6 @@
getActivity().getWindow().getDecorView());
mPasswordEntryInputDisabler.setInputEnabled(false);
- setNextEnabled(false);
-
mSaveAndFinishWorker = new SaveAndFinishWorker();
mSaveAndFinishWorker
.setListener(this)
diff --git a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
index 65f2705..0384f0d 100644
--- a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
+++ b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
@@ -23,7 +23,6 @@
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
-import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
@@ -67,7 +66,6 @@
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.graph.ThemedBatteryDrawable;
import com.android.wifitrackerlib.NetworkDetailsTracker;
import com.android.wifitrackerlib.WifiEntry;
@@ -441,23 +439,8 @@
@VisibleForTesting
void updateBattery(boolean isChanging, int percentage) {
Preference battery = getPreferenceScreen().findPreference(KEY_HOTSPOT_DEVICE_BATTERY);
- battery.setSummary(formatPercentage(percentage));
- ThemedBatteryDrawable drawable = getBatteryDrawable();
- if (drawable != null) {
- drawable.setCharging(isChanging);
- drawable.setBatteryLevel(percentage);
- }
- battery.setIcon(drawable);
- }
-
- @VisibleForTesting
- ThemedBatteryDrawable getBatteryDrawable() {
- int frameColor = getContext()
- .getColor(com.android.settingslib.R.color.meter_background_color);
- ThemedBatteryDrawable drawable = new ThemedBatteryDrawable(getContext(), frameColor);
- ColorFilter colorFilter = Utils.getAlphaInvariantColorFilterForColor(
- Utils.getColorAttrDefaultColor(getContext(), android.R.attr.colorControlNormal));
- drawable.setColorFilter(colorFilter);
- return drawable;
+ battery.setSummary((isChanging)
+ ? getString(R.string.hotspot_battery_charging_summary, formatPercentage(percentage))
+ : formatPercentage(percentage));
}
}
diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java
index b16d336..5eee615 100644
--- a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java
+++ b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java
@@ -17,6 +17,7 @@
package com.android.settings.datausage;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -38,22 +39,28 @@
import android.widget.FrameLayout;
import android.widget.Spinner;
+import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.loader.app.LoaderManager;
import androidx.preference.PreferenceManager;
import com.android.settings.R;
+import com.android.settings.datausage.lib.BillingCycleRepository;
import com.android.settings.network.MobileDataEnabledListener;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.widget.LoadingViewController;
import com.android.settingslib.NetworkPolicyEditor;
+import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
@@ -64,6 +71,8 @@
@RunWith(RobolectricTestRunner.class)
public class DataUsageListTest {
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock
private MobileDataEnabledListener mMobileDataEnabledListener;
@@ -73,20 +82,23 @@
private LoaderManager mLoaderManager;
@Mock
private UserManager mUserManager;
+ @Mock
+ private BillingCycleRepository mBillingCycleRepository;
private Activity mActivity;
- private DataUsageList mDataUsageList;
+
+ @Spy
+ private TestDataUsageList mDataUsageList;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
FakeFeatureFactory.setupForTest();
final ActivityController<Activity> mActivityController =
Robolectric.buildActivity(Activity.class);
mActivity = spy(mActivityController.get());
mNetworkServices.mPolicyEditor = mock(NetworkPolicyEditor.class);
- mDataUsageList = spy(DataUsageList.class);
mDataUsageList.mDataStateListener = mMobileDataEnabledListener;
+ mDataUsageList.mTemplate = mock(NetworkTemplate.class);
doReturn(mActivity).when(mDataUsageList).getContext();
doReturn(mUserManager).when(mActivity).getSystemService(UserManager.class);
@@ -97,6 +109,7 @@
doReturn(mLoaderManager).when(mDataUsageList).getLoaderManager();
mDataUsageList.mLoadingViewController = mock(LoadingViewController.class);
doNothing().when(mDataUsageList).updateSubscriptionInfoEntity();
+ when(mBillingCycleRepository.isBandwidthControlEnabled()).thenReturn(true);
}
@Test
@@ -225,15 +238,13 @@
final View rootView = LayoutInflater.from(mActivity)
.inflate(R.layout.preference_list_fragment, null, false);
final FrameLayout pinnedHeader = rootView.findViewById(R.id.pinned_header);
- final View header = mActivity.getLayoutInflater()
- .inflate(R.layout.apps_filter_spinner, pinnedHeader, false);
- return header;
+ return mActivity.getLayoutInflater()
+ .inflate(R.layout.apps_filter_spinner, pinnedHeader, false);
}
private Spinner getSpinner(View header) {
- final Spinner spinner = header.findViewById(R.id.filter_spinner);
- return spinner;
+ return header.findViewById(R.id.filter_spinner);
}
@Implements(DataUsageBaseFragment.class)
@@ -242,10 +253,18 @@
public void onCreate(Bundle icicle) {
// do nothing
}
+ }
- @Implementation
- protected boolean isBandwidthControlEnabled() {
- return true;
+ public class TestDataUsageList extends DataUsageList {
+ @Override
+ protected <T extends AbstractPreferenceController> T use(Class<T> clazz) {
+ return mock(clazz);
+ }
+
+ @NonNull
+ @Override
+ BillingCycleRepository createBillingCycleRepository() {
+ return mBillingCycleRepository;
}
}
}
diff --git a/tests/robotests/src/com/android/settings/wifi/details/WifiNetworkDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/wifi/details/WifiNetworkDetailsFragmentTest.java
index ad4aebf..8f96e27 100644
--- a/tests/robotests/src/com/android/settings/wifi/details/WifiNetworkDetailsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/details/WifiNetworkDetailsFragmentTest.java
@@ -58,7 +58,6 @@
import com.android.settings.wifi.WifiUtils;
import com.android.settings.wifi.details2.WifiDetailPreferenceController2;
import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.graph.ThemedBatteryDrawable;
import com.android.wifitrackerlib.NetworkDetailsTracker;
import com.android.wifitrackerlib.WifiEntry;
@@ -67,7 +66,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
@@ -112,8 +110,6 @@
FakeFragment mFragment;
PreferenceScreen mScreen;
- ArgumentCaptor<ThemedBatteryDrawable> mThemedBatteryDrawableCaptor =
- ArgumentCaptor.forClass(ThemedBatteryDrawable.class);
@Before
public void setUp() {
@@ -290,25 +286,20 @@
}
@Test
- public void updateBattery_hiPercentageNoCharging_setResourceCorrect() {
+ public void updateBattery_hiPercentageNoCharging_setSummaryCorrect() {
mFragment.updateBattery(false /* isChanging */, BATTERY_PERCENTAGE_MAX);
verify(mBattery).setSummary(formatPercentage(BATTERY_PERCENTAGE_MAX));
- verify(mBattery).setIcon(mThemedBatteryDrawableCaptor.capture());
- ThemedBatteryDrawable drawable = mThemedBatteryDrawableCaptor.getValue();
- assertThat(drawable.getCharging()).isFalse();
- assertThat(drawable.getBatteryLevel()).isEqualTo(BATTERY_PERCENTAGE_MAX);
}
@Test
- public void updateBattery_lowPercentageWithCharging_setResourceCorrect() {
+ public void updateBattery_lowPercentageWithCharging_setSummaryCorrect() {
+ String summary = mContext.getString(R.string.hotspot_battery_charging_summary,
+ formatPercentage(0));
+
mFragment.updateBattery(true /* isChanging */, 0 /* percentage */);
- verify(mBattery).setSummary(formatPercentage(0));
- verify(mBattery).setIcon(mThemedBatteryDrawableCaptor.capture());
- ThemedBatteryDrawable drawable = mThemedBatteryDrawableCaptor.getValue();
- assertThat(drawable.getCharging()).isTrue();
- assertThat(drawable.getBatteryLevel()).isEqualTo(0);
+ verify(mBattery).setSummary(summary);
}
// Fake WifiNetworkDetailsFragment to override the protected method as public.
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java
similarity index 92%
rename from tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java
rename to tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java
index c009442..4dca749 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java
+++ b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java
@@ -25,6 +25,7 @@
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
+/** Robolectric shadow for the bluetooth utils. */
@Implements(Utils.class)
public class ShadowBluetoothUtils {
@@ -35,6 +36,7 @@
return sLocalBluetoothManager;
}
+ /** Resets the local bluetooth manager to null. */
@Resetter
public static void reset() {
sLocalBluetoothManager = null;
diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt
index 590fe9e..0d2dcef 100644
--- a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt
@@ -17,16 +17,22 @@
package com.android.settings.network.apn
import android.content.Context
+import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isFocused
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onChild
import androidx.compose.ui.test.onChildAt
+import androidx.compose.ui.test.onLast
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToNode
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -49,15 +55,20 @@
private val apnType = "apn_type"
private val apnRoaming = "IPv4"
private val apnEnable = context.resources.getString(R.string.carrier_enabled)
- private val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList()
- private val apnData = ApnData(
- name = apnName,
- mmsc = mmsc,
- mmsProxy = mmsProxy,
- mnc = mnc,
- apnType = apnType,
- apnRoaming = apnProtocolOptions.indexOf(apnRoaming),
- apnEnable = true
+ private val apnProtocolOptions =
+ context.resources.getStringArray(R.array.apn_protocol_entries).toList()
+ private val bearer = context.resources.getString(R.string.bearer)
+ private val bearerOptions = context.resources.getStringArray(R.array.bearer_entries).toList()
+ private val apnData = mutableStateOf(
+ ApnData(
+ name = apnName,
+ mmsc = mmsc,
+ mmsProxy = mmsProxy,
+ mnc = mnc,
+ apnType = apnType,
+ apnRoaming = apnProtocolOptions.indexOf(apnRoaming),
+ apnEnable = true
+ )
)
@Test
@@ -69,7 +80,7 @@
fun title_displayed() {
composeTestRule.setContent {
ApnPage(remember {
- mutableStateOf(apnData)
+ apnData
})
}
composeTestRule.onNodeWithText(context.getString(R.string.apn_edit)).assertIsDisplayed()
@@ -79,7 +90,7 @@
fun name_displayed() {
composeTestRule.setContent {
ApnPage(remember {
- mutableStateOf(apnData)
+ apnData
})
}
composeTestRule.onNodeWithText(apnName, true).assertIsDisplayed()
@@ -89,7 +100,7 @@
fun mmsc_displayed() {
composeTestRule.setContent {
ApnPage(remember {
- mutableStateOf(apnData)
+ apnData
})
}
composeTestRule.onRoot().onChild().onChildAt(0)
@@ -101,7 +112,7 @@
fun mms_proxy_displayed() {
composeTestRule.setContent {
ApnPage(remember {
- mutableStateOf(apnData)
+ apnData
})
}
composeTestRule.onRoot().onChild().onChildAt(0)
@@ -113,7 +124,7 @@
fun mnc_displayed() {
composeTestRule.setContent {
ApnPage(remember {
- mutableStateOf(apnData)
+ apnData
})
}
composeTestRule.onRoot().onChild().onChildAt(0)
@@ -125,7 +136,7 @@
fun apn_type_displayed() {
composeTestRule.setContent {
ApnPage(remember {
- mutableStateOf(apnData)
+ apnData
})
}
composeTestRule.onRoot().onChild().onChildAt(0)
@@ -137,7 +148,7 @@
fun apn_roaming_displayed() {
composeTestRule.setContent {
ApnPage(remember {
- mutableStateOf(apnData)
+ apnData
})
}
composeTestRule.onRoot().onChild().onChildAt(0)
@@ -149,7 +160,7 @@
fun carrier_enabled_displayed() {
composeTestRule.setContent {
ApnPage(remember {
- mutableStateOf(apnData)
+ apnData
})
}
composeTestRule.onRoot().onChild().onChildAt(0)
@@ -161,11 +172,73 @@
fun carrier_enabled_isChecked() {
composeTestRule.setContent {
ApnPage(remember {
- mutableStateOf(apnData)
+ apnData
})
}
composeTestRule.onRoot().onChild().onChildAt(0)
.performScrollToNode(hasText(apnEnable, true))
composeTestRule.onNodeWithText(apnEnable, true).assertIsOn()
}
+
+ @Test
+ fun carrier_enabled_checkChanged() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ apnData
+ })
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(apnEnable, true))
+ composeTestRule.onNodeWithText(apnEnable, true).performClick()
+ composeTestRule.onNodeWithText(apnEnable, true).assertIsOff()
+ }
+
+ @Test
+ fun bearer_displayed() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ apnData
+ })
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(bearer, true))
+ composeTestRule.onNodeWithText(bearer, true).assertIsDisplayed()
+ }
+
+ @Test
+ fun bearer_changed() {
+ var apnDataa: MutableState<ApnData> = apnData
+ composeTestRule.setContent {
+ apnDataa = remember {
+ apnData
+ }
+ ApnPage(apnDataa)
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(bearer, true))
+ composeTestRule.onNodeWithText(bearer, true).performClick()
+ composeTestRule.onNodeWithText(bearerOptions[1], true).performClick()
+ composeTestRule.onNode(hasText(bearerOptions[0]) and isFocused(), true).assertDoesNotExist()
+ composeTestRule.onNode(hasText(bearerOptions[1]) and isFocused(), true).assertIsDisplayed()
+ }
+
+ @Test
+ fun bearer_changed_back2Default() {
+ var apnDataa: MutableState<ApnData> = apnData
+ composeTestRule.setContent {
+ apnDataa = remember {
+ apnData
+ }
+ ApnPage(apnDataa)
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(bearer, true))
+ composeTestRule.onNodeWithText(bearer, true).performClick()
+ composeTestRule.onNodeWithText(bearerOptions[1], true).performClick()
+ composeTestRule.onNode(hasText(bearerOptions[0]) and isFocused(), true).assertDoesNotExist()
+ composeTestRule.onNode(hasText(bearerOptions[1]) and isFocused(), true).assertIsDisplayed()
+ composeTestRule.onAllNodesWithText(bearerOptions[1], true).onLast().performClick()
+ composeTestRule.onNode(hasText(bearerOptions[0]) and isFocused(), true).assertIsDisplayed()
+ composeTestRule.onNode(hasText(bearerOptions[1]) and isFocused(), true).assertDoesNotExist()
+ }
}
\ No newline at end of file