diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index f4f99390054bb..88e12fa9a6045 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -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 */); diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java index 52e2acbe2b132..9c4187b35e93c 100644 --- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java +++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java @@ -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 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 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 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(); diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java index c66c7411530cc..693a96dd66186 100644 --- a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java +++ b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java @@ -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 diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java new file mode 100644 index 0000000000000..1a4a7bd4ff72d --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java @@ -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 mExpectedEvents; + + @GuardedBy("mLock") + private final List mReceivedEvents = new ArrayList<>(); + + @GuardedBy("mLock") + private final List mErrors = new ArrayList<>(); + + private AsyncUserVisibilityListener(Expect expect, Thread expectedReceiverThread, + List 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 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 receivedEvents = getReceivedEvents(); + return "[" + getClass().getSimpleName() + ": id=" + mId + + ", creationThread=" + mExpectedReceiverThread + + ", received=" + receivedEvents.size() + + ", events=" + receivedEvents + "]"; + } + + private List 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 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()); + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityChangedEvent.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityChangedEvent.java new file mode 100644 index 0000000000000..58a265b547446 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityChangedEvent.java @@ -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); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java index bc232e65a096e..c5a85721cc8ac 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java @@ -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(); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java index a483a92a064ce..fc0287f5d6381 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java @@ -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(); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java index a8d2944b354cf..17ee909279773 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java @@ -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),