summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt83
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java31
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorKosmos.kt29
12 files changed, 279 insertions, 70 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
index 44782529e5c3..33a08035a7b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
@@ -86,6 +86,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.UserLogoutInteractor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wakelock.WakeLockFake;
@@ -160,6 +161,8 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase {
@Mock
protected DeviceEntryFingerprintAuthInteractor mDeviceEntryFingerprintAuthInteractor;
@Mock
+ protected UserLogoutInteractor mUserLogoutInteractor;
+ @Mock
protected ScreenLifecycle mScreenLifecycle;
@Mock
protected AuthController mAuthController;
@@ -248,6 +251,9 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase {
when(mFaceHelpMessageDeferralFactory.create()).thenReturn(mFaceHelpMessageDeferral);
when(mDeviceEntryFingerprintAuthInteractor.isEngaged()).thenReturn(mock(StateFlow.class));
+ StateFlow mockLogoutEnabledFlow = mock(StateFlow.class);
+ when(mockLogoutEnabledFlow.getValue()).thenReturn(false);
+ when(mUserLogoutInteractor.isLogoutEnabled()).thenReturn(mockLogoutEnabledFlow);
mIndicationHelper = new IndicationHelper(mKeyguardUpdateMonitor);
@@ -291,7 +297,8 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase {
KeyguardInteractorFactory.create(mFlags).getKeyguardInteractor(),
mBiometricMessageInteractor,
mDeviceEntryFingerprintAuthInteractor,
- mDeviceEntryFaceAuthInteractor
+ mDeviceEntryFaceAuthInteractor,
+ mUserLogoutInteractor
);
mController.init();
mController.setIndicationArea(mIndicationArea);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index b03c679a9c23..6b371d74eacc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.user.data.repository
+import android.app.admin.devicePolicyManager
import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserManager
@@ -24,6 +25,7 @@ import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
@@ -57,6 +59,8 @@ class UserRepositoryImplTest : SysuiTestCase() {
private val testDispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
private val globalSettings = kosmos.fakeGlobalSettings
+ private val broadcastDispatcher = kosmos.broadcastDispatcher
+ private val devicePolicyManager = kosmos.devicePolicyManager
@Mock private lateinit var manager: UserManager
@@ -317,6 +321,8 @@ class UserRepositoryImplTest : SysuiTestCase() {
backgroundDispatcher = testDispatcher,
globalSettings = globalSettings,
tracker = tracker,
+ broadcastDispatcher = broadcastDispatcher,
+ devicePolicyManager = devicePolicyManager,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt
new file mode 100644
index 000000000000..26439df45ba3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.domain.interactor
+
+import android.content.pm.UserInfo
+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.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+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 UserLogoutInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val userRepository = kosmos.fakeUserRepository
+ private val testScope = kosmos.testScope
+
+ private val underTest = kosmos.userLogoutInteractor
+
+ @Before
+ fun setUp() {
+ userRepository.setUserInfos(USER_INFOS)
+ runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[1]) }
+ }
+
+ @Test
+ fun logOut_doesNothing_whenAdminDisabledSecondaryLogout() {
+ testScope.runTest {
+ val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
+ val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+ userRepository.setSecondaryUserLogoutEnabled(false)
+ assertThat(isLogoutEnabled).isFalse()
+ underTest.logOut()
+ assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount)
+ }
+ }
+
+ @Test
+ fun logOut_logsOut_whenAdminEnabledSecondaryLogout() {
+ testScope.runTest {
+ val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
+ val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+ userRepository.setSecondaryUserLogoutEnabled(true)
+ assertThat(isLogoutEnabled).isTrue()
+ underTest.logOut()
+ assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1)
+ }
+ }
+
+ companion object {
+ private val USER_INFOS =
+ listOf(UserInfo(0, "System user", 0), UserInfo(10, "Regular user", 0))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index eda07cfe8d91..b40e07019694 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -219,7 +219,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int MSG_USER_UNLOCKED = 334;
private static final int MSG_ASSISTANT_STACK_CHANGED = 335;
private static final int MSG_BIOMETRIC_AUTHENTICATION_CONTINUE = 336;
- private static final int MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED = 337;
private static final int MSG_TELEPHONY_CAPABLE = 338;
private static final int MSG_TIMEZONE_UPDATE = 339;
private static final int MSG_USER_STOPPED = 340;
@@ -402,7 +401,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
private boolean mFingerprintDetectRunning;
private boolean mIsDreaming;
- private boolean mLogoutEnabled;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private final FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
@@ -1739,9 +1737,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
} else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
mHandler.sendEmptyMessage(MSG_SIM_SUBSCRIPTION_INFO_CHANGED);
- } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
- action)) {
- mHandler.sendEmptyMessage(MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED);
}
}
};
@@ -2328,9 +2323,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
break;
- case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
- updateLogoutEnabled();
- break;
case MSG_TELEPHONY_CAPABLE:
updateTelephonyCapable((boolean) msg.obj);
break;
@@ -2496,7 +2488,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
boolean isUserUnlocked = mUserManager.isUserUnlocked(user);
mLogger.logUserUnlockedInitialState(user, isUserUnlocked);
mUserIsUnlocked.put(user, isUserUnlocked);
- mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
updateSecondaryLockscreenRequirement(user);
List<UserInfo> allUsers = mUserManager.getUsers();
for (UserInfo userInfo : allUsers) {
@@ -4062,28 +4053,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return null; // not found
}
- /**
- * @return a cached version of DevicePolicyManager.isLogoutEnabled()
- */
- public boolean isLogoutEnabled() {
- return mLogoutEnabled;
- }
-
- private void updateLogoutEnabled() {
- Assert.isMainThread();
- boolean logoutEnabled = mDevicePolicyManager.isLogoutEnabled();
- if (mLogoutEnabled != logoutEnabled) {
- mLogoutEnabled = logoutEnabled;
-
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onLogoutEnabledChanged();
- }
- }
- }
- }
-
protected int getBiometricLockoutDelay() {
return BIOMETRIC_LOCKOUT_RESET_DELAY_MS;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7ac5ac229793..fdee21bcc479 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -286,11 +286,6 @@ public class KeyguardUpdateMonitorCallback {
public void onTrustAgentErrorMessage(CharSequence message) { }
/**
- * Called when a value of logout enabled is change.
- */
- public void onLogoutEnabledChanged() { }
-
- /**
* Called when authenticated fingerprint biometrics are cleared.
*/
public void onFingerprintsCleared() { }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 162047bb3b79..91b44e7a6202 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -36,7 +36,6 @@ import android.app.Dialog;
import android.app.IActivityManager;
import android.app.StatusBarManager;
import android.app.WallpaperManager;
-import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -138,6 +137,7 @@ import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserLogoutInteractor;
import com.android.systemui.util.EmergencyDialerConstants;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.settings.GlobalSettings;
@@ -197,7 +197,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private final Context mContext;
private final GlobalActionsManager mWindowManagerFuncs;
private final AudioManager mAudioManager;
- private final DevicePolicyManager mDevicePolicyManager;
private final LockPatternUtils mLockPatternUtils;
private final SelectedUserInteractor mSelectedUserInteractor;
private final TelephonyListenerManager mTelephonyListenerManager;
@@ -260,6 +259,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private final ShadeController mShadeController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DialogTransitionAnimator mDialogTransitionAnimator;
+ private final UserLogoutInteractor mLogoutInteractor;
private final GlobalActionsInteractor mInteractor;
@VisibleForTesting
@@ -344,7 +344,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
Context context,
GlobalActionsManager windowManagerFuncs,
AudioManager audioManager,
- DevicePolicyManager devicePolicyManager,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
TelephonyListenerManager telephonyListenerManager,
@@ -376,11 +375,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
KeyguardUpdateMonitor keyguardUpdateMonitor,
DialogTransitionAnimator dialogTransitionAnimator,
SelectedUserInteractor selectedUserInteractor,
+ UserLogoutInteractor logoutInteractor,
GlobalActionsInteractor interactor) {
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
- mDevicePolicyManager = devicePolicyManager;
mLockPatternUtils = lockPatternUtils;
mTelephonyListenerManager = telephonyListenerManager;
mKeyguardStateController = keyguardStateController;
@@ -412,6 +411,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mDialogTransitionAnimator = dialogTransitionAnimator;
mSelectedUserInteractor = selectedUserInteractor;
+ mLogoutInteractor = logoutInteractor;
mInteractor = interactor;
// receive broadcasts
@@ -639,12 +639,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
} else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
addIfShouldShowAction(tempActions, new ScreenshotAction());
} else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
- // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of
- // hardcode it to USER_SYSTEM so it properly supports headless system user mode
- // (and then call mDevicePolicyManager.clearLogoutUser() after switched)
- if (mDevicePolicyManager.isLogoutEnabled()
- && currentUser.get() != null
- && currentUser.get().id != UserHandle.USER_SYSTEM) {
+ if (mLogoutInteractor.isLogoutEnabled().getValue()) {
addIfShouldShowAction(tempActions, new LogoutAction());
}
} else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
@@ -1134,7 +1129,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
// Add a little delay before executing, to give the dialog a chance to go away before
// switching user
mHandler.postDelayed(() -> {
- mDevicePolicyManager.logoutUser();
+ mLogoutInteractor.logOut();
}, mDialogPressDelay);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 8c5a711d6a75..a5595edcbb95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -116,6 +116,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.UserLogoutInteractor;
import com.android.systemui.util.AlarmTimeout;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.wakelock.SettableWakeLock;
@@ -162,6 +163,7 @@ public class KeyguardIndicationController {
private final KeyguardLogger mKeyguardLogger;
private final UserTracker mUserTracker;
private final BouncerMessageInteractor mBouncerMessageInteractor;
+
private ViewGroup mIndicationArea;
private KeyguardIndicationTextView mTopIndicationView;
private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -187,6 +189,7 @@ public class KeyguardIndicationController {
private final BiometricMessageInteractor mBiometricMessageInteractor;
private DeviceEntryFingerprintAuthInteractor mDeviceEntryFingerprintAuthInteractor;
private DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
+ private final UserLogoutInteractor mUserLogoutInteractor;
private String mPersistentUnlockMessage;
private String mAlignmentIndication;
private boolean mForceIsDismissible;
@@ -237,6 +240,13 @@ public class KeyguardIndicationController {
showTrustAgentErrorMessage(mTrustAgentErrorMessage);
}
};
+ @VisibleForTesting
+ final Consumer<Boolean> mIsLogoutEnabledCallback =
+ (Boolean isLogoutEnabled) -> {
+ if (mVisible) {
+ updateDeviceEntryIndication(false);
+ }
+ };
private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurnedOn() {
@@ -299,7 +309,8 @@ public class KeyguardIndicationController {
KeyguardInteractor keyguardInteractor,
BiometricMessageInteractor biometricMessageInteractor,
DeviceEntryFingerprintAuthInteractor deviceEntryFingerprintAuthInteractor,
- DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor
+ DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
+ UserLogoutInteractor userLogoutInteractor
) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
@@ -331,6 +342,8 @@ public class KeyguardIndicationController {
mBiometricMessageInteractor = biometricMessageInteractor;
mDeviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor;
mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
+ mUserLogoutInteractor = userLogoutInteractor;
+
mFaceAcquiredMessageDeferral = faceHelpMessageDeferral.create();
@@ -418,6 +431,9 @@ public class KeyguardIndicationController {
mCoExAcquisitionMsgIdsToShowCallback);
collectFlow(mIndicationArea, mDeviceEntryFingerprintAuthInteractor.isEngaged(),
mIsFingerprintEngagedCallback);
+ collectFlow(mIndicationArea,
+ mUserLogoutInteractor.isLogoutEnabled(),
+ mIsLogoutEnabledCallback);
}
/**
@@ -744,9 +760,7 @@ public class KeyguardIndicationController {
}
private void updateLockScreenLogoutView() {
- final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
- && getCurrentUser() != UserHandle.USER_SYSTEM;
- if (shouldShowLogout) {
+ if (mUserLogoutInteractor.isLogoutEnabled().getValue()) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_LOGOUT,
new KeyguardIndication.Builder()
@@ -760,7 +774,7 @@ public class KeyguardIndicationController {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) {
return;
}
- mDevicePolicyManager.logoutUser();
+ mUserLogoutInteractor.logOut();
})
.build(),
false);
@@ -1515,13 +1529,6 @@ public class KeyguardIndicationController {
}
@Override
- public void onLogoutEnabledChanged() {
- if (mVisible) {
- updateDeviceEntryIndication(false);
- }
- }
-
- @Override
public void onRequireUnlockForNfc() {
showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc));
hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 493aa8c11b18..f20ce63467f7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -19,12 +19,16 @@ package com.android.systemui.user.data.repository
import android.annotation.SuppressLint
import android.annotation.UserIdInt
+import android.app.admin.DevicePolicyManager
import android.content.Context
+import android.content.IntentFilter
import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -38,6 +42,7 @@ import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -49,11 +54,12 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@@ -100,6 +106,9 @@ interface UserRepository {
/** Whether refresh users should be paused. */
var isRefreshUsersPaused: Boolean
+ /** Whether logout for secondary users is enabled by admin device policy. */
+ val isSecondaryUserLogoutEnabled: StateFlow<Boolean>
+
/** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */
fun refreshUsers()
@@ -109,6 +118,9 @@ interface UserRepository {
fun isUserSwitcherEnabled(): Boolean
+ /** Performs logout logout for secondary users. */
+ suspend fun logOutSecondaryUser()
+
/**
* Returns the user ID of the "main user" of the device. This user may have access to certain
* features which are limited to at most one user. There will never be more than one main user
@@ -137,6 +149,8 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val globalSettings: GlobalSettings,
private val tracker: UserTracker,
+ private val devicePolicyManager: DevicePolicyManager,
+ private val broadcastDispatcher: BroadcastDispatcher,
) : UserRepository {
private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> =
@@ -147,7 +161,7 @@ constructor(
SETTING_SIMPLE_USER_SWITCHER,
Settings.Global.ADD_USERS_WHEN_LOCKED,
Settings.Global.USER_SWITCHER_ENABLED,
- ),
+ )
)
.onStart { emit(Unit) } // Forces an initial update.
.map { getSettings() }
@@ -163,6 +177,7 @@ constructor(
override var mainUserId: Int = UserHandle.USER_NULL
private set
+
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL
private set
@@ -221,12 +236,51 @@ constructor(
.stateIn(
applicationScope,
SharingStarted.Eagerly,
- initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus)
+ initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus),
)
}
override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
+ /** Whether the secondary user logout is enabled by the admin device policy. */
+ private val isSecondaryUserLogoutSupported: Flow<Boolean> =
+ broadcastDispatcher
+ .broadcastFlow(
+ filter =
+ IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
+ ) { intent, _ ->
+ if (
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED == intent.action
+ ) {
+ Unit
+ } else {
+ null
+ }
+ }
+ .filterNotNull()
+ .onStart { emit(Unit) }
+ .map { _ -> devicePolicyManager.isLogoutEnabled() }
+ .flowOn(backgroundDispatcher)
+
+ @SuppressLint("MissingPermission")
+ override val isSecondaryUserLogoutEnabled: StateFlow<Boolean> =
+ selectedUser
+ .flatMapLatestConflated { selectedUser ->
+ if (selectedUser.isEligibleForLogout()) {
+ isSecondaryUserLogoutSupported
+ } else {
+ flowOf(false)
+ }
+ }
+ .stateIn(applicationScope, SharingStarted.Eagerly, false)
+
+ @SuppressLint("MissingPermission")
+ override suspend fun logOutSecondaryUser() {
+ if (isSecondaryUserLogoutEnabled.value) {
+ withContext(backgroundDispatcher) { devicePolicyManager.logoutUser() }
+ }
+ }
+
@SuppressLint("MissingPermission")
override fun refreshUsers() {
applicationScope.launch {
@@ -277,10 +331,7 @@ constructor(
) != 0
val isAddUsersFromLockscreen =
- globalSettings.getInt(
- Settings.Global.ADD_USERS_WHEN_LOCKED,
- 0,
- ) != 0
+ globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0
val isUserSwitcherEnabled =
globalSettings.getInt(
@@ -309,3 +360,11 @@ constructor(
@VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
}
}
+
+fun SelectedUserModel.isEligibleForLogout(): Boolean {
+ // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of
+ // hardcode it to USER_SYSTEM so it properly supports headless system user mode
+ // (and then call mDevicePolicyManager.clearLogoutUser() after switched)
+ return selectionStatus == SelectionStatus.SELECTION_COMPLETE &&
+ userInfo.id != android.os.UserHandle.USER_SYSTEM
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt
new file mode 100644
index 000000000000..154f1dc3e747
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.domain.interactor
+
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.StateFlow
+
+/** Encapsulates business logic to for the logout. */
+@SysUISingleton
+class UserLogoutInteractor
+@Inject
+constructor(
+ private val userRepository: UserRepository,
+ @Application private val applicationScope: CoroutineScope,
+) {
+ val isLogoutEnabled: StateFlow<Boolean> = userRepository.isSecondaryUserLogoutEnabled
+
+ fun logOut() {
+ if (userRepository.isSecondaryUserLogoutEnabled.value) {
+ applicationScope.launch { userRepository.logOutSecondaryUser() }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index df50f765349c..24bca70fd41f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -31,7 +31,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -80,6 +79,7 @@ import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserLogoutInteractor;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.settings.FakeGlobalSettings;
@@ -106,7 +106,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
@Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs;
@Mock private AudioManager mAudioManager;
- @Mock private DevicePolicyManager mDevicePolicyManager;
@Mock private LockPatternUtils mLockPatternUtils;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private TelephonyListenerManager mTelephonyListenerManager;
@@ -140,6 +139,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private DialogTransitionAnimator mDialogTransitionAnimator;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
+ @Mock private UserLogoutInteractor mLogoutInteractor;
@Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
@Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
@@ -166,7 +166,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
mWindowManagerFuncs,
mAudioManager,
- mDevicePolicyManager,
mLockPatternUtils,
mBroadcastDispatcher,
mTelephonyListenerManager,
@@ -198,6 +197,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mKeyguardUpdateMonitor,
mDialogTransitionAnimator,
mSelectedUserInteractor,
+ mLogoutInteractor,
mInteractor);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index ed335f9a1834..1808a5f99f4e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.yield
@@ -67,6 +68,10 @@ class FakeUserRepository @Inject constructor() : UserRepository {
)
override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
+ private val _isSecondaryUserLogoutEnabled = MutableStateFlow<Boolean>(false)
+ override val isSecondaryUserLogoutEnabled: StateFlow<Boolean> =
+ _isSecondaryUserLogoutEnabled.asStateFlow()
+
override var mainUserId: Int = MAIN_USER_ID
override var lastSelectedNonGuestUserId: Int = mainUserId
@@ -107,6 +112,17 @@ class FakeUserRepository @Inject constructor() : UserRepository {
return _userSwitcherSettings.value.isUserSwitcherEnabled
}
+ fun setSecondaryUserLogoutEnabled(logoutEnabled: Boolean) {
+ _isSecondaryUserLogoutEnabled.value = logoutEnabled
+ }
+
+ var logOutSecondaryUserCallCount: Int = 0
+ private set
+
+ override suspend fun logOutSecondaryUser() {
+ logOutSecondaryUserCallCount++
+ }
+
fun setUserInfos(infos: List<UserInfo>) {
_userInfos.value = infos
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorKosmos.kt
new file mode 100644
index 000000000000..d06e74468d3e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.user.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.user.data.repository.userRepository
+
+val Kosmos.userLogoutInteractor by
+ Kosmos.Fixture {
+ UserLogoutInteractor(
+ userRepository = userRepository,
+ applicationScope = applicationCoroutineScope,
+ )
+ }