Deprecating UserCallback interface.
This commit corresponds to steps 1 and 2 from b/145689885 #16
Bug: 145689885
Test: atest CarServiceUnitTest
Change-Id: Ie7e1b2e852031098fe3767300ece7e9573341631
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 3858e9b..6546dd4 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -1240,7 +1240,7 @@
field public static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKING = 3; // 0x3
}
- public final class CarUserManager.UserLifecycleEvent {
+ public static final class CarUserManager.UserLifecycleEvent {
method public int getEventType();
method @Nullable public android.os.UserHandle getPreviousUserHandle();
method @NonNull public android.os.UserHandle getUserHandle();
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
index 09d9fd7..f655eca 100644
--- a/car-lib/api/test-current.txt
+++ b/car-lib/api/test-current.txt
@@ -65,7 +65,7 @@
field public static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKING = 3; // 0x3
}
- public final class CarUserManager.UserLifecycleEvent {
+ public static final class CarUserManager.UserLifecycleEvent {
method public int getEventType();
method @Nullable public android.os.UserHandle getPreviousUserHandle();
method @NonNull public android.os.UserHandle getUserHandle();
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index 810373b..38b1958 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -482,12 +482,13 @@
*/
@SystemApi
@TestApi
- public final class UserLifecycleEvent {
+ public static final class UserLifecycleEvent {
private final @UserLifecycleEventType int mEventType;
private final @NonNull UserHandle mUserHandle;
private final @Nullable UserHandle mPreviousUserHandle;
- private UserLifecycleEvent(@UserLifecycleEventType int eventType,
+ /** @hide */
+ public UserLifecycleEvent(@UserLifecycleEventType int eventType,
@NonNull UserHandle from, @Nullable UserHandle to) {
mEventType = eventType;
mPreviousUserHandle = from;
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index fedae06..164ecb8 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -30,6 +30,8 @@
import android.car.ICarUserService;
import android.car.settings.CarSettings;
import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleEvent;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.car.userlib.CarUserManagerHelper;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -113,19 +115,30 @@
@GuardedBy("mLockUser")
private final ArrayList<Integer> mBackgroundUsersRestartedHere = new ArrayList<>();
- // TODO(b/144120654): mege then
+ // TODO(b/144120654): merge then
private final CopyOnWriteArrayList<UserCallback> mUserCallbacks = new CopyOnWriteArrayList<>();
private final UserHalService mHal;
/**
+ * List of listeners to be notified on new user activities events.
+ */
+ private final CopyOnWriteArrayList<UserLifecycleListener>
+ mUserLifecycleListeners = new CopyOnWriteArrayList<>();
+
+ /**
* List of lifecycle listeners by uid.
*/
@GuardedBy("mLockUser")
private final SparseArray<IResultReceiver> mLifecycleListeners = new SparseArray<>();
- // TODO(b/144120654): replace by CarUserManager listener
- /** Interface for callbacks related to user activities. */
+ /**
+ * Interface for callbacks related to user activities.
+ *
+ * @deprecated {@link UserCallback} will be fully replaced by
+ * {@link UserLifecycleListener} as part of b/145689885
+ */
+ @Deprecated
public interface UserCallback {
/** Gets called when user lock status has been changed. */
void onUserLockChanged(@UserIdInt int userId, boolean unlocked);
@@ -567,18 +580,46 @@
return !mUserManager.getUserInfo(userId).isEphemeral();
}
- /** Adds callback to listen to user activity events. */
+ /**
+ * Adds a new callback to listen to user activity events.
+ *
+ * @deprecated users should rely on {@link UserLifecycleListener} and invoke
+ * {@link #addUserLifecycleListener} instead
+ */
+ @Deprecated
public void addUserCallback(@NonNull UserCallback callback) {
Objects.requireNonNull(callback, "callback cannot be null");
mUserCallbacks.add(callback);
}
- /** Removes previously added callback to listen user events. */
+ /**
+ * Removes previously added user callback.
+ *
+ * @deprecated users should rely on {@link UserLifecycleListener} and invoke
+ * {@link CarUserService#remove]UserLifecycleListener} instead
+ */
+ @Deprecated
public void removeUserCallback(@NonNull UserCallback callback) {
Objects.requireNonNull(callback, "callback cannot be null");
mUserCallbacks.remove(callback);
}
+ /**
+ * Adds a new {@link UserLifecycleListener} to listen to user activity events.
+ */
+ public void addUserLifecycleListener(@NonNull UserLifecycleListener listener) {
+ Objects.requireNonNull(listener, "listener cannot be null");
+ mUserLifecycleListeners.add(listener);
+ }
+
+ /**
+ * Removes previously added {@link UserLifecycleListener}.
+ */
+ public void removeUserLifecycleListener(@NonNull UserLifecycleListener listener) {
+ Objects.requireNonNull(listener, "listener cannot be null");
+ mUserLifecycleListeners.remove(listener);
+ }
+
/** Adds callback to listen to passenger activity events. */
public void addPassengerCallback(@NonNull PassengerCallback callback) {
Objects.requireNonNull(callback, "callback cannot be null");
@@ -802,14 +843,43 @@
}, "SwitchUser-" + userId + "-Listeners").start();
}
- Log.i(TAG_USER, "Notifying " + mUserCallbacks.size() + " callbacks");
+ notifyUserLifecycleListeners(t, userId);
+ notifyCallbacks(t, userId);
+ }
+
+ private void notifyUserLifecycleListeners(TimingsTraceLog t,
+ @UserIdInt int userId) {
+ if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+ Log.d(TAG_USER, "Notifying " + mUserLifecycleListeners.size()
+ + " user lifecycle listeners");
+ }
+ // TODO(b/145689885): passing null for `from` parameter until it gets properly replaced
+ // the expected Binder call.
+ UserLifecycleEvent event = new UserLifecycleEvent(
+ /* eventType= */ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ /* from= */ null, /* to= */ new UserHandle(userId));
+ for (UserLifecycleListener listener : mUserLifecycleListeners) {
+ t.traceBegin("onEvent-" + listener.getClass().getName());
+ try {
+ listener.onEvent(event);
+ } catch (RuntimeException e) {
+ Log.e(TAG_USER,
+ "Exception raised when invoking onEvent for " + listener, e);
+ }
+ t.traceEnd();
+ }
+ }
+
+ private void notifyCallbacks(TimingsTraceLog t, @UserIdInt int userId) {
+ if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+ Log.d(TAG_USER, "Notifying " + mUserCallbacks.size() + " callbacks");
+ }
for (UserCallback callback : mUserCallbacks) {
t.traceBegin("onSwitchUser-" + callback.getClass().getName());
callback.onSwitchUser(userId);
t.traceEnd();
}
t.traceEnd(); // onSwitchUser
-
}
/**
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index 48fa261..ba77782 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -33,6 +33,8 @@
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,6 +47,9 @@
import android.car.CarOccupantZoneManager.OccupantTypeEnum;
import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.car.settings.CarSettings;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleEvent;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.car.userlib.CarUserManagerHelper;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -77,6 +82,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.junit.MockitoJUnitRunner;
@@ -117,6 +124,8 @@
@Mock private UserManager mMockedUserManager;
@Mock private Resources mMockedResources;
@Mock private Drawable mMockedDrawable;
+ @Mock private UserLifecycleListener mUserLifecycleListener;
+ @Captor private ArgumentCaptor<UserLifecycleEvent> mArgumentCaptor;
private MockitoSession mSession;
private CarUserService mCarUserService;
@@ -201,6 +210,60 @@
UserHandle.of(UserHandle.USER_SYSTEM));
}
+ @Test
+ public void testAddUserLifecycleListener_checkNullParameter() {
+ assertThrows(NullPointerException.class,
+ () -> mCarUserService.addUserLifecycleListener(null));
+ }
+
+ @Test
+ public void testRemoveUserLifecycleListener_checkNullParameter() {
+ assertThrows(NullPointerException.class,
+ () -> mCarUserService.removeUserLifecycleListener(null));
+ }
+
+ @Test
+ public void testOnSwitchUser_addListenerAndReceiveEvent() {
+ // Arrange
+ mCarUserService.addUserLifecycleListener(mUserLifecycleListener);
+
+ // Act
+ int anyNewUserId = 11;
+ mCarUserService.onSwitchUser(anyNewUserId);
+
+ // Verify
+ verifyListenerOnEventInvoked(anyNewUserId,
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ }
+
+ @Test
+ public void testOnSwitchUser_ensureAllListenersAreNotified() {
+ // Arrange: add two listeners, one to fail on onEvent
+ // Adding the failure listener first.
+ UserLifecycleListener failureListener = mock(UserLifecycleListener.class);
+ doThrow(new RuntimeException("Failed onEvent invocation")).when(
+ failureListener).onEvent(any(UserLifecycleEvent.class));
+ mCarUserService.addUserLifecycleListener(failureListener);
+
+ // Adding the non-failure listener later.
+ mCarUserService.addUserLifecycleListener(mUserLifecycleListener);
+
+ // Act
+ int anyNewUserId = 11;
+ mCarUserService.onSwitchUser(anyNewUserId);
+
+ // Verify
+ verifyListenerOnEventInvoked(anyNewUserId,
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ }
+
+ private void verifyListenerOnEventInvoked(int expectedNewUserId, int expectedEventType) {
+ verify(mUserLifecycleListener).onEvent(mArgumentCaptor.capture());
+ UserLifecycleEvent actualEvent = mArgumentCaptor.getValue();
+ assertThat(actualEvent.getEventType()).isEqualTo(expectedEventType);
+ assertThat(actualEvent.getUserHandle().getIdentifier()).isEqualTo(expectedNewUserId);
+ }
+
/**
* Test that the {@link CarUserService} disables the location service for headless user 0 upon
* first run.