Merge "[flexiglass] Bouncer action button ("Emergency Call"/"Return to Call")." into main
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index d668c69..defaa20 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -25,7 +25,9 @@
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
+import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -68,7 +70,6 @@
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.LayoutDirection
@@ -77,7 +78,9 @@
import com.android.compose.PlatformButton
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
@@ -195,6 +198,7 @@
val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
var dialog: Dialog? by remember { mutableStateOf(null) }
+ val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -221,21 +225,7 @@
)
}
- if (viewModel.isEmergencyButtonVisible) {
- Button(
- onClick = viewModel::onEmergencyServicesButtonClicked,
- colors =
- ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.tertiaryContainer,
- contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
- ),
- ) {
- Text(
- text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
- style = MaterialTheme.typography.bodyMedium,
- )
- }
- }
+ actionButton?.let { BouncerActionButton(viewModel = it) }
if (dialogMessage != null) {
if (dialog == null) {
@@ -320,6 +310,37 @@
}
}
+/**
+ * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun BouncerActionButton(
+ viewModel: BouncerActionButtonModel,
+ modifier: Modifier = Modifier,
+) {
+ Button(
+ onClick = viewModel.onClick,
+ modifier =
+ modifier.thenIf(viewModel.onLongClick != null) {
+ Modifier.combinedClickable(
+ onClick = viewModel.onClick,
+ onLongClick = viewModel.onLongClick,
+ )
+ },
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ ),
+ ) {
+ Text(
+ text = viewModel.label,
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+}
+
/** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */
@Composable
private fun UserSwitcher(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
new file mode 100644
index 0000000..c2117ae
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 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.systemui.bouncer.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
+
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+
+ private lateinit var underTest: EmergencyServicesRepository
+
+ @Before
+ fun setUp() {
+ overrideResource(
+ R.bool.config_enable_emergency_call_while_sim_locked,
+ ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED
+ )
+
+ underTest =
+ EmergencyServicesRepository(
+ resources = context.resources,
+ applicationScope = testScope.backgroundScope,
+ configurationRepository = utils.configurationRepository,
+ )
+ }
+
+ @Test
+ fun enableEmergencyCallWhileSimLocked() =
+ testScope.runTest {
+ val enableEmergencyCallWhileSimLocked by
+ collectLastValue(underTest.enableEmergencyCallWhileSimLocked)
+
+ setEmergencyCallWhileSimLocked(isEnabled = false)
+ assertThat(enableEmergencyCallWhileSimLocked).isFalse()
+
+ setEmergencyCallWhileSimLocked(isEnabled = true)
+ assertThat(enableEmergencyCallWhileSimLocked).isTrue()
+ }
+
+ private fun TestScope.setEmergencyCallWhileSimLocked(isEnabled: Boolean) {
+ overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, isEnabled)
+ utils.configurationRepository.onConfigurationChange()
+ runCurrent()
+ }
+
+ companion object {
+ private const val ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED = true
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
new file mode 100644
index 0000000..fde3ad7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright 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.systemui.bouncer.domain.interactor
+
+import android.app.ActivityTaskManager
+import android.telecom.TelecomManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.logging.nano.MetricsProto
+import com.android.internal.logging.testing.FakeMetricsLogger
+import com.android.internal.util.EmergencyAffordanceManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BouncerActionButtonInteractorTest : SysuiTestCase() {
+
+ @Mock private lateinit var activityTaskManager: ActivityTaskManager
+ @Mock private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
+ @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+ @Mock private lateinit var tableLogger: TableLogBuffer
+ @Mock private lateinit var telecomManager: TelecomManager
+
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+ private val metricsLogger = FakeMetricsLogger()
+ private var currentUserId: Int = 0
+ private var needsEmergencyAffordance = true
+
+ private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
+
+ private lateinit var underTest: BouncerActionButtonInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL)
+ overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL)
+ overrideResource(
+ R.bool.config_enable_emergency_call_while_sim_locked,
+ ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED
+ )
+ whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUserId)
+ whenever(emergencyAffordanceManager.needsEmergencyAffordance())
+ .thenReturn(needsEmergencyAffordance)
+ whenever(telecomManager.isInCall).thenReturn(false)
+
+ utils.featureFlags.set(REFACTOR_GETCURRENTUSER, true)
+
+ mobileConnectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
+
+ utils.telephonyRepository.setHasTelephonyRadio(true)
+
+ underTest =
+ utils.bouncerActionButtonInteractor(
+ mobileConnectionsRepository = mobileConnectionsRepository,
+ activityTaskManager = activityTaskManager,
+ telecomManager = telecomManager,
+ emergencyAffordanceManager = emergencyAffordanceManager,
+ metricsLogger = metricsLogger,
+ )
+ }
+
+ @Test
+ fun noTelephonyRadio_noButton() =
+ testScope.runTest {
+ utils.telephonyRepository.setHasTelephonyRadio(false)
+ underTest =
+ utils.bouncerActionButtonInteractor(
+ mobileConnectionsRepository = mobileConnectionsRepository,
+ activityTaskManager = activityTaskManager,
+ telecomManager = telecomManager,
+ )
+
+ val actionButton by collectLastValue(underTest.actionButton)
+ assertThat(actionButton).isNull()
+ }
+
+ @Test
+ fun noTelecomManager_noButton() =
+ testScope.runTest {
+ underTest =
+ utils.bouncerActionButtonInteractor(
+ mobileConnectionsRepository = mobileConnectionsRepository,
+ activityTaskManager = activityTaskManager,
+ telecomManager = null,
+ )
+ val actionButton by collectLastValue(underTest.actionButton)
+ assertThat(actionButton).isNull()
+ }
+
+ @Test
+ fun duringCall_returnToCallButton() =
+ testScope.runTest {
+ val actionButton by collectLastValue(underTest.actionButton)
+ utils.telephonyRepository.setIsInCall(true)
+
+ assertThat(actionButton).isNotNull()
+ assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL)
+ assertThat(actionButton?.onClick).isNotNull()
+ assertThat(actionButton?.onLongClick).isNull()
+
+ actionButton?.onClick?.invoke()
+
+ assertThat(metricsLogger.logs.size).isEqualTo(1)
+ assertThat(metricsLogger.logs.element().category)
+ .isEqualTo(MetricsProto.MetricsEvent.ACTION_EMERGENCY_CALL)
+ verify(activityTaskManager).stopSystemLockTaskMode()
+ verify(telecomManager).showInCallScreen(eq(false))
+ }
+
+ @Test
+ fun noCall_secureAuthMethod_emergencyCallButton() =
+ testScope.runTest {
+ val actionButton by collectLastValue(underTest.actionButton)
+ mobileConnectionsRepository.isAnySimSecure.value = false
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.telephonyRepository.setIsInCall(false)
+
+ assertThat(actionButton).isNotNull()
+ assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
+ assertThat(actionButton?.onClick).isNotNull()
+ assertThat(actionButton?.onLongClick).isNotNull()
+
+ actionButton?.onClick?.invoke()
+
+ assertThat(metricsLogger.logs.size).isEqualTo(1)
+ assertThat(metricsLogger.logs.element().category)
+ .isEqualTo(MetricsProto.MetricsEvent.ACTION_EMERGENCY_CALL)
+ verify(activityTaskManager).stopSystemLockTaskMode()
+
+ // TODO(b/25189994): Test the activity has been started once we switch to the
+ // ActivityStarter interface here.
+ verify(emergencyAffordanceManager, never()).performEmergencyCall()
+
+ actionButton?.onLongClick?.invoke()
+ verify(emergencyAffordanceManager).performEmergencyCall()
+ }
+
+ @Test
+ fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() =
+ testScope.runTest {
+ val actionButton by collectLastValue(underTest.actionButton)
+ mobileConnectionsRepository.isAnySimSecure.value = true
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.telephonyRepository.setIsInCall(false)
+
+ assertThat(actionButton).isNotNull()
+ assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
+ assertThat(actionButton?.onClick).isNotNull()
+ assertThat(actionButton?.onLongClick).isNotNull()
+ }
+
+ @Test
+ fun noCall_insecure_noButton() =
+ testScope.runTest {
+ val actionButton by collectLastValue(underTest.actionButton)
+ mobileConnectionsRepository.isAnySimSecure.value = false
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.telephonyRepository.setIsInCall(false)
+
+ assertThat(actionButton).isNull()
+ }
+
+ @Test
+ fun noCall_simSecureButEmergencyNotSupported_noButton() =
+ testScope.runTest {
+ val actionButton by collectLastValue(underTest.actionButton)
+ mobileConnectionsRepository.isAnySimSecure.value = true
+ overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, false)
+ utils.configurationRepository.onConfigurationChange()
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.telephonyRepository.setIsInCall(false)
+ runCurrent()
+
+ assertThat(actionButton).isNull()
+ }
+
+ companion object {
+ private const val MESSAGE_EMERGENCY_CALL = "Emergency"
+ private const val MESSAGE_RETURN_TO_CALL = "Return to call"
+ private const val ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED = true
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
new file mode 100644
index 0000000..262795f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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.systemui.telephony.data.repository
+
+import android.telecom.TelecomManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TelephonyRepositoryImplTest : SysuiTestCase() {
+
+ @Mock private lateinit var manager: TelephonyListenerManager
+ @Mock private lateinit var telecomManager: TelecomManager
+
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+
+ private lateinit var underTest: TelephonyRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(telecomManager.isInCall).thenReturn(false)
+
+ underTest =
+ TelephonyRepositoryImpl(
+ applicationScope = testScope.backgroundScope,
+ applicationContext = context,
+ backgroundDispatcher = utils.testDispatcher,
+ manager = manager,
+ telecomManager = telecomManager,
+ )
+ }
+
+ @Test
+ fun callState() =
+ testScope.runTest {
+ val callState by collectLastValue(underTest.callState)
+ runCurrent()
+
+ val listenerCaptor = kotlinArgumentCaptor<TelephonyCallback.CallStateListener>()
+ verify(manager).addCallStateListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+
+ listener.onCallStateChanged(0)
+ assertThat(callState).isEqualTo(0)
+
+ listener.onCallStateChanged(1)
+ assertThat(callState).isEqualTo(1)
+
+ listener.onCallStateChanged(2)
+ assertThat(callState).isEqualTo(2)
+ }
+
+ @Test
+ fun isInCall() =
+ testScope.runTest {
+ val isInCall by collectLastValue(underTest.isInCall)
+ runCurrent()
+
+ val listenerCaptor = kotlinArgumentCaptor<TelephonyCallback.CallStateListener>()
+ verify(manager).addCallStateListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+ whenever(telecomManager.isInCall).thenReturn(true)
+ listener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK)
+
+ assertThat(isInCall).isTrue()
+
+ whenever(telecomManager.isInCall).thenReturn(false)
+ listener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE)
+
+ assertThat(isInCall).isFalse()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index 5de370f..7733841 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -28,7 +28,6 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -54,21 +53,20 @@
/** View Controller for {@link com.android.keyguard.EmergencyButton}. */
@KeyguardBouncerScope
public class EmergencyButtonController extends ViewController<EmergencyButton> {
- static final String LOG_TAG = "EmergencyButton";
+ private static final String TAG = "EmergencyButton";
private final ConfigurationController mConfigurationController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final TelephonyManager mTelephonyManager;
private final PowerManager mPowerManager;
private final ActivityTaskManager mActivityTaskManager;
- private ShadeController mShadeController;
+ private final ShadeController mShadeController;
private final TelecomManager mTelecomManager;
private final MetricsLogger mMetricsLogger;
private EmergencyButtonCallback mEmergencyButtonCallback;
- private LockPatternUtils mLockPatternUtils;
- private Executor mMainExecutor;
- private Executor mBackgroundExecutor;
- private SelectedUserInteractor mSelectedUserInteractor;
+ private final LockPatternUtils mLockPatternUtils;
+ private final Executor mMainExecutor;
+ private final Executor mBackgroundExecutor;
+ private final SelectedUserInteractor mSelectedUserInteractor;
private final KeyguardUpdateMonitorCallback mInfoCallback =
new KeyguardUpdateMonitorCallback() {
@@ -93,17 +91,18 @@
@VisibleForTesting
public EmergencyButtonController(@Nullable EmergencyButton view,
ConfigurationController configurationController,
- KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
- PowerManager powerManager, ActivityTaskManager activityTaskManager,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ PowerManager powerManager,
+ ActivityTaskManager activityTaskManager,
ShadeController shadeController,
- @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
+ @Nullable TelecomManager telecomManager,
+ MetricsLogger metricsLogger,
LockPatternUtils lockPatternUtils,
Executor mainExecutor, Executor backgroundExecutor,
SelectedUserInteractor selectedUserInteractor) {
super(view);
mConfigurationController = configurationController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mTelephonyManager = telephonyManager;
mPowerManager = powerManager;
mActivityTaskManager = activityTaskManager;
mShadeController = shadeController;
@@ -183,7 +182,7 @@
} else {
mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
if (mTelecomManager == null) {
- Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+ Log.wtf(TAG, "TelecomManager was null, cannot launch emergency dialer");
return;
}
Intent emergencyDialIntent =
@@ -212,10 +211,9 @@
public static class Factory {
private final ConfigurationController mConfigurationController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final TelephonyManager mTelephonyManager;
private final PowerManager mPowerManager;
private final ActivityTaskManager mActivityTaskManager;
- private ShadeController mShadeController;
+ private final ShadeController mShadeController;
@Nullable
private final TelecomManager mTelecomManager;
private final MetricsLogger mMetricsLogger;
@@ -226,10 +224,12 @@
@Inject
public Factory(ConfigurationController configurationController,
- KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
- PowerManager powerManager, ActivityTaskManager activityTaskManager,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ PowerManager powerManager,
+ ActivityTaskManager activityTaskManager,
ShadeController shadeController,
- @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
+ @Nullable TelecomManager telecomManager,
+ MetricsLogger metricsLogger,
LockPatternUtils lockPatternUtils,
@Main Executor mainExecutor,
@Background Executor backgroundExecutor,
@@ -237,7 +237,6 @@
mConfigurationController = configurationController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mTelephonyManager = telephonyManager;
mPowerManager = powerManager;
mActivityTaskManager = activityTaskManager;
mShadeController = shadeController;
@@ -252,9 +251,9 @@
/** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
public EmergencyButtonController create(EmergencyButton view) {
return new EmergencyButtonController(view, mConfigurationController,
- mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
- mShadeController, mTelecomManager, mMetricsLogger, mLockPatternUtils,
- mMainExecutor, mBackgroundExecutor, mSelectedUserInteractor);
+ mKeyguardUpdateMonitor, mPowerManager, mActivityTaskManager, mShadeController,
+ mTelecomManager, mMetricsLogger, mLockPatternUtils, mMainExecutor,
+ mBackgroundExecutor, mSelectedUserInteractor);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
new file mode 100644
index 0000000..bba0050
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.systemui.bouncer.data.repository
+
+import android.content.res.Resources
+import com.android.internal.R
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Encapsulates Emergency Services related state. */
+@SysUISingleton
+class EmergencyServicesRepository
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val resources: Resources,
+ configurationRepository: ConfigurationRepository,
+) {
+ /**
+ * Whether to enable emergency services calls while the SIM card is locked. This is disabled in
+ * certain countries that don't support this.
+ */
+ val enableEmergencyCallWhileSimLocked: StateFlow<Boolean> =
+ configurationRepository.onConfigurationChange
+ .map { getEnableEmergencyCallWhileSimLocked() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = getEnableEmergencyCallWhileSimLocked()
+ )
+
+ private fun getEnableEmergencyCallWhileSimLocked(): Boolean {
+ return resources.getBoolean(R.bool.config_enable_emergency_call_while_sim_locked)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
new file mode 100644
index 0000000..f36ef66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
@@ -0,0 +1,181 @@
+/*
+ * 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.systemui.bouncer.domain.interactor
+
+import android.annotation.SuppressLint
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import android.telecom.TelecomManager
+import com.android.internal.R
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent
+import com.android.internal.util.EmergencyAffordanceManager
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
+import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.doze.DozeLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.EmergencyDialerConstants
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.withContext
+
+/**
+ * Encapsulates business logic and application state for the bouncer action button. The action
+ * button can support multiple different actions, depending on device state.
+ */
+@SysUISingleton
+class BouncerActionButtonInteractor
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val repository: EmergencyServicesRepository,
+ // TODO(b/307977401): Replace with `MobileConnectionsInteractor` when available.
+ private val mobileConnectionsRepository: MobileConnectionsRepository,
+ private val telephonyInteractor: TelephonyInteractor,
+ private val authenticationInteractor: AuthenticationInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val activityTaskManager: ActivityTaskManager,
+ private val telecomManager: TelecomManager?,
+ private val emergencyAffordanceManager: EmergencyAffordanceManager,
+ private val emergencyDialerIntentFactory: EmergencyDialerIntentFactory,
+ private val metricsLogger: MetricsLogger,
+ private val dozeLogger: DozeLogger,
+) {
+ /** The bouncer action button. If `null`, the button should not be shown. */
+ val actionButton: Flow<BouncerActionButtonModel?> =
+ if (telecomManager == null || !telephonyInteractor.hasTelephonyRadio) {
+ flowOf(null)
+ } else {
+ merge(
+ telephonyInteractor.isInCall.asUnitFlow,
+ mobileConnectionsRepository.isAnySimSecure.asUnitFlow,
+ authenticationInteractor.authenticationMethod.asUnitFlow,
+ repository.enableEmergencyCallWhileSimLocked.asUnitFlow,
+ )
+ .map {
+ when {
+ isReturnToCallButton() -> returnToCallButtonModel
+ isEmergencyCallButton() -> emergencyCallButtonModel
+ else -> null // Do not show the button.
+ }
+ }
+ .distinctUntilChanged()
+ }
+
+ private val returnToCallButtonModel: BouncerActionButtonModel by lazy {
+ BouncerActionButtonModel(
+ label = applicationContext.getString(R.string.lockscreen_return_to_call),
+ onClick = {
+ prepareToPerformAction()
+ returnToCall()
+ },
+ onLongClick = null
+ )
+ }
+
+ private val emergencyCallButtonModel: BouncerActionButtonModel by lazy {
+ BouncerActionButtonModel(
+ label = applicationContext.getString(R.string.lockscreen_emergency_call),
+ onClick = {
+ prepareToPerformAction()
+ dozeLogger.logEmergencyCall()
+ startEmergencyDialerActivity()
+ },
+ // TODO(b/308001302): The long click detector doesn't work properly, investigate.
+ onLongClick = {
+ if (emergencyAffordanceManager.needsEmergencyAffordance()) {
+ prepareToPerformAction()
+
+ // TODO(b/298026988): Check that !longPressWasDragged before invoking.
+ emergencyAffordanceManager.performEmergencyCall()
+ }
+ }
+ )
+ }
+
+ private fun startEmergencyDialerActivity() {
+ emergencyDialerIntentFactory()?.apply {
+ flags =
+ Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or
+ Intent.FLAG_ACTIVITY_CLEAR_TOP
+
+ putExtra(
+ EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
+ EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON,
+ )
+
+ // TODO(b/25189994): Use the ActivityStarter interface instead.
+ applicationContext.startActivityAsUser(
+ this,
+ ActivityOptions.makeCustomAnimation(applicationContext, 0, 0).toBundle(),
+ UserHandle(selectedUserInteractor.getSelectedUserId())
+ )
+ }
+ }
+
+ private fun isReturnToCallButton() = telephonyInteractor.isInCall.value
+
+ private suspend fun isEmergencyCallButton(): Boolean {
+ return if (mobileConnectionsRepository.getIsAnySimSecure()) {
+ // Some countries can't handle emergency calls while SIM is locked.
+ repository.enableEmergencyCallWhileSimLocked.value
+ } else {
+ // Only show if there is a secure screen (password/pin/pattern/SIM pin/SIM puk).
+ withContext(backgroundDispatcher) {
+ authenticationInteractor.getAuthenticationMethod().isSecure
+ }
+ }
+ }
+
+ private fun prepareToPerformAction() {
+ // TODO(b/308001302): Trigger occlusion and resetting bouncer state.
+ metricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL)
+ activityTaskManager.stopSystemLockTaskMode()
+ }
+
+ @SuppressLint("MissingPermission")
+ private fun returnToCall() {
+ telecomManager?.showInCallScreen(/* showDialpad = */ false)
+ }
+
+ private val <T> Flow<T>.asUnitFlow: Flow<Unit>
+ get() = map {}
+}
+
+/**
+ * Creates an intent to launch the Emergency Services dialer. If no [TelecomManager] is present,
+ * returns `null`.
+ */
+interface EmergencyDialerIntentFactory {
+ operator fun invoke(): Intent?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
new file mode 100644
index 0000000..e398c93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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.systemui.bouncer.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.telecom.TelecomManager
+import com.android.internal.util.EmergencyAffordanceManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import dagger.Module
+import dagger.Provides
+
+/** Module for providing interactor-related objects for the bouncer. */
+@Module
+object BouncerInteractorModule {
+
+ @Provides
+ fun emergencyDialerIntentFactory(
+ telecomManager: TelecomManager?
+ ): EmergencyDialerIntentFactory {
+ return object : EmergencyDialerIntentFactory {
+ override fun invoke(): Intent? {
+ return telecomManager?.createLaunchEmergencyDialerIntent(/* number = */ null)
+ }
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ fun emergencyAffordanceManager(
+ @Application applicationContext: Context,
+ ): EmergencyAffordanceManager {
+ return EmergencyAffordanceManager(applicationContext)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerActionButtonModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerActionButtonModel.kt
new file mode 100644
index 0000000..7f1730c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerActionButtonModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.systemui.bouncer.shared.model
+
+/** Models the action button on the bouncer. */
+data class BouncerActionButtonModel(
+ /** The text to be shown on the button. */
+ val label: String,
+
+ /** The action to perform when the user clicks on the button. */
+ val onClick: () -> Unit,
+
+ /**
+ * The action to perform when the user long-clicks on the button. When not provided, long-clicks
+ * will be treated as regular clicks.
+ */
+ val onLongClick: (() -> Unit)? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index ef0609a..0f77724 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -21,14 +21,15 @@
import androidx.core.graphics.drawable.toBitmap
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.user.ui.viewmodel.UserViewModel
@@ -59,10 +60,10 @@
private val bouncerInteractor: BouncerInteractor,
authenticationInteractor: AuthenticationInteractor,
flags: SceneContainerFlags,
- private val telephonyInteractor: TelephonyInteractor,
selectedUser: Flow<UserViewModel>,
users: Flow<List<UserViewModel>>,
userSwitcherMenu: Flow<List<UserActionViewModel>>,
+ actionButtonInteractor: BouncerActionButtonInteractor,
) {
val selectedUserImage: StateFlow<Bitmap?> =
selectedUser
@@ -150,8 +151,16 @@
),
)
- val isEmergencyButtonVisible: Boolean
- get() = telephonyInteractor.hasTelephonyRadio
+ /**
+ * The bouncer action button (Return to Call / Emergency Call). If `null`, the button should not
+ * be shown.
+ */
+ val actionButton: StateFlow<BouncerActionButtonModel?> =
+ actionButtonInteractor.actionButton.stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null
+ )
init {
if (flags.isEnabled()) {
@@ -176,11 +185,6 @@
}
}
- /** Notifies that the emergency services button was clicked. */
- fun onEmergencyServicesButtonClicked() {
- // TODO(b/280877228): implement this
- }
-
/** Notifies that a throttling dialog has been dismissed by the user. */
fun onThrottlingDialogDismissed() {
_throttlingDialogMessage.value = null
@@ -271,8 +275,8 @@
bouncerInteractor: BouncerInteractor,
authenticationInteractor: AuthenticationInteractor,
flags: SceneContainerFlags,
- telephonyInteractor: TelephonyInteractor,
userSwitcherViewModel: UserSwitcherViewModel,
+ actionButtonInteractor: BouncerActionButtonInteractor,
): BouncerViewModel {
return BouncerViewModel(
applicationContext = applicationContext,
@@ -281,10 +285,10 @@
bouncerInteractor = bouncerInteractor,
authenticationInteractor = authenticationInteractor,
flags = flags,
- telephonyInteractor = telephonyInteractor,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
userSwitcherMenu = userSwitcherViewModel.menu,
+ actionButtonInteractor = actionButtonInteractor,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index e449274..7fa762a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -44,11 +44,15 @@
interface ConfigurationRepository {
/** Called whenever ui mode, theme or configuration has changed. */
val onAnyConfigurationChange: Flow<Unit>
+
+ /** Called whenever the configuration has changed. */
+ val onConfigurationChange: Flow<Unit>
+
val scaleForResolution: Flow<Float>
fun getResolutionScale(): Float
- /** Convience to context.resources.getDimensionPixelSize() */
+ /** Convenience to context.resources.getDimensionPixelSize() */
fun getDimensionPixelSize(id: Int): Int
}
@@ -87,7 +91,7 @@
awaitClose { configurationController.removeCallback(callback) }
}
- private val configurationChange: Flow<Unit> =
+ override val onConfigurationChange: Flow<Unit> =
ConflatedCallbackFlow.conflatedCallbackFlow {
val callback =
object : ConfigurationController.ConfigurationListener {
@@ -100,7 +104,7 @@
}
override val scaleForResolution: StateFlow<Float> =
- configurationChange
+ onConfigurationChange
.mapLatest { getResolutionScale() }
.distinctUntilChanged()
.stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 58dcf069..8eb80ad 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -37,6 +37,7 @@
import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
import com.android.systemui.biometrics.dagger.BiometricsModule;
import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule;
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractorModule;
import com.android.systemui.bouncer.ui.BouncerViewModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
@@ -166,6 +167,7 @@
AuthenticationModule.class,
BiometricsModule.class,
BiometricsDomainLayerModule.class,
+ BouncerInteractorModule.class,
BouncerViewModule.class,
ClipboardOverlayModule.class,
ClockRegistryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
index 934f310..8242087 100644
--- a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
@@ -32,12 +32,12 @@
import com.android.systemui.power.shared.model.WakefulnessModel
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import javax.inject.Inject
/** Defines interface for classes that act as source of truth for power-related data. */
interface PowerRepository {
@@ -67,15 +67,22 @@
/** Wakes up the device. */
fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int)
- /** Notifies the power repository that a user touch happened. */
- fun userTouch()
+ /**
+ * Notifies the power repository that a user touch happened.
+ *
+ * @param noChangeLights If true, does not cause the keyboard backlight to turn on because of
+ * this event. This is set when the power key is pressed. We want the device to stay on while
+ * the button is down, but we're about to turn off the screen so we don't want the keyboard
+ * backlight to turn on again. Otherwise the lights flash on and then off and it looks weird.
+ */
+ fun userTouch(noChangeLights: Boolean = false)
/** Updates the wakefulness state, keeping previous values by default. */
fun updateWakefulness(
- rawState: WakefulnessState = wakefulness.value.internalWakefulnessState,
- lastWakeReason: WakeSleepReason = wakefulness.value.lastWakeReason,
- lastSleepReason: WakeSleepReason = wakefulness.value.lastSleepReason,
- powerButtonLaunchGestureTriggered: Boolean =
+ rawState: WakefulnessState = wakefulness.value.internalWakefulnessState,
+ lastWakeReason: WakeSleepReason = wakefulness.value.lastWakeReason,
+ lastSleepReason: WakeSleepReason = wakefulness.value.lastSleepReason,
+ powerButtonLaunchGestureTriggered: Boolean =
wakefulness.value.powerButtonLaunchGestureTriggered,
)
@@ -121,10 +128,10 @@
override val wakefulness = _wakefulness.asStateFlow()
override fun updateWakefulness(
- rawState: WakefulnessState,
- lastWakeReason: WakeSleepReason,
- lastSleepReason: WakeSleepReason,
- powerButtonLaunchGestureTriggered: Boolean,
+ rawState: WakefulnessState,
+ lastWakeReason: WakeSleepReason,
+ lastSleepReason: WakeSleepReason,
+ powerButtonLaunchGestureTriggered: Boolean,
) {
_wakefulness.value =
WakefulnessModel(
@@ -150,11 +157,11 @@
)
}
- override fun userTouch() {
+ override fun userTouch(noChangeLights: Boolean) {
manager.userActivity(
systemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH,
- /* flags= */ 0,
+ if (noChangeLights) PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS else 0,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 13c9964..9471574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -100,6 +100,17 @@
val isAnySimSecure: Flow<Boolean>
/**
+ * Returns whether any active SIM on the device is in
+ * [android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED] or
+ * [android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED] or
+ * [android.telephony.TelephonyManager.SIM_STATE_PERM_DISABLED].
+ *
+ * Note: Unfortunately, we cannot name this [isAnySimSecure] due to a conflict with the flow
+ * name above (Java code-gen is having issues with it).
+ */
+ fun getIsAnySimSecure(): Boolean
+
+ /**
* Checks if any subscription has [android.telephony.TelephonyManager.getEmergencyCallbackMode]
* == true
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 87dd17e..8a8e33e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -152,6 +152,7 @@
activeRepo.flatMapLatest { it.defaultMobileIconGroup }
override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure }
+ override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure()
override val defaultDataSubId: StateFlow<Int> =
activeRepo
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 2ecd435..2b3c632 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -136,7 +136,8 @@
override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
- override val isAnySimSecure: Flow<Boolean> = flowOf(false)
+ override val isAnySimSecure: Flow<Boolean> = flowOf(getIsAnySimSecure())
+ override fun getIsAnySimSecure(): Boolean = false
override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index cf7bf86..2a510e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -262,7 +262,7 @@
object : KeyguardUpdateMonitorCallback() {
override fun onSimStateChanged(subId: Int, slotId: Int, simState: Int) {
logger.logOnSimStateChanged()
- trySend(keyguardUpdateMonitor.isSimPinSecure)
+ trySend(getIsAnySimSecure())
}
}
keyguardUpdateMonitor.registerCallback(callback)
@@ -277,6 +277,8 @@
)
.distinctUntilChanged()
+ override fun getIsAnySimSecure() = keyguardUpdateMonitor.isSimPinSecure
+
override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository =
getOrCreateRepoForSubId(subId)
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
index 3b300249..b1b6014 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
@@ -17,23 +17,40 @@
package com.android.systemui.telephony.data.repository
+import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
+import android.telecom.TelecomManager
import android.telephony.Annotation
import android.telephony.TelephonyCallback
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.telephony.TelephonyListenerManager
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Defines interface for classes that encapsulate _some_ telephony-related state. */
interface TelephonyRepository {
/** The state of the current call. */
@Annotation.CallState val callState: Flow<Int>
+ /**
+ * Whether there is an ongoing phone call (can be in dialing, ringing, active or holding states)
+ * originating from either a manager or self-managed {@link ConnectionService}.
+ */
+ val isInCall: StateFlow<Boolean>
+
/** Whether the device has a radio that can be used for telephony. */
val hasTelephonyRadio: Boolean
}
@@ -49,18 +66,35 @@
class TelephonyRepositoryImpl
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
@Application private val applicationContext: Context,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
private val manager: TelephonyListenerManager,
+ private val telecomManager: TelecomManager?,
) : TelephonyRepository {
+
@Annotation.CallState
override val callState: Flow<Int> = conflatedCallbackFlow {
- val listener = TelephonyCallback.CallStateListener { state -> trySend(state) }
+ val listener = TelephonyCallback.CallStateListener(::trySend)
manager.addCallStateListener(listener)
awaitClose { manager.removeCallStateListener(listener) }
}
- override val hasTelephonyRadio: Boolean
- get() = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ @SuppressLint("MissingPermission")
+ override val isInCall: StateFlow<Boolean> =
+ if (telecomManager == null) {
+ flowOf(false)
+ } else {
+ callState.map { withContext(backgroundDispatcher) { telecomManager.isInCall } }
+ }
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ override val hasTelephonyRadio =
+ applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
}
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
index 4642f55..4b0e5d1 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
@@ -22,17 +22,18 @@
import com.android.systemui.telephony.data.repository.TelephonyRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/** Hosts business logic related to telephony. */
@SysUISingleton
class TelephonyInteractor
@Inject
constructor(
- private val repository: TelephonyRepository,
+ repository: TelephonyRepository,
) {
@Annotation.CallState val callState: Flow<Int> = repository.callState
- /** Whether the device has a radio that can be used for telephony. */
- val hasTelephonyRadio: Boolean
- get() = repository.hasTelephonyRadio
+ val isInCall: StateFlow<Boolean> = repository.isInCall
+
+ val hasTelephonyRadio: Boolean = repository.hasTelephonyRadio
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
index 78fb7a4..3ed05aa 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -34,10 +34,10 @@
@UserIdInt
@JvmOverloads
fun getSelectedUserId(bypassFlag: Boolean = false): Int {
- if (bypassFlag || flags.isEnabled(REFACTOR_GETCURRENTUSER)) {
- return repository.getSelectedUserInfo().id
+ return if (bypassFlag || flags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+ repository.getSelectedUserInfo().id
} else {
- return KeyguardUpdateMonitor.getCurrentUser()
+ KeyguardUpdateMonitor.getCurrentUser()
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index c61b11a..9a95b17 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -48,7 +48,6 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class EmergencyButtonControllerTest : SysuiTestCase() {
- lateinit var underTest: EmergencyButtonController
@Mock lateinit var emergencyButton: EmergencyButton
@Mock lateinit var configurationController: ConfigurationController
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -61,10 +60,13 @@
@Mock lateinit var lockPatternUtils: LockPatternUtils
@Mock lateinit var packageManager: PackageManager
@Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
+
val fakeSystemClock = FakeSystemClock()
val mainExecutor = FakeExecutor(fakeSystemClock)
val backgroundExecutor = FakeExecutor(fakeSystemClock)
+ lateinit var underTest: EmergencyButtonController
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -73,7 +75,6 @@
emergencyButton,
configurationController,
keyguardUpdateMonitor,
- telephonyManager,
powerManager,
activityTaskManager,
shadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
index a049191..44c57f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -2,15 +2,13 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -18,32 +16,34 @@
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardBouncerRepositoryTest : SysuiTestCase() {
@Mock private lateinit var systemClock: SystemClock
- @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
@Mock private lateinit var bouncerLogger: TableLogBuffer
+
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+
lateinit var underTest: KeyguardBouncerRepository
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- val testCoroutineScope = TestCoroutineScope()
underTest =
KeyguardBouncerRepositoryImpl(
systemClock,
- testCoroutineScope,
+ testScope.backgroundScope,
bouncerLogger,
)
}
@Test
- fun changingFlowValueTriggersLogging() = runBlocking {
- underTest.setPrimaryShow(true)
- Mockito.verify(bouncerLogger)
- .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
- }
+ fun changingFlowValueTriggersLogging() =
+ testScope.runTest {
+ underTest.setPrimaryShow(true)
+ Mockito.verify(bouncerLogger)
+ .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 2c97809..c159b66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -45,6 +45,7 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val authenticationInteractor = utils.authenticationInteractor()
+ private val actionButtonInteractor = utils.bouncerActionButtonInteractor()
private val deviceEntryInteractor =
utils.deviceEntryInteractor(
authenticationInteractor = authenticationInteractor,
@@ -60,6 +61,7 @@
utils.bouncerViewModel(
bouncerInteractor = bouncerInteractor,
authenticationInteractor = authenticationInteractor,
+ actionButtonInteractor = actionButtonInteractor,
)
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index b75f3e0..2cc8f0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -52,8 +52,7 @@
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyguardBouncerViewModelTest : SysuiTestCase() {
- lateinit var underTest: KeyguardBouncerViewModel
- lateinit var bouncerInteractor: PrimaryBouncerInteractor
+
@Mock lateinit var bouncerView: BouncerView
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
@@ -62,9 +61,13 @@
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ lateinit var bouncerInteractor: PrimaryBouncerInteractor
private val mainHandler = FakeHandler(Looper.getMainLooper())
val repository = FakeKeyguardBouncerRepository()
+ lateinit var underTest: KeyguardBouncerViewModel
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index ba8dcef..390742031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -61,7 +61,9 @@
utils.bouncerViewModel(
bouncerInteractor = bouncerInteractor,
authenticationInteractor = authenticationInteractor,
+ actionButtonInteractor = utils.bouncerActionButtonInteractor(),
)
+
private val underTest =
PasswordBouncerViewModel(
viewModelScope = testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index bfaa6ed..47db4f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -64,6 +64,7 @@
utils.bouncerViewModel(
bouncerInteractor = bouncerInteractor,
authenticationInteractor = authenticationInteractor,
+ actionButtonInteractor = utils.bouncerActionButtonInteractor(),
)
private val underTest =
PatternBouncerViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 7873899..3ddac7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -63,6 +63,7 @@
utils.bouncerViewModel(
bouncerInteractor = bouncerInteractor,
authenticationInteractor = authenticationInteractor,
+ actionButtonInteractor = utils.bouncerActionButtonInteractor(),
)
private val underTest =
PinBouncerViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
index f566efe6..f3b114d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
@@ -210,6 +210,22 @@
assertThat(flagsCaptor.value).isNotEqualTo(PowerManager.USER_ACTIVITY_FLAG_INDIRECT)
}
+ @Test
+ fun userActivity_notifiesPowerManager_noChangeLightsTrue() {
+ systemClock.setUptimeMillis(345000)
+
+ underTest.userTouch(noChangeLights = true)
+
+ val flagsCaptor = argumentCaptor<Int>()
+ verify(manager)
+ .userActivity(
+ eq(345000L),
+ eq(PowerManager.USER_ACTIVITY_EVENT_TOUCH),
+ capture(flagsCaptor)
+ )
+ assertThat(flagsCaptor.value).isEqualTo(PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)
+ }
+
private fun verifyRegistered() {
// We must verify with all arguments, even those that are optional because they have default
// values because Mockito is forcing us to. Once we can use mockito-kotlin, we should be
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 3feb5bf..e84d274 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -18,17 +18,23 @@
package com.android.systemui.scene
+import android.telecom.TelecomManager
+import android.telephony.TelephonyManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.util.EmergencyAffordanceManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.model.SysUiState
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -49,7 +55,9 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -63,6 +71,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
/**
* Integration test cases for the Scene Framework.
@@ -87,6 +99,10 @@
@RunWith(AndroidJUnit4::class)
class SceneFrameworkIntegrationTest : SysuiTestCase() {
+ @Mock private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
+ @Mock private lateinit var tableLogger: TableLogBuffer
+ @Mock private lateinit var telecomManager: TelecomManager
+
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneContainerConfig = utils.fakeSceneContainerConfig()
@@ -123,11 +139,10 @@
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
)
- private val bouncerViewModel =
- utils.bouncerViewModel(
- bouncerInteractor = bouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- )
+
+ private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
+ private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
+ private lateinit var bouncerViewModel: BouncerViewModel
private val lockscreenSceneViewModel =
LockscreenSceneViewModel(
@@ -141,7 +156,6 @@
)
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
- private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -155,7 +169,7 @@
FakeMobileConnectionsRepository(),
),
constants = mock(),
- flags,
+ utils.featureFlags,
scope = testScope.backgroundScope,
)
@@ -173,6 +187,39 @@
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, true)
+ whenever(telecomManager.isInCall).thenReturn(false)
+ whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true)
+
+ utils.featureFlags.apply {
+ set(Flags.NEW_NETWORK_SLICE_UI, false)
+ set(Flags.REFACTOR_GETCURRENTUSER, true)
+ }
+
+ mobileConnectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
+ mobileConnectionsRepository.isAnySimSecure.value = true
+
+ utils.telephonyRepository.apply {
+ setHasTelephonyRadio(true)
+ setCallState(TelephonyManager.CALL_STATE_IDLE)
+ setIsInCall(false)
+ }
+
+ bouncerActionButtonInteractor =
+ utils.bouncerActionButtonInteractor(
+ mobileConnectionsRepository = mobileConnectionsRepository,
+ telecomManager = telecomManager,
+ emergencyAffordanceManager = emergencyAffordanceManager,
+ )
+ bouncerViewModel =
+ utils.bouncerViewModel(
+ bouncerInteractor = bouncerInteractor,
+ authenticationInteractor = authenticationInteractor,
+ actionButtonInteractor = bouncerActionButtonInteractor,
+ )
+
shadeHeaderViewModel =
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
@@ -395,6 +442,45 @@
emulateUiSceneTransition()
}
+ @Test
+ fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
+ testScope.runTest {
+ setAuthMethod(DomainLayerAuthenticationMethodModel.Password)
+ val upDestinationSceneKey by
+ collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+ emulateUserDrivenTransition(to = upDestinationSceneKey)
+
+ val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
+ assertWithMessage("Bouncer action button not visible")
+ .that(bouncerActionButton)
+ .isNotNull()
+ bouncerActionButton?.onClick?.invoke()
+ runCurrent()
+
+ // TODO(b/298026988): Assert that an activity was started once we use ActivityStarter.
+ }
+
+ @Test
+ fun bouncerActionButtonClick_duringCall_returnsToCall() =
+ testScope.runTest {
+ setAuthMethod(DomainLayerAuthenticationMethodModel.Password)
+ startPhoneCall()
+ val upDestinationSceneKey by
+ collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+ emulateUserDrivenTransition(to = upDestinationSceneKey)
+
+ val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
+ assertWithMessage("Bouncer action button not visible during call")
+ .that(bouncerActionButton)
+ .isNotNull()
+ bouncerActionButton?.onClick?.invoke()
+ runCurrent()
+
+ verify(telecomManager).showInCallScreen(any())
+ }
+
/**
* Asserts that the current scene in the view-model matches what's expected.
*
@@ -438,6 +524,17 @@
runCurrent()
}
+ /** Emulates a phone call in progress. */
+ private fun TestScope.startPhoneCall() {
+ whenever(telecomManager.isInCall).thenReturn(true)
+ utils.telephonyRepository.apply {
+ setHasTelephonyRadio(true)
+ setIsInCall(true)
+ setCallState(TelephonyManager.CALL_STATE_OFFHOOK)
+ }
+ runCurrent()
+ }
+
/**
* Emulates a complete transition in the UI from whatever the current scene is in the UI to
* whatever the current scene should be, based on the value in
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 74b2723..cce038f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -94,6 +94,7 @@
override val defaultMobileIconGroup = _defaultMobileIconGroup
override val isAnySimSecure = MutableStateFlow(false)
+ override fun getIsAnySimSecure(): Boolean = isAnySimSecure.value
private var isInEcmMode: Boolean = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index ac75d4f..03f3005 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -92,7 +92,6 @@
// to run the callback and this makes the looper place nicely with TestScope etc.
@TestableLooper.RunWithLooper
class MobileConnectionsRepositoryTest : SysuiTestCase() {
- private lateinit var underTest: MobileConnectionsRepositoryImpl
private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
@@ -101,6 +100,7 @@
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var wifiRepository: WifiRepository
private lateinit var carrierConfigRepository: CarrierConfigRepository
+
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@@ -115,6 +115,8 @@
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
+ private lateinit var underTest: MobileConnectionsRepositoryImpl
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -1179,6 +1181,16 @@
}
@Test
+ fun getIsAnySimSecure_delegatesCallToKeyguardUpdateMonitor() =
+ testScope.runTest {
+ assertThat(underTest.getIsAnySimSecure()).isFalse()
+
+ whenever(updateMonitor.isSimPinSecure).thenReturn(true)
+
+ assertThat(underTest.getIsAnySimSecure()).isTrue()
+ }
+
+ @Test
fun noSubscriptionsInEcmMode_notInEcmMode() =
testScope.runTest {
whenever(telephonyManager.emergencyCallbackMode).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
deleted file mode 100644
index 0209030..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2022 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.systemui.telephony.data.repository
-
-import android.telephony.TelephonyCallback
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.telephony.TelephonyListenerManager
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class TelephonyRepositoryImplTest : SysuiTestCase() {
-
- @Mock private lateinit var manager: TelephonyListenerManager
-
- private lateinit var underTest: TelephonyRepositoryImpl
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- underTest =
- TelephonyRepositoryImpl(
- applicationContext = context,
- manager = manager,
- )
- }
-
- @Test
- fun callState() =
- runBlocking(IMMEDIATE) {
- var callState: Int? = null
- val job = underTest.callState.onEach { callState = it }.launchIn(this)
- val listenerCaptor = kotlinArgumentCaptor<TelephonyCallback.CallStateListener>()
- verify(manager).addCallStateListener(listenerCaptor.capture())
- val listener = listenerCaptor.value
-
- listener.onCallStateChanged(0)
- assertThat(callState).isEqualTo(0)
-
- listener.onCallStateChanged(1)
- assertThat(callState).isEqualTo(1)
-
- listener.onCallStateChanged(2)
- assertThat(callState).isEqualTo(2)
-
- job.cancel()
-
- verify(manager).removeCallStateListener(listener)
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 1968d75..017eefe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -38,16 +38,14 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Text
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.res.R
+import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -65,9 +63,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -102,18 +97,16 @@
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- private lateinit var underTest: UserSwitcherInteractor
-
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
private lateinit var spyContext: Context
- private lateinit var testScope: TestScope
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies
private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var telephonyRepository: FakeTelephonyRepository
- private lateinit var testDispatcher: TestDispatcher
- private lateinit var featureFlags: FakeFeatureFlags
private lateinit var refreshUsersScheduler: RefreshUsersScheduler
+ private lateinit var underTest: UserSwitcherInteractor
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -127,22 +120,17 @@
SUPERVISED_USER_CREATION_APP_PACKAGE,
)
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- set(Flags.FACE_AUTH_REFACTOR, true)
- }
+ utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ utils.featureFlags.set(Flags.FACE_AUTH_REFACTOR, true)
+
spyContext = spy(context)
- keyguardReply = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+ keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.featureFlags)
keyguardRepository = keyguardReply.repository
userRepository = FakeUserRepository()
- telephonyRepository = FakeTelephonyRepository()
- testDispatcher = StandardTestDispatcher()
- testScope = TestScope(testDispatcher)
refreshUsersScheduler =
RefreshUsersScheduler(
applicationScope = testScope.backgroundScope,
- mainDispatcher = testDispatcher,
+ mainDispatcher = utils.testDispatcher,
repository = userRepository,
)
}
@@ -372,7 +360,7 @@
fun actions_deviceUnlocked_fullScreen() {
createUserInteractor()
testScope.runTest {
- featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
@@ -456,7 +444,7 @@
fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() {
createUserInteractor()
testScope.runTest {
- featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -649,7 +637,7 @@
val refreshUsersCallCount = userRepository.refreshUsersCallCount
- telephonyRepository.setCallState(1)
+ utils.telephonyRepository.setCallState(1)
runCurrent()
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
@@ -801,7 +789,7 @@
fun userRecordsFullScreen() {
createUserInteractor()
testScope.runTest {
- featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
userRepository.setUserInfos(userInfos)
@@ -910,7 +898,7 @@
fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() {
createUserInteractor()
testScope.runTest {
- featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val expandable = mock<Expandable>()
underTest.showUserSwitcher(expandable)
@@ -1125,21 +1113,18 @@
manager = manager,
headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
- telephonyInteractor =
- TelephonyInteractor(
- repository = telephonyRepository,
- ),
+ telephonyInteractor = utils.telephonyInteractor(),
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
- backgroundDispatcher = testDispatcher,
+ backgroundDispatcher = utils.testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor =
GuestUserInteractor(
applicationContext = spyContext,
applicationScope = testScope.backgroundScope,
- mainDispatcher = testDispatcher,
- backgroundDispatcher = testDispatcher,
+ mainDispatcher = utils.testDispatcher,
+ backgroundDispatcher = utils.testDispatcher,
manager = manager,
repository = userRepository,
deviceProvisionedController = deviceProvisionedController,
@@ -1150,7 +1135,7 @@
resetOrExitSessionReceiver = resetOrExitSessionReceiver,
),
uiEventLogger = uiEventLogger,
- featureFlags = featureFlags,
+ featureFlags = utils.featureFlags,
userRestrictionChecker = mock(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 8353cf7..d0fa27e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -20,6 +20,7 @@
import dagger.Binds
import dagger.Module
import javax.inject.Inject
+import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -31,16 +32,23 @@
private val _onAnyConfigurationChange = MutableSharedFlow<Unit>()
override val onAnyConfigurationChange: Flow<Unit> = _onAnyConfigurationChange.asSharedFlow()
+ private val _onConfigurationChange =
+ MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ override val onConfigurationChange: Flow<Unit> = _onConfigurationChange.asSharedFlow()
+
private val _scaleForResolution = MutableStateFlow(1f)
override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>()
- private val colors = mutableMapOf<Int, MutableStateFlow<Int>>()
fun onAnyConfigurationChange() {
_onAnyConfigurationChange.tryEmit(Unit)
}
+ fun onConfigurationChange() {
+ _onConfigurationChange.tryEmit(Unit)
+ }
+
fun setScaleForResolution(scale: Float) {
_scaleForResolution.value = scale
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
index 957fbbd..ace6500 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
@@ -18,11 +18,11 @@
package com.android.systemui.power.data.repository
import android.os.PowerManager
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessModel
import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
import dagger.Module
import javax.inject.Inject
@@ -55,15 +55,15 @@
lastWakeReason = wakeReason
}
- override fun userTouch() {
+ override fun userTouch(noChangeLights: Boolean) {
userTouchRegistered = true
}
override fun updateWakefulness(
- rawState: WakefulnessState,
- lastWakeReason: WakeSleepReason,
- lastSleepReason: WakeSleepReason,
- powerButtonLaunchGestureTriggered: Boolean
+ rawState: WakefulnessState,
+ lastWakeReason: WakeSleepReason,
+ lastSleepReason: WakeSleepReason,
+ powerButtonLaunchGestureTriggered: Boolean
) {
_wakefulness.value =
WakefulnessModel(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 14c4b86..36ec18f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -16,10 +16,15 @@
package com.android.systemui.scene
+import android.app.ActivityTaskManager
import android.content.Context
+import android.content.Intent
import android.content.pm.UserInfo
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
+import android.telecom.TelecomManager
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.util.EmergencyAffordanceManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
import com.android.systemui.authentication.data.repository.AuthenticationRepository
@@ -27,8 +32,11 @@
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.EmergencyDialerIntentFactory
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
@@ -41,6 +49,7 @@
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.doze.DozeLogger
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
@@ -62,10 +71,13 @@
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.data.repository.TelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserViewModel
import com.android.systemui.util.mockito.mock
@@ -102,12 +114,23 @@
currentTime = { testScope.currentTime },
)
}
+ val configurationRepository: FakeConfigurationRepository by lazy {
+ FakeConfigurationRepository()
+ }
+ private val emergencyServicesRepository: EmergencyServicesRepository by lazy {
+ EmergencyServicesRepository(
+ applicationScope = applicationScope(),
+ resources = context.resources,
+ configurationRepository = configurationRepository,
+ )
+ }
+ val telephonyRepository: FakeTelephonyRepository by lazy { FakeTelephonyRepository() }
val communalRepository: FakeCommunalRepository by lazy { FakeCommunalRepository() }
val keyguardRepository: FakeKeyguardRepository by lazy { FakeKeyguardRepository() }
val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
- private val userRepository: UserRepository by lazy {
+ val userRepository: UserRepository by lazy {
FakeUserRepository().apply {
val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
setUserInfos(users)
@@ -183,7 +206,7 @@
featureFlags = featureFlags,
sceneContainerFlags = sceneContainerFlags,
bouncerRepository = FakeKeyguardBouncerRepository(),
- configurationRepository = FakeConfigurationRepository(),
+ configurationRepository = configurationRepository,
shadeRepository = FakeShadeRepository(),
sceneInteractorProvider = { sceneInteractor() },
powerInteractor = PowerInteractorFactory.create().powerInteractor,
@@ -217,6 +240,7 @@
fun bouncerViewModel(
bouncerInteractor: BouncerInteractor,
authenticationInteractor: AuthenticationInteractor,
+ actionButtonInteractor: BouncerActionButtonInteractor,
users: List<UserViewModel> = createUsers(),
): BouncerViewModel {
return BouncerViewModel(
@@ -229,13 +253,16 @@
selectedUser = flowOf(users.first { it.isSelectionMarkerVisible }),
users = flowOf(users),
userSwitcherMenu = flowOf(createMenuActions()),
- telephonyInteractor =
- TelephonyInteractor(
- repository = FakeTelephonyRepository(),
- ),
+ actionButtonInteractor = actionButtonInteractor,
)
}
+ fun telephonyInteractor(
+ repository: TelephonyRepository = telephonyRepository,
+ ): TelephonyInteractor {
+ return TelephonyInteractor(repository = repository)
+ }
+
fun falsingInteractor(collector: FalsingCollector = falsingCollector()): FalsingInteractor {
return falsingInteractor ?: FalsingInteractor(collector).also { falsingInteractor = it }
}
@@ -285,6 +312,40 @@
}
}
+ fun selectedUserInteractor(): SelectedUserInteractor {
+ return SelectedUserInteractor(userRepository, featureFlags)
+ }
+
+ fun bouncerActionButtonInteractor(
+ mobileConnectionsRepository: MobileConnectionsRepository = mock(),
+ activityTaskManager: ActivityTaskManager = mock(),
+ telecomManager: TelecomManager? = null,
+ emergencyAffordanceManager: EmergencyAffordanceManager =
+ EmergencyAffordanceManager(context),
+ emergencyDialerIntentFactory: EmergencyDialerIntentFactory =
+ object : EmergencyDialerIntentFactory {
+ override fun invoke(): Intent = Intent()
+ },
+ metricsLogger: MetricsLogger = mock(),
+ dozeLogger: DozeLogger = mock(),
+ ): BouncerActionButtonInteractor {
+ return BouncerActionButtonInteractor(
+ applicationContext = context,
+ backgroundDispatcher = testDispatcher,
+ repository = emergencyServicesRepository,
+ mobileConnectionsRepository = mobileConnectionsRepository,
+ telephonyInteractor = telephonyInteractor(),
+ authenticationInteractor = authenticationInteractor(),
+ selectedUserInteractor = selectedUserInteractor(),
+ activityTaskManager = activityTaskManager,
+ telecomManager = telecomManager,
+ emergencyAffordanceManager = emergencyAffordanceManager,
+ emergencyDialerIntentFactory = emergencyDialerIntentFactory,
+ metricsLogger = metricsLogger,
+ dozeLogger = dozeLogger,
+ )
+ }
+
companion object {
fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
return when (this) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
index 992ac62..5cde386 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
@@ -23,6 +23,7 @@
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@SysUISingleton
@@ -31,6 +32,9 @@
private val _callState = MutableStateFlow(0)
override val callState: Flow<Int> = _callState.asStateFlow()
+ private val _isInCall = MutableStateFlow(false)
+ override val isInCall: StateFlow<Boolean> = _isInCall.asStateFlow()
+
override var hasTelephonyRadio: Boolean = true
private set
@@ -38,8 +42,12 @@
_callState.value = value
}
- fun setHasRadio(hasRadio: Boolean) {
- this.hasTelephonyRadio = hasRadio
+ fun setIsInCall(isInCall: Boolean) {
+ _isInCall.value = isInCall
+ }
+
+ fun setHasTelephonyRadio(hasTelephonyRadio: Boolean) {
+ this.hasTelephonyRadio = hasTelephonyRadio
}
}