Merge "Added UserVisibilityListener logic to UserVisibilityMediator."

This commit is contained in:
Felipe Leme
2022-11-20 01:44:40 +00:00
committed by Android (Google) Code Review
8 changed files with 582 additions and 70 deletions

View File

@@ -634,7 +634,7 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mUserStates")
private final WatchedUserStates mUserStates = new WatchedUserStates();
private final UserVisibilityMediator mUserVisibilityMediator = new UserVisibilityMediator();
private final UserVisibilityMediator mUserVisibilityMediator;
private static UserManagerService sInstance;
@@ -733,6 +733,7 @@ public class UserManagerService extends IUserManager.Stub {
mPackagesLock = packagesLock;
mUsers = users != null ? users : new SparseArray<>();
mHandler = new MainHandler();
mUserVisibilityMediator = new UserVisibilityMediator(mHandler);
mUserDataPreparer = userDataPreparer;
mUserTypes = UserTypeFactory.getUserTypes();
invalidateOwnerNameIfNecessary(context.getResources(), true /* forceUpdate */);

View File

@@ -28,6 +28,7 @@ import static com.android.server.pm.UserManagerInternal.userAssignmentResultToSt
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Dumpable;
@@ -40,9 +41,11 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.android.server.utils.Slogf;
import java.io.PrintWriter;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Class responsible for deciding whether a user is visible (or visible for a given display).
@@ -110,15 +113,24 @@ public final class UserVisibilityMediator implements Dumpable {
@GuardedBy("mLock")
private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray();
UserVisibilityMediator() {
this(UserManager.isUsersOnSecondaryDisplaysEnabled());
/**
* Handler user to call listeners
*/
private final Handler mHandler;
// @GuardedBy("mLock") - hold lock for writes, no lock necessary for simple reads
final CopyOnWriteArrayList<UserVisibilityListener> mListeners =
new CopyOnWriteArrayList<>();
UserVisibilityMediator(Handler handler) {
this(UserManager.isUsersOnSecondaryDisplaysEnabled(), handler);
}
@VisibleForTesting
UserVisibilityMediator(boolean usersOnSecondaryDisplaysEnabled) {
UserVisibilityMediator(boolean usersOnSecondaryDisplaysEnabled, Handler handler) {
mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
mUsersOnSecondaryDisplays = mUsersOnSecondaryDisplaysEnabled ? new SparseIntArray() : null;
mHandler = handler;
// TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
mStartedProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID);
}
@@ -151,6 +163,7 @@ public final class UserVisibilityMediator implements Dumpable {
}
int result;
IntArray visibleUsersBefore, visibleUsersAfter;
synchronized (mLock) {
result = getUserVisibilityOnStartLocked(userId, profileGroupId, foreground, displayId);
if (DBG) {
@@ -166,6 +179,8 @@ public final class UserVisibilityMediator implements Dumpable {
return USER_ASSIGNMENT_RESULT_FAILURE;
}
visibleUsersBefore = getVisibleUsers();
// Set current user / profiles state
if (foreground) {
mCurrentUserId = userId;
@@ -195,8 +210,12 @@ public final class UserVisibilityMediator implements Dumpable {
Slogf.wtf(TAG, "invalid resut from canAssignUserToDisplayLocked: %d",
mappingResult);
}
visibleUsersAfter = getVisibleUsers();
}
dispatchVisibilityChanged(visibleUsersBefore, visibleUsersAfter);
if (DBG) {
Slogf.d(TAG, "returning %s", userAssignmentResultToString(result));
}
@@ -320,29 +339,40 @@ public final class UserVisibilityMediator implements Dumpable {
/**
* See {@link UserManagerInternal#unassignUserFromDisplayOnStop(int)}.
*/
public void unassignUserFromDisplayOnStop(int userId) {
public void unassignUserFromDisplayOnStop(@UserIdInt int userId) {
if (DBG) {
Slogf.d(TAG, "unassignUserFromDisplayOnStop(%d)", userId);
}
IntArray visibleUsersBefore, visibleUsersAfter;
synchronized (mLock) {
if (DBG) {
Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
mStartedProfileGroupIds);
}
mStartedProfileGroupIds.delete(userId);
visibleUsersBefore = getVisibleUsers();
if (!mUsersOnSecondaryDisplaysEnabled) {
// Don't need to do update mUsersOnSecondaryDisplays because methods (such as
// isUserVisible()) already know that the current user (and their profiles) is
// assigned to the default display.
return;
}
if (DBG) {
Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
mUsersOnSecondaryDisplays);
}
mUsersOnSecondaryDisplays.delete(userId);
unassignUserFromDisplayOnStopLocked(userId);
visibleUsersAfter = getVisibleUsers();
}
dispatchVisibilityChanged(visibleUsersBefore, visibleUsersAfter);
}
@GuardedBy("mLock")
private void unassignUserFromDisplayOnStopLocked(@UserIdInt int userId) {
if (DBG) {
Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
mStartedProfileGroupIds);
}
mStartedProfileGroupIds.delete(userId);
if (!mUsersOnSecondaryDisplaysEnabled) {
// Don't need to do update mUsersOnSecondaryDisplays because methods (such as
// isUserVisible()) already know that the current user (and their profiles) is
// assigned to the default display.
return;
}
if (DBG) {
Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
mUsersOnSecondaryDisplays);
}
mUsersOnSecondaryDisplays.delete(userId);
}
/**
@@ -351,18 +381,29 @@ public final class UserVisibilityMediator implements Dumpable {
public boolean isUserVisible(@UserIdInt int userId) {
// First check current foreground user and their profiles (on main display)
if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
if (DBG) {
Slogf.d(TAG, "isUserVisible(%d): true to current user or profile", userId);
}
return true;
}
// Device doesn't support multiple users on multiple displays, so only users checked above
// can be visible
if (!mUsersOnSecondaryDisplaysEnabled) {
if (DBG) {
Slogf.d(TAG, "isUserVisible(%d): false for non-current user on MUMD", userId);
}
return false;
}
boolean visible;
synchronized (mLock) {
return mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
visible = mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
}
if (DBG) {
Slogf.d(TAG, "isUserVisible(%d): %b from mapping", userId, visible);
}
return visible;
}
/**
@@ -481,6 +522,79 @@ public final class UserVisibilityMediator implements Dumpable {
return visibleUsers;
}
/**
* Adds a {@link UserVisibilityListener listener}.
*/
public void addListener(UserVisibilityListener listener) {
if (DBG) {
Slogf.d(TAG, "adding listener %s", listener);
}
synchronized (mLock) {
mListeners.add(listener);
}
}
/**
* Removes a {@link UserVisibilityListener listener}.
*/
public void removeListener(UserVisibilityListener listener) {
if (DBG) {
Slogf.d(TAG, "removing listener %s", listener);
}
synchronized (mLock) {
mListeners.remove(listener);
}
}
/**
* Nofify all listeners about the visibility changes from before / after a change of state.
*/
private void dispatchVisibilityChanged(IntArray visibleUsersBefore,
IntArray visibleUsersAfter) {
if (visibleUsersBefore == null) {
// Optimization - it's only null when listeners is empty
if (DBG) {
Slogf.d(TAG, "dispatchVisibilityChanged(): ignoring, no listeners");
}
return;
}
CopyOnWriteArrayList<UserVisibilityListener> listeners = mListeners;
if (DBG) {
Slogf.d(TAG,
"dispatchVisibilityChanged(): visibleUsersBefore=%s, visibleUsersAfter=%s, "
+ "%d listeners (%s)", visibleUsersBefore, visibleUsersAfter, listeners.size(),
mListeners);
}
for (int i = 0; i < visibleUsersBefore.size(); i++) {
int userId = visibleUsersBefore.get(i);
if (visibleUsersAfter.indexOf(userId) == -1) {
dispatchVisibilityChanged(listeners, userId, /* visible= */ false);
}
}
for (int i = 0; i < visibleUsersAfter.size(); i++) {
int userId = visibleUsersAfter.get(i);
if (visibleUsersBefore.indexOf(userId) == -1) {
dispatchVisibilityChanged(listeners, userId, /* visible= */ true);
}
}
}
private void dispatchVisibilityChanged(CopyOnWriteArrayList<UserVisibilityListener> listeners,
@UserIdInt int userId, boolean visible) {
if (DBG) {
Slogf.d(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %d listeners",
userId, visible, listeners.size());
}
for (int i = 0; i < mListeners.size(); i++) {
UserVisibilityListener listener = mListeners.get(i);
if (DBG) {
Slogf.v(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %s",
userId, visible, listener);
}
mHandler.post(() -> listener.onUserVisibilityChanged(userId, visible));
}
}
private void dump(IndentingPrintWriter ipw) {
ipw.println("UserVisibilityMediator");
ipw.increaseIndent();
@@ -504,6 +618,18 @@ public final class UserVisibilityMediator implements Dumpable {
dumpSparseIntArray(ipw, mUsersOnSecondaryDisplays,
"background user / secondary display", "u", "d");
}
int numberListeners = mListeners.size();
ipw.print("Number of listeners: ");
ipw.println(numberListeners);
if (numberListeners > 0) {
ipw.increaseIndent();
for (int i = 0; i < numberListeners; i++) {
ipw.print(i);
ipw.print(": ");
ipw.println(mListeners.get(i));
}
ipw.decreaseIndent();
}
}
ipw.decreaseIndent();

View File

@@ -43,7 +43,7 @@ public abstract class ExtendedMockitoTestCase {
private MockitoSession mSession;
private final Expect mExpect = Expect.create();
protected final Expect mExpect = Expect.create();
protected final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule();
@Rule

View File

@@ -0,0 +1,184 @@
/*
* 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.server.pm;
import static org.junit.Assert.fail;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.google.common.truth.Expect;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* {@link UserVisibilityListener} implementation that expects callback events to be asynchronously
* received.
*/
public final class AsyncUserVisibilityListener implements UserVisibilityListener {
private static final String TAG = AsyncUserVisibilityListener.class.getSimpleName();
private static final long WAIT_TIMEOUT_MS = 2_000;
private static final long WAIT_NO_EVENTS_TIMEOUT_MS = 1_000;
private static int sNextId;
private final Object mLock = new Object();
private final Expect mExpect;
private final int mId = ++sNextId;
private final Thread mExpectedReceiverThread;
private final CountDownLatch mLatch;
private final List<UserVisibilityChangedEvent> mExpectedEvents;
@GuardedBy("mLock")
private final List<UserVisibilityChangedEvent> mReceivedEvents = new ArrayList<>();
@GuardedBy("mLock")
private final List<String> mErrors = new ArrayList<>();
private AsyncUserVisibilityListener(Expect expect, Thread expectedReceiverThread,
List<UserVisibilityChangedEvent> expectedEvents) {
mExpect = expect;
mExpectedReceiverThread = expectedReceiverThread;
mExpectedEvents = expectedEvents;
mLatch = new CountDownLatch(expectedEvents.size());
}
@Override
public void onUserVisibilityChanged(int userId, boolean visible) {
UserVisibilityChangedEvent event = new UserVisibilityChangedEvent(userId, visible);
Thread callingThread = Thread.currentThread();
Log.d(TAG, "Received event (" + event + ") on thread " + callingThread);
if (callingThread != mExpectedReceiverThread) {
addError("event %s received in on thread %s but was expected on thread %s",
event, callingThread, mExpectedReceiverThread);
}
synchronized (mLock) {
mReceivedEvents.add(event);
mLatch.countDown();
}
}
/**
* Verifies the expected events were called.
*/
public void verify() throws InterruptedException {
waitForEventsAndCheckErrors();
List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
if (receivedEvents.isEmpty()) {
mExpect.withMessage("received events").that(receivedEvents).isEmpty();
return;
}
// NOTE: check "inOrder" might be too harsh in some cases (for example, if the fg user
// has 2 profiles, the order of the events on the profiles wouldn't matter), but we
// still need some dependency (like "user A became invisible before user B became
// visible", so this is fine for now (but eventually we might need to add more
// sophisticated assertions)
mExpect.withMessage("received events").that(receivedEvents)
.containsExactlyElementsIn(mExpectedEvents).inOrder();
}
@Override
public String toString() {
List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
return "[" + getClass().getSimpleName() + ": id=" + mId
+ ", creationThread=" + mExpectedReceiverThread
+ ", received=" + receivedEvents.size()
+ ", events=" + receivedEvents + "]";
}
private List<UserVisibilityChangedEvent> getReceivedEvents() {
synchronized (mLock) {
return Collections.unmodifiableList(mReceivedEvents);
}
}
private void waitForEventsAndCheckErrors() throws InterruptedException {
waitForEvents();
synchronized (mLock) {
if (!mErrors.isEmpty()) {
fail(mErrors.size() + " errors on received events: " + mErrors);
}
}
}
private void waitForEvents() throws InterruptedException {
if (mExpectedEvents.isEmpty()) {
Log.v(TAG, "Sleeping " + WAIT_NO_EVENTS_TIMEOUT_MS + "ms to make sure no event is "
+ "received");
Thread.sleep(WAIT_NO_EVENTS_TIMEOUT_MS);
return;
}
int expectedNumberEvents = mExpectedEvents.size();
Log.v(TAG, "Waiting up to " + WAIT_TIMEOUT_MS + "ms until " + expectedNumberEvents
+ " events are received");
if (!mLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
addError("Timed out (%d ms) waiting for %d events; received %d so far (%s), "
+ "but expecting %d (%s)", WAIT_NO_EVENTS_TIMEOUT_MS, expectedNumberEvents,
receivedEvents.size(), receivedEvents, expectedNumberEvents, mExpectedEvents);
}
}
@SuppressWarnings("AnnotateFormatMethod")
private void addError(String format, Object...args) {
synchronized (mLock) {
mErrors.add(String.format(format, args));
}
}
/**
* Factory for {@link AsyncUserVisibilityListener} objects.
*/
public static final class Factory {
private final Expect mExpect;
private final Thread mExpectedReceiverThread;
public Factory(Expect expect, Thread expectedReceiverThread) {
mExpect = expect;
mExpectedReceiverThread = expectedReceiverThread;
}
/**
* Creates a {@link AsyncUserVisibilityListener} that is expecting the given events.
*/
public AsyncUserVisibilityListener forEvents(UserVisibilityChangedEvent...expectedEvents) {
return new AsyncUserVisibilityListener(mExpect, mExpectedReceiverThread,
Arrays.asList(expectedEvents));
}
/**
* Creates a {@link AsyncUserVisibilityListener} that is expecting no events.
*/
public AsyncUserVisibilityListener forNoEvents() {
return new AsyncUserVisibilityListener(mExpect, mExpectedReceiverThread,
Collections.emptyList());
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.server.pm;
import android.annotation.UserIdInt;
/**
* Representation of a {@link UserManagerInternal.UserVisibilityListener} event.
*/
public final class UserVisibilityChangedEvent {
public @UserIdInt int userId;
public boolean visible;
UserVisibilityChangedEvent(@UserIdInt int userId, boolean visible) {
this.userId = userId;
this.visible = visible;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + userId;
result = prime * result + (visible ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
UserVisibilityChangedEvent other = (UserVisibilityChangedEvent) obj;
if (userId != other.userId) return false;
if (visible != other.visible) return false;
return true;
}
@Override
public String toString() {
return userId + ":" + (visible ? "visible" : "invisible");
}
/**
* Factory method.
*/
public static UserVisibilityChangedEvent onVisible(@UserIdInt int userId) {
return new UserVisibilityChangedEvent(userId, /* visible= */ true);
}
/**
* Factory method.
*/
public static UserVisibilityChangedEvent onInvisible(@UserIdInt int userId) {
return new UserVisibilityChangedEvent(userId, /* visible= */ false);
}
}

View File

@@ -21,6 +21,7 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
import org.junit.Test;
@@ -34,28 +35,38 @@ import org.junit.Test;
*/
public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediatorTestCase {
public UserVisibilityMediatorMUMDTest() {
public UserVisibilityMediatorMUMDTest() throws Exception {
super(/* usersOnSecondaryDisplaysEnabled= */ true);
}
@Test
public void testStartFgUser_onInvalidDisplay() {
public void testStartFgUser_onInvalidDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, INVALID_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
listener.verify();
}
@Test
public void testStartBgUser_onInvalidDisplay() {
public void testStartBgUser_onInvalidDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG, INVALID_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
expectUserIsNotVisibleAtAll(USER_ID);
listener.verify();
}
@Test
public void testStartBgUser_onSecondaryDisplay_displayAvailable() {
public void testStartBgUser_onSecondaryDisplay_displayAvailable() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
@@ -68,10 +79,13 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
expectDisplayAssignedToUser(USER_ID, SECONDARY_DISPLAY_ID);
expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
listener.verify();
}
@Test
public void testVisibilityOfCurrentUserAndProfilesOnDisplayAssignedToAnotherUser() {
public void testVisibilityOfCurrentUserAndProfilesOnDisplayAssignedToAnotherUser()
throws Exception {
startDefaultProfile();
// Make sure they were visible before
@@ -87,7 +101,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
}
@Test
public void testStartBgUser_onSecondaryDisplay_displayAlreadyAssigned() {
public void testStartBgUser_onSecondaryDisplay_displayAlreadyAssigned() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(OTHER_USER_ID));
startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
@@ -97,10 +112,13 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
expectUserIsNotVisibleAtAll(USER_ID);
expectNoDisplayAssignedToUser(USER_ID);
expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, OTHER_USER_ID);
listener.verify();
}
@Test
public void testStartBgUser_onSecondaryDisplay_userAlreadyAssigned() {
public void testStartBgUser_onSecondaryDisplay_userAlreadyAssigned() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
@@ -116,10 +134,14 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
expectDisplayAssignedToUser(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, USER_ID);
listener.verify();
}
@Test
public void testStartBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay() {
public void testStartBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay()
throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
@@ -129,5 +151,7 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
listener.verify();
}
}

View File

@@ -17,8 +17,6 @@ package com.android.server.pm;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
import android.annotation.UserIdInt;
import org.junit.Test;
/**
@@ -35,26 +33,18 @@ public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediator
}
@Test
public void testStartBgUser_onSecondaryDisplay() {
startUserInBackgroundOnSecondaryDisplayAndAssertFailure(USER_ID, USER_ID);
public void testStartBgUser_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
}
@Test
public void testStartBgProfileUser_onSecondaryDisplay() {
startForegroundUser(PARENT_USER_ID);
startUserInBackgroundOnSecondaryDisplayAndAssertFailure(PROFILE_USER_ID, PARENT_USER_ID);
}
private void startUserInBackgroundOnSecondaryDisplayAndAssertFailure(@UserIdInt int userId,
@UserIdInt int profileGroupId) {
int result = mMediator.assignUserToDisplayOnStart(userId, profileGroupId, BG,
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
expectUserIsNotVisibleAtAll(userId);
expectNoDisplayAssignedToUser(userId);
expectUserIsNotVisibleAtAll(USER_ID);
expectNoDisplayAssignedToUser(USER_ID);
expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
listener.verify();
}
}

View File

@@ -27,6 +27,8 @@ import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_F
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -34,13 +36,16 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import android.annotation.UserIdInt;
import android.os.HandlerThread;
import android.util.IntArray;
import android.util.Log;
import com.android.internal.util.Preconditions;
import com.android.server.ExtendedMockitoTestCase;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Arrays;
@@ -98,6 +103,11 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
protected static final boolean FG = true;
protected static final boolean BG = false;
private static final HandlerThread sHandlerThread = new HandlerThread(TAG);
protected final AsyncUserVisibilityListener.Factory mListenerFactory =
new AsyncUserVisibilityListener.Factory(mExpect, sHandlerThread);
private final boolean mUsersOnSecondaryDisplaysEnabled;
protected UserVisibilityMediator mMediator;
@@ -106,9 +116,24 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
}
@BeforeClass
public static final void startHandlerThread() {
Log.d(TAG, "Starting handler thread " + sHandlerThread);
sHandlerThread.start();
}
@AfterClass
public static final void quitHandlerThread() {
Log.d(TAG, "Quitting handler thread " + sHandlerThread);
if (!sHandlerThread.quit()) {
Log.w(TAG, "sHandlerThread(" + sHandlerThread + ").quit() returned false");
}
}
@Before
public final void setMediator() {
mMediator = new UserVisibilityMediator(mUsersOnSecondaryDisplaysEnabled);
public final void setFixtures() {
mMediator = new UserVisibilityMediator(mUsersOnSecondaryDisplaysEnabled,
sHandlerThread.getThreadHandler());
mDumpableDumperRule.addDumpable(mMediator);
}
@@ -125,7 +150,11 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
}
@Test
public final void testStartFgUser_onDefaultDisplay() {
public final void testStartFgUser_onDefaultDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
onVisible(USER_ID));
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
@@ -144,12 +173,19 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
listener.verify();
}
@Test
public final void testSwitchFgUser_onDefaultDisplay() {
public final void testSwitchFgUser_onDefaultDisplay() throws Exception {
int previousCurrentUserId = OTHER_USER_ID;
int currentUserId = USER_ID;
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
onVisible(previousCurrentUserId),
onInvisible(previousCurrentUserId),
onVisible(currentUserId));
startForegroundUser(previousCurrentUserId);
int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
@@ -169,22 +205,29 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectUserIsNotVisibleAtAll(previousCurrentUserId);
expectNoDisplayAssignedToUser(previousCurrentUserId);
listener.verify();
}
@Test
public final void testStartFgUser_onSecondaryDisplay() {
int userId = USER_ID;
public final void testStartFgUser_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(userId, userId, FG, SECONDARY_DISPLAY_ID);
int result =
mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
expectUserIsNotVisibleAtAll(userId);
expectUserIsNotVisibleAtAll(USER_ID);
expectNoDisplayAssignedToUser(USER_ID);
expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
listener.verify();
}
@Test
public final void testStartBgUser_onDefaultDisplay() {
public final void testStartBgUser_onDefaultDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
@@ -192,10 +235,15 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectUserIsNotVisibleAtAll(USER_ID);
expectNoDisplayAssignedToUser(USER_ID);
expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
listener.verify();
}
@Test
public final void testStartBgSystemUser_onSecondaryDisplay() {
public final void testStartBgSystemUser_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
onVisible(USER_ID));
// Must explicitly set current user, as USER_SYSTEM is the default current user
startForegroundUser(USER_ID);
@@ -207,10 +255,17 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectNoDisplayAssignedToUser(USER_SYSTEM);
expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
listener.verify();
}
@Test
public final void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() {
public final void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
onVisible(PARENT_USER_ID),
onVisible(PROFILE_USER_ID));
startForegroundUser(PARENT_USER_ID);
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
@@ -226,10 +281,16 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
listener.verify();
}
@Test
public final void testStopVisibleProfile() {
public final void testStopVisibleProfile() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
onVisible(PARENT_USER_ID),
onVisible(PROFILE_USER_ID),
onInvisible(PROFILE_USER_ID));
startDefaultProfile();
mMediator.unassignUserFromDisplayOnStop(PROFILE_USER_ID);
@@ -237,10 +298,19 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
listener.verify();
}
@Test
public final void testVisibleProfileBecomesInvisibleWhenParentIsSwitchedOut() {
public final void testVisibleProfileBecomesInvisibleWhenParentIsSwitchedOut() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
onVisible(PARENT_USER_ID),
onVisible(PROFILE_USER_ID),
onInvisible(PARENT_USER_ID),
onInvisible(PROFILE_USER_ID),
onVisible(OTHER_USER_ID));
startDefaultProfile();
startForegroundUser(OTHER_USER_ID);
@@ -248,20 +318,29 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
expectUserAssignedToDisplay(DEFAULT_DISPLAY, OTHER_USER_ID);
listener.verify();
}
@Test
public final void testStartBgProfile_onDefaultDisplay_whenParentIsNotStarted() {
public final void testStartBgProfile_onDefaultDisplay_whenParentIsNotStarted()
throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
listener.verify();
}
@Test
public final void testStartBgProfile_onDefaultDisplay_whenParentIsStartedOnBg() {
public final void testStartBgProfile_onDefaultDisplay_whenParentIsStartedOnBg()
throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
startBackgroundUser(PARENT_USER_ID);
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
@@ -272,11 +351,15 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
listener.verify();
}
// Not supported - profiles can only be started on default display
@Test
public final void testStartBgProfile_onSecondaryDisplay() {
public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
@@ -284,10 +367,14 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
listener.verify();
}
@Test
public final void testStartFgProfile_onDefaultDisplay() {
public final void testStartFgProfile_onDefaultDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, FG,
DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
@@ -296,10 +383,14 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
listener.verify();
}
@Test
public final void testStartFgProfile_onSecondaryDisplay() {
public final void testStartFgProfile_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, FG,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
@@ -307,10 +398,12 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
listener.verify();
}
@Test
public final void testIsUserVisible_invalidUsers() {
public final void testIsUserVisible_invalidUsers() throws Exception {
expectWithMessage("isUserVisible(%s)", USER_NULL)
.that(mMediator.isUserVisible(USER_NULL))
.isFalse();
@@ -325,6 +418,16 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
.isFalse();
}
@Test
public final void testRemoveListener() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
mMediator.removeListener(listener);
startForegroundUser(USER_ID);
listener.verify();
}
/**
* Starts a user in foreground on the default display, asserting it was properly started.
*
@@ -397,6 +500,19 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
}
}
protected AsyncUserVisibilityListener addListenerForNoEvents() {
AsyncUserVisibilityListener listener = mListenerFactory.forNoEvents();
mMediator.addListener(listener);
return listener;
}
protected AsyncUserVisibilityListener addListenerForEvents(
UserVisibilityChangedEvent... events) {
AsyncUserVisibilityListener listener = mListenerFactory.forEvents(events);
mMediator.addListener(listener);
return listener;
}
protected void assertStartUserResult(int actualResult, int expectedResult) {
assertWithMessage("startUser() result (where %s=%s and %s=%s)",
expectedResult, userAssignmentResultToString(expectedResult),