Added user lifecycle events to CarUserManager.
Test: atest CtsCarTestCases:android.car.cts.CarUserManagerTest
Bug: 144120654
Bug: 146207078
Change-Id: I1c4ca36337f68d893089b3c07b54ff5d02456e67
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index d84c798..e2f21b5 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -29,18 +29,21 @@
import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.car.ICarUserService;
import android.car.settings.CarSettings;
+import android.car.user.CarUserManager;
import android.car.userlib.CarUserManagerHelper;
import android.content.Context;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.location.LocationManager;
import android.os.Binder;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
+import android.util.SparseArray;
import android.util.TimingsTraceLog;
import com.android.car.CarServiceBase;
@@ -48,6 +51,7 @@
import com.android.car.hal.UserHalService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.util.UserIcons;
import java.io.PrintWriter;
@@ -69,6 +73,8 @@
* <ol/>
*/
public final class CarUserService extends ICarUserService.Stub implements CarServiceBase {
+
+
private final Context mContext;
private final CarUserManagerHelper mCarUserManagerHelper;
private final IActivityManager mAm;
@@ -96,10 +102,18 @@
@GuardedBy("mLockUser")
private final ArrayList<Integer> mBackgroundUsersRestartedHere = new ArrayList<>();
+ // TODO(b/144120654): mege then
private final CopyOnWriteArrayList<UserCallback> mUserCallbacks = new CopyOnWriteArrayList<>();
private final UserHalService mHal;
+ /**
+ * 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. */
public interface UserCallback {
/** Gets called when user lock status has been changed. */
@@ -170,7 +184,19 @@
public void dump(@NonNull PrintWriter writer) {
checkAtLeastOnePermission("dump()", android.Manifest.permission.DUMP);
writer.println("*CarUserService*");
+ String indent = " ";
synchronized (mLockUser) {
+ int numberListeners = mLifecycleListeners.size();
+ if (numberListeners == 0) {
+ writer.println("No lifecycle listeners");
+ } else {
+ writer.printf("%d lifecycle listeners\n", numberListeners);
+ for (int i = 0; i < numberListeners; i++) {
+ int uid = mLifecycleListeners.keyAt(i);
+ IResultReceiver listener = mLifecycleListeners.valueAt(i);
+ writer.printf("%suid: %d Listener %s\n", indent, uid, listener);
+ }
+ }
writer.println("User0Unlocked: " + mUser0Unlocked);
writer.println("MaxRunningUsers: " + mMaxRunningUsers);
writer.println("BackgroundUsersToRestart: " + mBackgroundUsersToRestart);
@@ -178,10 +204,9 @@
List<UserInfo> allDrivers = getAllDrivers();
int driversSize = allDrivers.size();
writer.println("NumberOfDrivers: " + driversSize);
- String prefix = " ";
for (int i = 0; i < driversSize; i++) {
int driverId = allDrivers.get(i).id;
- writer.print(prefix + "#" + i + ": id=" + driverId);
+ writer.print(indent + "#" + i + ": id=" + driverId);
List<UserInfo> passengers = getPassengers(driverId);
int passengersSize = passengers.size();
writer.print(" NumberPassengers: " + passengersSize);
@@ -405,6 +430,42 @@
}
}
+ @Override
+ public void setLifecycleListenerForUid(IResultReceiver listener) {
+ int uid = Binder.getCallingUid();
+ checkInteractAcrossUsersPermission("setLifecycleListenerForUid" + uid);
+
+ try {
+ listener.asBinder().linkToDeath(() -> onListenerDeath(uid), 0);
+ } catch (RemoteException e) {
+ Log.wtf(TAG_USER, "Cannot listen to death of " + uid);
+ }
+ synchronized (mLockUser) {
+ mLifecycleListeners.append(uid, listener);
+ }
+ }
+
+ private void onListenerDeath(int uid) {
+ Log.i(TAG_USER, "Removing listeners for uid " + uid + " on binder death");
+ synchronized (mLockUser) {
+ removeLifecycleListenerLocked(uid);
+ }
+ }
+
+ @Override
+ public void resetLifecycleListenerForUid() {
+ int uid = Binder.getCallingUid();
+ checkInteractAcrossUsersPermission("resetLifecycleListenerForUid-" + uid);
+
+ synchronized (mLockUser) {
+ removeLifecycleListenerLocked(uid);
+ }
+ }
+
+ private void removeLifecycleListenerLocked(int uid) {
+ mLifecycleListeners.remove(uid);
+ }
+
/** Returns whether the given user is a system user. */
private static boolean isSystemUser(@UserIdInt int userId) {
return userId == UserHandle.USER_SYSTEM;
@@ -612,9 +673,10 @@
* @param userId User id of new user.
*/
public void onSwitchUser(@UserIdInt int userId) {
- TimingsTraceLog t = new TimingsTraceLog(TAG_USER,
- Trace.TRACE_TAG_SYSTEM_SERVER);
+ Log.i(TAG_USER, "onSwitchUser() callback for user " + userId);
+ TimingsTraceLog t = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
t.traceBegin("onSwitchUser-" + userId);
+
if (!isSystemUser(userId)) {
mCarUserManagerHelper.setLastActiveUser(userId);
}
@@ -625,12 +687,52 @@
setupPassengerUser();
startFirstPassenger(userId);
}
+
+ // TODO(b/144120654): right now just the app listeners are running in the background so the
+ // CTS tests pass (as otherwise they might fail if a car service callback takes too long),
+ // but once we refactor the car service callback into lifecycle listeners, we should use a
+ // proper thread management (like a Threadpool / executor);
+
+ int listenersSize = mLifecycleListeners.size();
+ if (listenersSize == 0) {
+ Log.i(TAG_USER, "Not notifying app listeners");
+ } else {
+ new Thread(() -> {
+ // Must use a different TimingsTraceLog because it's another thread
+ TimingsTraceLog t2 = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
+ Log.i(TAG_USER, "Notifying " + listenersSize + " listeners");
+ for (int i = 0; i < listenersSize; i++) {
+ int uid = mLifecycleListeners.keyAt(i);
+ IResultReceiver listener = mLifecycleListeners.valueAt(i);
+ t2.traceBegin("notify-listener-" + uid);
+ Bundle data = new Bundle();
+ data.putInt(CarUserManager.BUNDLE_PARAM_ACTION,
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ // TODO(b/144120654): should pass currentId from CarServiceHelperService so it
+ // can set BUNDLE_PARAM_PREVIOUS_USER_HANDLE
+ if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+ Log.d(TAG_USER, "Notifying listener for uid " + uid);
+ }
+ try {
+ listener.send(userId, data);
+ } catch (RemoteException e) {
+ Log.e(TAG_USER, "Error calling lifecycle listener", e);
+ } finally {
+ t2.traceEnd();
+ }
+ }
+
+ }, "SwitchUser-" + userId + "-Listeners").start();
+ }
+
+ Log.i(TAG_USER, "Notifying " + mUserCallbacks.size() + " callbacks");
for (UserCallback callback : mUserCallbacks) {
- t.traceBegin("onSwitchUser-" + callback.getClass().getSimpleName());
+ t.traceBegin("onSwitchUser-" + callback.getClass().getName());
callback.onSwitchUser(userId);
t.traceEnd();
}
- t.traceEnd();
+ t.traceEnd(); // onSwitchUser
+
}
/**
@@ -746,11 +848,16 @@
android.Manifest.permission.DUMP);
}
+ private void checkInteractAcrossUsersPermission(String message) {
+ checkAtLeastOnePermission(message, android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ }
+
private static void checkAtLeastOnePermission(String message, String...permissions) {
int callingUid = Binder.getCallingUid();
if (!hasAtLeastOnePermissionGranted(callingUid, permissions)) {
throw new SecurityException("You need one of " + Arrays.toString(permissions)
- + " to: " + message);
+ + " to: " + message);
}
}