/*
* Copyright (C) 2019 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 android.car.user;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.os.Process.myUid;
import static com.android.car.internal.util.FunctionalUtils.getLambdaName;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.car.CarManagerBase;
import android.car.ICarResultReceiver;
import android.car.ICarUserService;
import android.car.ResultCallback;
import android.car.SyncResultCallback;
import android.car.builtin.os.UserManagerHelper;
import android.car.builtin.util.EventLogHelper;
import android.car.drivingstate.CarUxRestrictions;
import android.car.feature.Flags;
import android.car.util.concurrent.AndroidAsyncFuture;
import android.car.util.concurrent.AndroidFuture;
import android.car.util.concurrent.AsyncFuture;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.Dumpable;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import com.android.car.internal.ICarBase;
import com.android.car.internal.ResultCallbackImpl;
import com.android.car.internal.common.CommonConstants;
import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
import com.android.car.internal.common.UserHelperLite;
import com.android.car.internal.os.CarSystemProperties;
import com.android.car.internal.util.ArrayUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/**
* API to manage users related to car.
*
* @hide
*/
@SystemApi
public final class CarUserManager extends CarManagerBase {
/** @hide */
public static final String TAG = CarUserManager.class.getSimpleName();
private static final int HAL_TIMEOUT_MS = CarSystemProperties.getUserHalTimeout().orElse(5_000);
private static final int USER_CALL_TIMEOUT_MS = 60_000;
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
/**
* {@link UserLifecycleEvent} called when the user is starting, for components to initialize
* any per-user state they maintain for running users.
*
* @hide
*/
@SystemApi
public static final int USER_LIFECYCLE_EVENT_TYPE_STARTING =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
/**
* {@link UserLifecycleEvent} called when switching to a different foreground user, for
* components that have special behavior for whichever user is currently in the foreground.
*
*
This is called before any application processes are aware of the new user.
*
*
Notice that internal system services might not have handled user switching yet, so be
* careful with interaction with them.
*
* @hide
*/
@SystemApi
public static final int USER_LIFECYCLE_EVENT_TYPE_SWITCHING =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
/**
* {@link UserLifecycleEvent} called when an existing user is in the process of being unlocked.
*
*
This means the credential-encrypted storage for that user is now available, and
* encryption-aware component filtering is no longer in effect.
*
*
Notice that internal system services might not have handled unlock yet, so most components
* should ignore this callback and rely on {@link #USER_LIFECYCLE_EVENT_TYPE_UNLOCKED} instead.
*
* @hide
*/
@SystemApi
public static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKING =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
/**
* {@link UserLifecycleEvent} called after an existing user is unlocked.
*
* @hide
*/
@SystemApi
public static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKED =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
/**
* {@link UserLifecycleEvent} called after an existing user is unlocked for components to
* perform non-urgent tasks for user unlocked.
*
*
Note: This event type is intended only for internal system services. Application listeners
* should not use this event type and will not receive any events of this type.
*
* @hide
*/
public static final int USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED;
/**
* {@link UserLifecycleEvent} called when an existing user is stopping, for components to
* finalize any per-user state they maintain for running users.
*
*
This is called prior to sending the {@code SHUTDOWN} broadcast to the user; it is a good
* place to stop making use of any resources of that user (such as binding to a service running
* in the user).
*
*
Note: this is the last callback where the callee may access the target user's CE
* storage.
*
* @hide
*/
@SystemApi
public static final int USER_LIFECYCLE_EVENT_TYPE_STOPPING =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
/**
* {@link UserLifecycleEvent} called after an existing user is stopped.
*
*
This is called after all application process teardown of the user is complete.
*
* @hide
*/
@SystemApi
public static final int USER_LIFECYCLE_EVENT_TYPE_STOPPED =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
/**
* {@link UserLifecycleEvent} called after an existing user is created.
*
* @hide
*/
@SystemApi
public static final int USER_LIFECYCLE_EVENT_TYPE_CREATED =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_CREATED;
/**
* {@link UserLifecycleEvent} called after an existing user is removed.
*
* @hide
*/
@SystemApi
public static final int USER_LIFECYCLE_EVENT_TYPE_REMOVED =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_REMOVED;
/**
* {@link UserLifecycleEvent} called after an existing user becomes visible.
*
* @hide
*/
@SystemApi
public static final int USER_LIFECYCLE_EVENT_TYPE_VISIBLE =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_VISIBLE;
/**
* {@link UserLifecycleEvent} called after an existing user becomes invisible.
*
* @hide
*/
@SystemApi
public static final int USER_LIFECYCLE_EVENT_TYPE_INVISIBLE =
CommonConstants.USER_LIFECYCLE_EVENT_TYPE_INVISIBLE;
/** @hide */
public static final String BUNDLE_PARAM_ACTION = "action";
/** @hide */
public static final String BUNDLE_PARAM_PREVIOUS_USER_ID = "previous_user";
/**
* {@link UserIdentificationAssociationType} for key fob.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_TYPE_KEY_FOB = 1;
/**
* {@link UserIdentificationAssociationType} for custom type 1.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_TYPE_CUSTOM_1 = 101;
/**
* {@link UserIdentificationAssociationType} for custom type 2.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_TYPE_CUSTOM_2 = 102;
/**
* {@link UserIdentificationAssociationType} for custom type 3.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_TYPE_CUSTOM_3 = 103;
/**
* {@link UserIdentificationAssociationType} for custom type 4.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_TYPE_CUSTOM_4 = 104;
/**
* User HAL's user identification association types
*
* @hide
*/
@IntDef(prefix = { "USER_IDENTIFICATION_ASSOCIATION_TYPE_" }, value = {
USER_IDENTIFICATION_ASSOCIATION_TYPE_KEY_FOB,
USER_IDENTIFICATION_ASSOCIATION_TYPE_CUSTOM_1,
USER_IDENTIFICATION_ASSOCIATION_TYPE_CUSTOM_2,
USER_IDENTIFICATION_ASSOCIATION_TYPE_CUSTOM_3,
USER_IDENTIFICATION_ASSOCIATION_TYPE_CUSTOM_4,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserIdentificationAssociationType{}
/**
* {@link UserIdentificationAssociationSetValue} to associate the identification type with the
* current foreground Android user.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_SET_VALUE_ASSOCIATE_CURRENT_USER = 1;
/**
* {@link UserIdentificationAssociationSetValue} to disassociate the identification type from
* the current foreground Android user.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_SET_VALUE_DISASSOCIATE_CURRENT_USER = 2;
/**
* {@link UserIdentificationAssociationSetValue} to disassociate the identification type from
* all Android users.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_SET_VALUE_DISASSOCIATE_ALL_USERS = 3;
/**
* User HAL's user identification association types
*
* @hide
*/
@IntDef(prefix = { "USER_IDENTIFICATION_ASSOCIATION_SET_VALUE_" }, value = {
USER_IDENTIFICATION_ASSOCIATION_SET_VALUE_ASSOCIATE_CURRENT_USER,
USER_IDENTIFICATION_ASSOCIATION_SET_VALUE_DISASSOCIATE_CURRENT_USER,
USER_IDENTIFICATION_ASSOCIATION_SET_VALUE_DISASSOCIATE_ALL_USERS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserIdentificationAssociationSetValue{}
/**
* {@link UserIdentificationAssociationValue} when the status of an association could not be
* determined.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_VALUE_UNKNOWN = 1;
/**
* {@link UserIdentificationAssociationValue} when the identification type is associated with
* the current foreground Android user.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_VALUE_ASSOCIATE_CURRENT_USER = 2;
/**
* {@link UserIdentificationAssociationValue} when the identification type is associated with
* another Android user.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_VALUE_ASSOCIATED_ANOTHER_USER = 3;
/**
* {@link UserIdentificationAssociationValue} when the identification type is not associated
* with any Android user.
*
* @hide
*/
public static final int USER_IDENTIFICATION_ASSOCIATION_VALUE_NOT_ASSOCIATED_ANY_USER = 4;
/**
* User HAL's user identification association types
*
* @hide
*/
@IntDef(prefix = { "USER_IDENTIFICATION_ASSOCIATION_VALUE_" }, value = {
USER_IDENTIFICATION_ASSOCIATION_VALUE_UNKNOWN,
USER_IDENTIFICATION_ASSOCIATION_VALUE_ASSOCIATE_CURRENT_USER,
USER_IDENTIFICATION_ASSOCIATION_VALUE_ASSOCIATED_ANOTHER_USER,
USER_IDENTIFICATION_ASSOCIATION_VALUE_NOT_ASSOCIATED_ANY_USER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserIdentificationAssociationValue{}
private final Object mLock = new Object();
private final ICarUserService mService;
private final UserManager mUserManager;
private final boolean mIsHeadlessSystemUserMode;
/**
* Map of listeners registers by the app.
*/
@Nullable
@GuardedBy("mLock")
private ArrayMap> mListeners;
/**
* Receiver used to receive user-lifecycle callbacks from the service.
*/
@Nullable
@GuardedBy("mLock")
private LifecycleResultReceiver mReceiver;
private final Dumper mDumper;
/**
* Logs the number of received events so it's shown on {@code Dumper.dump()}.
*/
private int mNumberReceivedEvents;
/**
* Logs the received events so they're shown on {@code Dumper.dump()}.
*
* Note: these events are only logged when {@link #VERBOSE} is {@code true}.
*/
@Nullable
private List mEvents;
/**
* @hide
*/
public CarUserManager(@NonNull ICarBase car, @NonNull IBinder service) {
this(car, ICarUserService.Stub.asInterface(service),
car.getContext().getSystemService(UserManager.class),
UserManager.isHeadlessSystemUserMode());
}
/**
* @hide
*/
@VisibleForTesting
public CarUserManager(@NonNull ICarBase car, @NonNull ICarUserService service,
@NonNull UserManager userManager, boolean isHeadlessSystemUserMode) {
super(car);
mDumper = addDumpable(car.getContext(), () -> new Dumper());
Slog.d(TAG, "CarUserManager(): DBG= " + DBG + ", mDumper=" + mDumper);
mService = service;
mUserManager = userManager;
mIsHeadlessSystemUserMode = isHeadlessSystemUserMode;
}
/**
* Starts the specified user.
*
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.INTERACT_ACROSS_USERS})
public void startUser(@NonNull UserStartRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull ResultCallback callback) {
int uid = myUid();
int userId = request.getUserHandle().getIdentifier();
int displayId = request.getDisplayId();
EventLogHelper.writeCarUserManagerStartUserReq(uid, userId, displayId);
try {
ResultCallbackImpl callbackImpl = new ResultCallbackImpl<>(
executor, callback) {
@Override
protected void onCompleted(UserStartResponse response) {
EventLogHelper.writeCarUserManagerStartUserResp(uid, userId, displayId,
response != null ? response.getStatus()
: UserStartResponse.STATUS_ANDROID_FAILURE);
super.onCompleted(response);
}
};
mService.startUser(request, callbackImpl);
} catch (SecurityException e) {
Slog.e(TAG, "startUser(userId=" + userId + ", displayId=" + displayId + ")", e);
throw e;
} catch (RemoteException | RuntimeException e) {
UserStartResponse response = handleExceptionFromCarService(e,
new UserStartResponse(UserStartResponse.STATUS_ANDROID_FAILURE));
callback.onResult(response);
}
}
/**
* Stops the specified user.
*
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.INTERACT_ACROSS_USERS})
public void stopUser(@NonNull UserStopRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull ResultCallback callback) {
int uid = myUid();
int userId = request.getUserHandle().getIdentifier();
EventLogHelper.writeCarUserManagerStopUserReq(uid, userId);
try {
ResultCallbackImpl callbackImpl = new ResultCallbackImpl<>(
executor, callback) {
@Override
protected void onCompleted(UserStopResponse response) {
EventLogHelper.writeCarUserManagerStopUserResp(uid, userId,
response != null ? response.getStatus()
: UserStopResponse.STATUS_ANDROID_FAILURE);
super.onCompleted(response);
}
};
mService.stopUser(request, callbackImpl);
} catch (SecurityException e) {
Slog.e(TAG, "stopUser(userId=" + userId + ")", e);
throw e;
} catch (RemoteException | RuntimeException e) {
UserStopResponse response = handleExceptionFromCarService(e,
new UserStopResponse(UserStopResponse.STATUS_ANDROID_FAILURE));
callback.onResult(response);
}
}
/**
* Switches the foreground user to the given user. Ignores UX Restrictions regarding user
* switching or {@link CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP}.
*
* @param userSwitchRequest contains target user.
* @param executor to execute the callback.
* @param callback called with the {@code UserSwitchResult}
*
* @hide
*/
@FlaggedApi(Flags.FLAG_SWITCH_USER_IGNORING_UXR)
@SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public void switchUserIgnoringUxRestriction(@NonNull UserSwitchRequest userSwitchRequest,
@NonNull @CallbackExecutor Executor executor,
@NonNull ResultCallback callback) {
switchUser(userSwitchRequest, executor, callback, /* ignoreUxRestriction=*/ true);
}
/**
* Switches the foreground user to the given user.
*
* @param userSwitchRequest contains target user.
* @param executor to execute the callback.
* @param callback called with the {@code UserSwitchResult}
*
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public void switchUser(@NonNull UserSwitchRequest userSwitchRequest,
@NonNull @CallbackExecutor Executor executor,
@NonNull ResultCallback callback) {
switchUser(userSwitchRequest, executor, callback, /* ignoreUxRestriction=*/ false);
}
private void switchUser(@NonNull UserSwitchRequest userSwitchRequest,
@NonNull @CallbackExecutor Executor executor,
@NonNull ResultCallback callback, boolean ignoreUxRestriction) {
if (DBG) {
Slog.d(TAG, "switchuser(): userHandle=" + userSwitchRequest.getUserHandle()
+ ", ignoreUxRestriction=" + ignoreUxRestriction);
}
int uid = myUid();
int targetUserId = userSwitchRequest.getUserHandle().getIdentifier();
try {
ResultCallbackImpl resultCallbackImpl = new ResultCallbackImpl<>(
executor, callback) {
@Override
protected void onCompleted(UserSwitchResult result) {
if (result == null) {
EventLogHelper.writeCarUserManagerSwitchUserResp(uid,
UserSwitchResult.STATUS_ANDROID_FAILURE, /* errorMessage=*/ null);
} else {
EventLogHelper.writeCarUserManagerSwitchUserResp(uid, result.getStatus(),
result.getErrorMessage());
}
super.onCompleted(result);
}
};
EventLogHelper.writeCarUserManagerSwitchUserReq(uid, targetUserId);
mService.switchUser(targetUserId, HAL_TIMEOUT_MS, resultCallbackImpl,
ignoreUxRestriction);
} catch (SecurityException e) {
Slog.w(TAG, "switchUser(" + targetUserId + ") failed: " + e);
throw e;
} catch (RemoteException | RuntimeException e) {
UserSwitchResult result = handleExceptionFromCarService(e,
new UserSwitchResult(UserSwitchResult.STATUS_HAL_INTERNAL_FAILURE, null));
callback.onResult(result);
}
}
/**
* Switches the foreground user to the given target user.
*
* @hide
* @deprecated Use {@link #switchUser(UserSwitchRequest, Executor, ResultCallback)} instead.
*/
@TestApi
@Deprecated
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public AsyncFuture switchUser(@UserIdInt int targetUserId) {
UserSwitchRequest userSwitchRequest = new UserSwitchRequest.Builder(
UserHandle.of(targetUserId)).build();
AndroidFuture future = new AndroidFuture<>();
switchUser(userSwitchRequest, Runnable::run, future::complete);
return new AndroidAsyncFuture<>(future);
}
/**
* Logouts the current user (if it was switched to by a device admin).
*
* @hide
*/
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public AsyncFuture logoutUser() {
int uid = myUid();
try {
AndroidFuture future = new AndroidFuture<>();
ResultCallbackImpl resultCallbackImpl = new ResultCallbackImpl<>(
Runnable::run, new SyncResultCallback<>()) {
@Override
protected void onCompleted(UserSwitchResult result) {
if (result == null) {
EventLogHelper.writeCarUserManagerLogoutUserResp(uid,
UserSwitchResult.STATUS_ANDROID_FAILURE, /* errorMessage=*/ null);
} else {
EventLogHelper.writeCarUserManagerLogoutUserResp(uid, result.getStatus(),
result.getErrorMessage());
}
future.complete(result);
super.onCompleted(result);
}
};
EventLogHelper.writeCarUserManagerLogoutUserReq(uid);
mService.logoutUser(HAL_TIMEOUT_MS, resultCallbackImpl);
return new AndroidAsyncFuture<>(future);
} catch (SecurityException e) {
throw e;
} catch (RemoteException | RuntimeException e) {
AsyncFuture future =
newSwitchResultForFailure(UserSwitchResult.STATUS_HAL_INTERNAL_FAILURE);
return handleExceptionFromCarService(e, future);
}
}
private AndroidAsyncFuture newSwitchResultForFailure(
@UserSwitchResult.Status int status) {
AndroidFuture future = new AndroidFuture<>();
future.complete(new UserSwitchResult(status, null));
return new AndroidAsyncFuture<>(future);
}
/**
* Creates a new guest Android user.
*
* @hide
* @deprecated Use {@link #createUser(UserCreationRequest, Executor, ResultCallback)} instead.
*/
@Deprecated
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public AsyncFuture createGuest(@Nullable String name) {
AndroidFuture future = new AndroidFuture<>();
UserCreationRequest.Builder userCreationRequestBuilder = new UserCreationRequest.Builder();
if (name != null) {
userCreationRequestBuilder.setName(name);
}
createUser(userCreationRequestBuilder.setGuest().build(), Runnable::run, future::complete);
return new AndroidAsyncFuture<>(future);
}
/**
* Creates a new Android user.
*
* @hide
* @deprecated Use {@link #createUser(UserCreationRequest, Executor, ResultCallback)} instead.
*/
@Deprecated
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public AsyncFuture createUser(@Nullable String name,
int flags) {
AndroidFuture future = new AndroidFuture<>();
UserCreationRequest.Builder userCreationRequestBuilder = new UserCreationRequest.Builder();
if (name != null) {
userCreationRequestBuilder.setName(name);
}
if ((flags & UserManagerHelper.FLAG_ADMIN) == UserManagerHelper.FLAG_ADMIN) {
userCreationRequestBuilder.setAdmin();
}
if ((flags & UserManagerHelper.FLAG_EPHEMERAL) == UserManagerHelper.FLAG_EPHEMERAL) {
userCreationRequestBuilder.setEphemeral();
}
createUser(userCreationRequestBuilder.build(), Runnable::run, future::complete);
return new AndroidAsyncFuture<>(future);
}
/**
* Creates a new Android user.
*
* @param userCreationRequest contains new user information
* @param executor to execute the callback.
* @param callback called with the {code UserCreationResult}
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public void createUser(@NonNull UserCreationRequest userCreationRequest,
@NonNull @CallbackExecutor Executor executor,
@NonNull ResultCallback callback) {
Objects.requireNonNull(userCreationRequest, "userCreationRequest cannot be null");
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(callback, "callback cannot be null");
int uid = myUid();
try {
ResultCallbackImpl resultCallbackImpl =
new ResultCallbackImpl(
executor, callback) {
@Override
protected void onCompleted(UserCreationResult result) {
if (result == null) {
EventLogHelper.writeCarUserManagerCreateUserResp(uid,
UserCreationResult.STATUS_ANDROID_FAILURE, /* errorMessage=*/ null);
} else {
EventLogHelper.writeCarUserManagerCreateUserResp(uid, result.getStatus(),
result.getErrorMessage());
}
super.onCompleted(result);
}
};
String name = userCreationRequest.getName();
String userType = userCreationRequest.isGuest() ? UserManager.USER_TYPE_FULL_GUEST
: UserManager.USER_TYPE_FULL_SECONDARY;
int flags = 0;
flags |= userCreationRequest.isAdmin() ? UserManagerHelper.FLAG_ADMIN : 0;
flags |= userCreationRequest.isEphemeral() ? UserManagerHelper.FLAG_EPHEMERAL : 0;
EventLogHelper.writeCarUserManagerCreateUserReq(uid,
UserHelperLite.safeName(name), userType, flags);
mService.createUser(userCreationRequest, HAL_TIMEOUT_MS, resultCallbackImpl);
System.out.println("manager test API replied");
} catch (SecurityException e) {
throw e;
} catch (RemoteException | RuntimeException e) {
callback.onResult(
new UserCreationResult(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE));
handleExceptionFromCarService(e, null);
}
}
/**
* Updates pre-created users.
*
* @deprecated Pre-created users are no longer supported.
* This method is no-op and will be removed soon.
*
* @hide
*/
@Deprecated
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public void updatePreCreatedUsers() {
Slog.w(TAG, "updatePreCreatedUsers(): This method should not be called."
+ " Pre-created users are no longer supported.");
}
/**
* Removes the given user.
*
* @param userRemovalRequest contains user to be removed.
* @param executor to execute the callback.
* @param callback called with the {code UserRemovalResult}
*
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public void removeUser(@NonNull UserRemovalRequest userRemovalRequest,
@NonNull @CallbackExecutor Executor executor,
@NonNull ResultCallback callback) {
int uid = myUid();
EventLogHelper.writeCarUserManagerRemoveUserReq(uid,
userRemovalRequest.getUserHandle().getIdentifier());
try {
ResultCallbackImpl resultCallbackImpl = new ResultCallbackImpl<>(
executor, callback) {
@Override
protected void onCompleted(UserRemovalResult result) {
EventLogHelper.writeCarUserManagerRemoveUserResp(uid,
result != null ? result.getStatus()
: UserRemovalResult.STATUS_ANDROID_FAILURE);
super.onCompleted(result);
}
};
mService.removeUser(userRemovalRequest.getUserHandle().getIdentifier(),
resultCallbackImpl);
} catch (SecurityException e) {
Slog.e(TAG, "CarUserManager removeUser", e);
throw e;
} catch (RemoteException | RuntimeException e) {
UserRemovalResult result = handleExceptionFromCarService(e,
new UserRemovalResult(UserRemovalResult.STATUS_ANDROID_FAILURE));
callback.onResult(result);
}
}
/**
* Removes the given user.
*
* @param userId identification of the user to be removed.
*
* @return whether the user was successfully removed.
*
* @hide
*
* @deprecated use {@link #removeUser(UserRemovalRequest, Executor, ResultCallback)} instead.
* It will be marked removed in {@code V} and hard removed in {@code X}.
*/
@Deprecated
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
@NonNull
public UserRemovalResult removeUser(@UserIdInt int userId) {
UserRemovalRequest userRemovalRequest = new UserRemovalRequest.Builder(
UserHandle.of(userId)).build();
SyncResultCallback userRemovalResultCallback =
new SyncResultCallback<>();
removeUser(userRemovalRequest, Runnable::run, userRemovalResultCallback);
UserRemovalResult userRemovalResult = new UserRemovalResult(
UserRemovalResult.STATUS_ANDROID_FAILURE);
try {
userRemovalResult = userRemovalResultCallback.get(USER_CALL_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
Slog.e(TAG, "CarUserManager removeUser(" + userId + "): ", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Slog.e(TAG, "CarUserManager removeUser(" + userId + "): ", e);
}
return userRemovalResult;
}
/**
* Adds a listener for {@link UserLifecycleEvent user lifecycle events}.
*
* @throws IllegalStateException if the listener was already added.
*
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
public void addListener(@NonNull @CallbackExecutor Executor executor,
@NonNull UserLifecycleListener listener) {
addListenerInternal(executor, /* filter= */null, listener);
}
/**
* Adds a listener for {@link UserLifecycleEvent user lifecycle events} with a filter that can
* specify a specific event type or a user id.
*
* @throws IllegalStateException if the listener was already added.
*
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
public void addListener(@NonNull @CallbackExecutor Executor executor,
@NonNull UserLifecycleEventFilter filter, @NonNull UserLifecycleListener listener) {
Objects.requireNonNull(filter, "filter cannot be null");
addListenerInternal(executor, filter, listener);
}
private void addListenerInternal(@CallbackExecutor Executor executor,
@Nullable UserLifecycleEventFilter filter, UserLifecycleListener listener) {
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(listener, "listener cannot be null");
int uid = myUid();
String packageName = getContext().getPackageName();
if (DBG) {
Slog.d(TAG, "addListener(): uid=" + uid + ", pkg=" + packageName
+ ", listener=" + listener + ", filter= " + filter);
}
synchronized (mLock) {
Preconditions.checkState(mListeners == null || !mListeners.containsKey(listener),
"already called for this listener");
if (mReceiver == null) {
mReceiver = new LifecycleResultReceiver();
if (DBG) {
Slog.d(TAG, "Setting lifecycle receiver with filter " + filter
+ " for uid " + uid + " and package " + packageName);
}
} else {
if (DBG) {
Slog.d(TAG, "Already set receiver for uid " + uid + " and package "
+ packageName + " adding new filter " + filter);
}
}
try {
boolean hasFilter = filter != null;
EventLogHelper.writeCarUserManagerAddListener(uid, packageName, hasFilter);
mService.setLifecycleListenerForApp(packageName, filter, mReceiver);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
if (mListeners == null) {
mListeners = new ArrayMap<>(1); // Most likely app will have just one listener
} else if (DBG) {
Slog.d(TAG, "addListener(" + getLambdaName(listener) + "): context " + getContext()
+ " already has " + mListeners.size() + " listeners: "
+ mListeners.keySet().stream()
.map((l) -> getLambdaName(l))
.collect(Collectors.toList()), new Exception("caller's stack"));
}
if (DBG) Slog.d(TAG, "Adding listener: " + listener + " with filter " + filter);
mListeners.put(listener, Pair.create(filter, executor));
}
}
/**
* Removes a listener for {@link UserLifecycleEvent user lifecycle events}.
*
* @throws IllegalStateException if the listener was not added beforehand.
*
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
public void removeListener(@NonNull UserLifecycleListener listener) {
Objects.requireNonNull(listener, "listener cannot be null");
int uid = myUid();
String packageName = getContext().getPackageName();
if (DBG) {
Slog.d(TAG, "removeListener(): uid=" + uid + ", pkg=" + packageName
+ ", listener=" + listener);
}
synchronized (mLock) {
Preconditions.checkState(mListeners != null && mListeners.containsKey(listener),
"not called for this listener yet");
mListeners.remove(listener);
// Note that there can be some rare corner cases that a listener is removed but its
// corresponding filter remains in the service side. This may cause slight inefficiency
// due to unnecessary receiver calls. It will still be functionally correct, because the
// removed listener will no longer be invoked.
if (!mListeners.isEmpty()) {
if (DBG) Slog.d(TAG, "removeListeners(): still " + mListeners.size() + " left");
return;
}
mListeners = null;
if (mReceiver == null) {
Slog.wtf(TAG, "removeListener(): receiver already null");
return;
}
EventLogHelper.writeCarUserManagerRemoveListener(uid, packageName);
if (DBG) {
Slog.d(TAG, "Removing lifecycle receiver for uid=" + uid + " and package "
+ packageName);
}
try {
mService.resetLifecycleListenerForApp(mReceiver);
mReceiver = null;
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
}
/**
* Check if user hal supports user association.
*
* @hide
*/
public boolean isUserHalUserAssociationSupported() {
try {
return mService.isUserHalUserAssociationSupported();
} catch (RemoteException | RuntimeException e) {
return handleExceptionFromCarService(e, false);
}
}
/**
* Gets the user authentication types associated with this manager's user.
*
* @hide
*/
@NonNull
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public UserIdentificationAssociationResponse getUserIdentificationAssociation(
@UserIdentificationAssociationType int... types) {
Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
EventLogHelper.writeCarUserManagerGetUserAuthReq(convertToObjectArray(types));
try {
UserIdentificationAssociationResponse response =
mService.getUserIdentificationAssociation(types);
if (response != null) {
int[] values = response.getValues();
EventLogHelper.writeCarUserManagerGetUserAuthResp(convertToObjectArray(values));
}
return response;
} catch (SecurityException e) {
throw e;
} catch (RemoteException | RuntimeException e) {
return handleExceptionFromCarService(e,
UserIdentificationAssociationResponse.forFailure(e.getMessage()));
}
}
@Nullable
private Object[] convertToObjectArray(int[] input) {
if (input == null) return null;
Object[] output = new Object[input.length];
for (int i = 0; i < input.length; i++) {
output[i] = input[i];
}
return output;
}
/**
* Sets the user authentication types associated with this manager's user.
*
* @hide
*/
@NonNull
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public AsyncFuture setUserIdentificationAssociation(
@UserIdentificationAssociationType int[] types,
@UserIdentificationAssociationSetValue int[] values) {
Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
Preconditions.checkArgument(!ArrayUtils.isEmpty(values), "must have at least one value");
if (types.length != values.length) {
throw new IllegalArgumentException("types (" + Arrays.toString(types) + ") and values ("
+ Arrays.toString(values) + ") should have the same length");
}
// TODO(b/153900032): move this logic to a common helper
Object[] loggedValues = new Integer[types.length * 2];
for (int i = 0; i < types.length; i++) {
loggedValues[i * 2] = types[i];
loggedValues[i * 2 + 1 ] = values[i];
}
EventLogHelper.writeCarUserManagerSetUserAuthReq(loggedValues);
try {
AndroidFuture future =
new AndroidFuture() {
@Override
protected void onCompleted(UserIdentificationAssociationResponse result,
Throwable err) {
if (result != null) {
int[] rawValues = result.getValues();
// TODO(b/153900032): move this logic to a common helper
if (rawValues != null) {
Object[] loggedValues = new Object[rawValues.length];
for (int i = 0; i < rawValues.length; i++) {
loggedValues[i] = rawValues[i];
}
EventLogHelper.writeCarUserManagerSetUserAuthResp(loggedValues);
}
} else {
Slog.w(TAG, "setUserIdentificationAssociation(" + Arrays.toString(types)
+ ", " + Arrays.toString(values) + ") failed: " + err);
}
super.onCompleted(result, err);
};
};
mService.setUserIdentificationAssociation(HAL_TIMEOUT_MS, types, values, future);
return new AndroidAsyncFuture<>(future);
} catch (SecurityException e) {
throw e;
} catch (RemoteException | RuntimeException e) {
AndroidFuture future = new AndroidFuture<>();
future.complete(UserIdentificationAssociationResponse.forFailure());
return handleExceptionFromCarService(e, new AndroidAsyncFuture<>(future));
}
}
/**
* Sets a callback to be notified before user switch. It should only be used by Car System UI.
*
* @hide
* @deprecated use {@link #setUserSwitchUiCallback(Executor, UserHandleSwitchUiCallback)}
* instead.
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public void setUserSwitchUiCallback(@NonNull UserSwitchUiCallback callback) {
Preconditions.checkArgument(callback != null, "Null callback");
UserHandleSwitchUiCallback userHandleSwitchUiCallback = (userHandle) -> {
callback.showUserSwitchDialog(userHandle.getIdentifier());
};
setUserSwitchUiCallback(Runnable::run, userHandleSwitchUiCallback);
}
/**
* Sets a callback to be notified before user switch.
*
* It should only be used by Car System UI. Setting this callback will notify the Car
* System UI to show the user switching dialog.
*
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public void setUserSwitchUiCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull UserHandleSwitchUiCallback callback) {
Preconditions.checkArgument(callback != null, "Null callback");
UserSwitchUiCallbackReceiver userSwitchUiCallbackReceiver =
new UserSwitchUiCallbackReceiver(callback);
try {
mService.setUserSwitchUiCallback(userSwitchUiCallbackReceiver);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
// TODO(b/154958003): use mReceiver instead as now there are two binder objects
/**
* {@code ICarResultReceiver} used to receive user switch UI Callback.
*/
private final class UserSwitchUiCallbackReceiver extends ICarResultReceiver.Stub {
private final UserHandleSwitchUiCallback mUserHandleSwitchUiCallback;
UserSwitchUiCallbackReceiver(UserHandleSwitchUiCallback callback) {
mUserHandleSwitchUiCallback = callback;
}
@Override
public void send(int userId, Bundle unused) throws RemoteException {
mUserHandleSwitchUiCallback.onUserSwitchStart(UserHandle.of(userId));
}
}
/**
* {@code ICarResultReceiver} used to receive lifecycle events and dispatch to the proper
* listener.
*/
private class LifecycleResultReceiver extends ICarResultReceiver.Stub {
@Override
public void send(int resultCode, Bundle resultData) {
if (resultData == null) {
Slog.w(TAG, "Received result (" + resultCode + ") without data");
return;
}
int from = resultData.getInt(BUNDLE_PARAM_PREVIOUS_USER_ID,
UserManagerHelper.USER_NULL);
int to = resultCode;
int eventType = resultData.getInt(BUNDLE_PARAM_ACTION);
UserLifecycleEvent event = new UserLifecycleEvent(eventType, from, to);
ArrayMap> listeners;
synchronized (mLock) {
if (mListeners == null) {
Slog.w(TAG, "No listeners for event " + event);
return;
}
listeners = new ArrayMap<>(mListeners);
}
int size = listeners.size();
EventLogHelper.writeCarUserManagerNotifyLifecycleListener(size, eventType, from, to);
for (int i = 0; i < size; i++) {
UserLifecycleListener listener = listeners.keyAt(i);
UserLifecycleEventFilter filter = listeners.valueAt(i).first;
if (filter != null && !filter.apply(event)) {
if (DBG) {
Slog.d(TAG, "Listener " + getLambdaName(listener)
+ " is skipped for the event " + event + " due to the filter "
+ filter);
}
continue;
}
Executor executor = listeners.valueAt(i).second;
if (DBG) {
Slog.d(TAG, "Calling " + getLambdaName(listener) + " for event " + event);
}
executor.execute(() -> listener.onEvent(event));
}
mNumberReceivedEvents++;
if (VERBOSE) {
if (mEvents == null) {
mEvents = new ArrayList<>();
}
mEvents.add(event);
}
}
}
/** @hide */
@Override
public void onCarDisconnected() {
// nothing to do
}
private final class Dumper implements Dumpable {
@Override
public void dump(PrintWriter pw, String[] args) {
String prefix = " ";
pw.printf("DBG=%b, VERBOSE=%b\n", DBG, VERBOSE);
int listenersSize = 0;
synchronized (mLock) {
pw.printf("mReceiver: %s\n", mReceiver);
if (mListeners == null) {
pw.println("no listeners");
} else {
listenersSize = mListeners.size();
pw.printf("%d listeners\n", listenersSize);
}
if (DBG) {
for (int i = 0; i < listenersSize; i++) {
pw.printf("%s%d: %s\n", prefix, i + 1, mListeners.keyAt(i));
}
}
}
pw.printf("mNumberReceivedEvents: %d\n", mNumberReceivedEvents);
if (VERBOSE && mEvents != null) {
for (int i = 0; i < mEvents.size(); i++) {
pw.printf("%s%d: %s\n", prefix, i + 1, mEvents.get(i));
}
}
}
@Override
public String getDumpableName() {
return CarUserManager.class.getSimpleName();
}
}
/**
* @hide
*/
@SystemApi
@NonNull
public static String lifecycleEventTypeToString(@UserLifecycleEventType int type) {
switch (type) {
case USER_LIFECYCLE_EVENT_TYPE_STARTING:
return "STARTING";
case USER_LIFECYCLE_EVENT_TYPE_SWITCHING:
return "SWITCHING";
case USER_LIFECYCLE_EVENT_TYPE_UNLOCKING:
return "UNLOCKING";
case USER_LIFECYCLE_EVENT_TYPE_UNLOCKED:
return "UNLOCKED";
case USER_LIFECYCLE_EVENT_TYPE_STOPPING:
return "STOPPING";
case USER_LIFECYCLE_EVENT_TYPE_STOPPED:
return "STOPPED";
case USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED:
return "POST_UNLOCKED";
case USER_LIFECYCLE_EVENT_TYPE_CREATED:
return "CREATED";
case USER_LIFECYCLE_EVENT_TYPE_REMOVED:
return "REMOVED";
case USER_LIFECYCLE_EVENT_TYPE_VISIBLE:
return "VISIBLE";
case USER_LIFECYCLE_EVENT_TYPE_INVISIBLE:
return "INVISIBLE";
default:
return "UNKNOWN-" + type;
}
}
/**
* Checks if the given {@code userId} represents a valid user.
*
* A "valid" user:
*
*
* - Must exist in the device.
*
- Is not in the process of being deleted.
*
- Cannot be the {@link UserHandle#isSystem() system} user on devices that use
* {@link UserManager#isHeadlessSystemUserMode() headless system user mode}.
*
*
* @hide
* @deprecated use {@link #isValidUser(UserHandle)} instead.
*/
@Deprecated
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public boolean isValidUser(@UserIdInt int userId) {
return isValidUser(UserHandle.of(userId));
}
/**
* Checks if the given {@code userHandle} represents a valid user.
*
* A "valid" user:
*
*
* - Must exist in the device.
*
- Is not in the process of being deleted.
*
- Cannot be the {@link UserHandle#isSystem() system} user on devices that use
* {@link UserManager#isHeadlessSystemUserMode() headless system user mode}.
*
*
*/
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
@SuppressWarnings("UserHandle")
public boolean isValidUser(@NonNull UserHandle userHandle) {
List allUsers = mUserManager.getUserHandles(/* excludeDying=*/ true);
for (int i = 0; i < allUsers.size(); i++) {
UserHandle user = allUsers.get(i);
if (user.equals(userHandle) && (!userHandle.equals(UserHandle.SYSTEM)
|| !mIsHeadlessSystemUserMode)) {
return true;
}
}
return false;
}
/**
* Defines a lifecycle event for an Android user.
*
* @hide
*/
@SystemApi
public static final class UserLifecycleEvent {
private final @UserLifecycleEventType int mEventType;
private final @UserIdInt int mUserId;
private final @UserIdInt int mPreviousUserId;
/** @hide */
public UserLifecycleEvent(@UserLifecycleEventType int eventType,
@UserIdInt int from, @UserIdInt int to) {
mEventType = eventType;
mPreviousUserId = from;
mUserId = to;
}
/** @hide */
public UserLifecycleEvent(@UserLifecycleEventType int eventType, @UserIdInt int to) {
this(eventType, UserManagerHelper.USER_NULL, to);
}
/**
* Gets the event type.
*
* @return either {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_STARTING},
* {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_SWITCHING},
* {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_UNLOCKING},
* {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_UNLOCKED},
* {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_STOPPING} or
* {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_STOPPED} for all apps;
* for apps {@link CarPackageManager#getTargetCarVersion() targeting car version}
* {@link CarVersion.VERSION_CODES#TIRAMISU_1} or higher, it could be new types
* added on later releases, such as
* {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_CREATED},
* {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_REMOVED} and possibly others.
*
*/
@UserLifecycleEventType
public int getEventType() {
return mEventType;
}
/**
* Gets the id of the user whose event is being reported.
*
* @hide
*/
@UserIdInt
public int getUserId() {
return mUserId;
}
/**
* Gets the handle of the user whose event is being reported.
*/
@NonNull
public UserHandle getUserHandle() {
return UserHandle.of(mUserId);
}
/**
* Gets the id of the user being switched from.
*
* This method returns {@link UserHandle#USER_NULL} for all event types but
* {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_SWITCHING}.
*
* @hide
*/
@UserIdInt
public int getPreviousUserId() {
return mPreviousUserId;
}
/**
* Gets the handle of the user being switched from.
*
*
This method returns {@code null} for all event types but
* {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_SWITCHING}.
*/
@Nullable
public UserHandle getPreviousUserHandle() {
return mPreviousUserId == UserManagerHelper.USER_NULL ? null
: UserHandle.of(mPreviousUserId);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("Event[type=")
.append(lifecycleEventTypeToString(mEventType));
if (mPreviousUserId != UserManagerHelper.USER_NULL) {
builder
.append(",from=").append(mPreviousUserId)
.append(",to=").append(mUserId);
} else {
builder.append(",user=").append(mUserId);
}
return builder.append(']').toString();
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserLifecycleEvent that = (UserLifecycleEvent) o;
return mEventType == that.mEventType && mUserId == that.mUserId
&& mPreviousUserId == that.mPreviousUserId;
}
@Override
public int hashCode() {
int hash = 23;
hash = 17 * hash + mEventType;
hash = 17 * hash + mUserId;
hash = 17 * hash + mPreviousUserId;
return hash;
}
}
/**
* Listener for Android User lifecycle events.
*
*
Must be registered using {@link CarUserManager#addListener(UserLifecycleListener)} and
* unregistered through {@link CarUserManager#removeListener(UserLifecycleListener)}.
*
* @hide
*/
@SystemApi
public interface UserLifecycleListener {
/**
* Called to notify the given {@code event}.
*/
void onEvent(@NonNull UserLifecycleEvent event);
}
/**
* Callback for notifying user switch before switch started.
*
*
It should only be used by Car System UI. The purpose of this callback is to notify the
* Car System UI to display the user switch UI.
*
* @hide
* @deprecated use {@link #UserHandleSwitchUiCallback} instead.
*/
@Deprecated
public interface UserSwitchUiCallback {
/**
* Called to notify that user switch dialog should be shown now.
*/
void showUserSwitchDialog(@UserIdInt int userId);
}
/**
* Callback for notifying user switch before switch started.
*
* @hide
*/
@SystemApi
public interface UserHandleSwitchUiCallback {
/**
* Called before the user switch starts.
*
*
This is typically used to show the user dialog.
*/
@SuppressWarnings("UserHandleName")
void onUserSwitchStart(@NonNull UserHandle userHandle);
}
}