blob: 4f830cb6daa93c1a56655757eb5cc1309a81a576 [file] [log] [blame]
/*
* Copyright (C) 2020 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.internal.car.updatable;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_CREATED;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_INVISIBLE;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_REMOVED;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_VISIBLE;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.car.ICarResultReceiver;
import android.car.builtin.os.UserManagerHelper;
import android.car.builtin.util.Slogf;
import android.car.builtin.util.TimingsTraceLog;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.car.internal.ICarSystemServerClient;
import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
import com.android.car.internal.util.DebugUtils;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
* Manages CarService operations requested by CarServiceHelperService.
*
* <p>
* It is used to send and re-send binder calls to CarService when it connects and dies & reconnects.
* It does not simply queue the operations, because it needs to "replay" some of them on every
* reconnection.
*/
final class CarServiceProxy {
/*
* The logic of re-queue:
*
* There are two sparse array - mLastUserLifecycle and mPendingOperations
*
* First sparse array - mLastUserLifecycle - is to keep track of the life-cycle events for each
* user. It would have the last life-cycle event of each running user (typically user 0 and the
* current user). All life-cycle events seen so far would be replayed on connection and
* reconnection.
*
* Second sparse array - mPendingOperations - would keep all the non-life-cycle events related
* operations, which are represented by PendintOperation and PendingOperationId.
* Most operations (like initBootUser and preCreateUsers) just need to be sent only, but some
* need to be queued (like onUserRemoved).
*/
// Operation ID for each non life-cycle event calls
// NOTE: public because of DebugUtils
public static final int PO_INIT_BOOT_USER = 0;
public static final int PO_ON_USER_REMOVED = 1;
public static final int PO_ON_FACTORY_RESET = 2;
@IntDef(prefix = { "PO_" }, value = {
PO_INIT_BOOT_USER,
PO_ON_USER_REMOVED,
PO_ON_FACTORY_RESET
})
@Retention(RetentionPolicy.SOURCE)
public @interface PendingOperationId{}
@VisibleForTesting
static final String TAG = CarServiceProxy.class.getSimpleName();
private static final long TRACE_TAG_SYSTEM_SERVER = 1L << 19;
private static final boolean DBG = false;
private static final int USER_SYSTEM = UserHandle.SYSTEM.getIdentifier();
private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mCarServiceCrashed;
@UserIdInt
@GuardedBy("mLock")
private int mLastSwitchedUser = UserManagerHelper.USER_NULL;
@UserIdInt
@GuardedBy("mLock")
private int mPreviousUserOfLastSwitchedUser = UserManagerHelper.USER_NULL;
// Key: user id, value: life-cycle
@GuardedBy("mLock")
private final SparseIntArray mLastUserLifecycle = new SparseIntArray();
// Key: @PendingOperationId, value: PendingOperation
@GuardedBy("mLock")
private final SparseArray<PendingOperation> mPendingOperations = new SparseArray<>();
@GuardedBy("mLock")
private ICarSystemServerClient mCarService;
private final CarServiceHelperServiceUpdatableImpl mCarServiceHelperServiceUpdatableImpl;
private final UserMetrics mUserMetrics = new UserMetrics();
@GuardedBy("mLock")
private UserHandle mInitialUser;
CarServiceProxy(CarServiceHelperServiceUpdatableImpl carServiceHelperServiceUpdatableImpl) {
mCarServiceHelperServiceUpdatableImpl = carServiceHelperServiceUpdatableImpl;
}
/**
* Handles new CarService Connection.
*/
void handleCarServiceConnection(ICarSystemServerClient carService) {
Slogf.i(TAG, "CarService connected.");
TimingsTraceLog t = newTimingsTraceLog();
t.traceBegin("handleCarServiceConnection");
synchronized (mLock) {
mCarService = carService;
mCarServiceCrashed = false;
runQueuedOperationLocked(PO_INIT_BOOT_USER);
runQueuedOperationLocked(PO_ON_USER_REMOVED);
runQueuedOperationLocked(PO_ON_FACTORY_RESET);
}
sendLifeCycleEvents();
sendInitialUser();
t.traceEnd();
}
private void sendInitialUser() {
UserHandle initialUser;
ICarSystemServerClient carService;
synchronized (mLock) {
initialUser = mInitialUser;
carService = mCarService;
}
if (initialUser != null && carService != null) {
try {
carService.setInitialUser(initialUser);
} catch (RemoteException e) {
Slogf.w(TAG, "RemoteException from car service while calling setInitialUser.", e);
}
} else {
Slogf.i(TAG, "Didn't send Initial User, User: %s, CarService: %s", initialUser,
carService);
}
}
void saveInitialUser(UserHandle user) {
synchronized (mLock) {
if (user != null || user.getIdentifier() != UserManagerHelper.USER_NULL) {
mInitialUser = user;
}
}
}
@GuardedBy("mLock")
private void runQueuedOperationLocked(@PendingOperationId int operationId) {
PendingOperation pendingOperation = mPendingOperations.get(operationId);
if (pendingOperation != null) {
runLocked(operationId, pendingOperation.value);
return;
}
if (DBG) {
Slogf.d(TAG, "No queued operation of type " + pendingOperationToString(operationId));
}
}
private void sendLifeCycleEvents() {
int lastSwitchedUser;
SparseIntArray lastUserLifecycle;
synchronized (mLock) {
lastSwitchedUser = mLastSwitchedUser;
lastUserLifecycle = mLastUserLifecycle.clone();
}
// Send user0 events first
int user0Lifecycle = lastUserLifecycle.get(USER_SYSTEM);
boolean user0IsCurrent = lastSwitchedUser == USER_SYSTEM;
// If user0Lifecycle is 0, then no life-cycle event received yet.
if (user0Lifecycle != 0) {
sendAllLifecycleToUser(USER_SYSTEM, user0Lifecycle,
user0IsCurrent);
}
lastUserLifecycle.delete(USER_SYSTEM);
// Send current user events next
if (!user0IsCurrent) {
int currentUserLifecycle = lastUserLifecycle.get(lastSwitchedUser);
// If currentUserLifecycle is 0, then no life-cycle event received yet.
if (currentUserLifecycle != 0) {
sendAllLifecycleToUser(lastSwitchedUser, currentUserLifecycle,
/* isCurrentUser= */ true);
}
}
lastUserLifecycle.delete(lastSwitchedUser);
// Send all other users' events
for (int i = 0; i < lastUserLifecycle.size(); i++) {
int userId = lastUserLifecycle.keyAt(i);
int lifecycle = lastUserLifecycle.valueAt(i);
sendAllLifecycleToUser(userId, lifecycle, /* isCurrentUser= */ false);
}
}
private void sendAllLifecycleToUser(@UserIdInt int userId, int lifecycle,
boolean isCurrentUser) {
if (DBG) {
Slogf.d(TAG, "sendAllLifecycleToUser, user:" + userId + " lifecycle:" + lifecycle);
}
// User created and user removed are unrelated to the user switching/unlocking flow.
// Return early to prevent them from going into the following logic
// that makes assumptions about the sequence of lifecycle event types
// following numerical order.
if (lifecycle == USER_LIFECYCLE_EVENT_TYPE_CREATED) {
sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_CREATED,
UserManagerHelper.USER_NULL, userId);
return;
}
if (lifecycle == USER_LIFECYCLE_EVENT_TYPE_REMOVED) {
sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_REMOVED,
UserManagerHelper.USER_NULL, userId);
return;
}
// User visible and user invisible are unrelated to the user switching/unlocking flow.
// Return early to prevent them from going into the following logic
// that makes assumptions about the sequence of lifecycle event types
// following numerical order.
// If we don't return early here, because the user visible and visible event numbers are
// greater than user starting/switching/unlocking/unlocked events, they will cause these
// events to be sent which is an unintended effect.
// TODO(b/277148129): Refactor the entire lifecycle events replay logic taking into
// consideration the visible and invisible events. Currently only the last event per use is
// tracked so it's hard to infer events before user visible and user invisible.
if (lifecycle == USER_LIFECYCLE_EVENT_TYPE_VISIBLE) {
sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_VISIBLE,
UserManagerHelper.USER_NULL, userId);
return;
}
if (lifecycle == USER_LIFECYCLE_EVENT_TYPE_INVISIBLE) {
sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_INVISIBLE,
UserManagerHelper.USER_NULL, userId);
return;
}
// The following logic makes assumptions about the sequence of lifecycle event types
// following numerical order.
if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_STARTING) {
sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_STARTING,
UserManagerHelper.USER_NULL, userId);
}
if (isCurrentUser && userId != USER_SYSTEM) {
synchronized (mLock) {
sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
mPreviousUserOfLastSwitchedUser, userId);
}
}
if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKING) {
sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
UserManagerHelper.USER_NULL, userId);
}
if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) {
sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
UserManagerHelper.USER_NULL, userId);
}
}
/**
* Initializes boot user.
*/
void initBootUser() {
if (DBG) Slogf.d(TAG, "initBootUser()");
saveOrRun(PO_INIT_BOOT_USER);
}
// TODO(b/173664653): add unit test
/**
* Callback to indifcate the given user was removed.
*/
void onUserRemoved(@NonNull UserHandle user) {
if (DBG) Slogf.d(TAG, "onUserRemoved(): " + user);
saveOrRun(PO_ON_USER_REMOVED, user);
}
// TODO(b/173664653): add unit test
/**
* Callback to ask user to confirm if it's ok to factory reset the device.
*/
void onFactoryReset(@NonNull ICarResultReceiver callback) {
if (DBG) Slogf.d(TAG, "onFactoryReset(): " + callback);
saveOrRun(PO_ON_FACTORY_RESET, callback);
}
private void saveOrRun(@PendingOperationId int operationId) {
saveOrRun(operationId, /* value= */ null);
}
private void saveOrRun(@PendingOperationId int operationId, @Nullable Object value) {
synchronized (mLock) {
if (mCarService == null) {
if (DBG) {
Slogf.d(TAG, "CarService null. Operation "
+ pendingOperationToString(operationId)
+ (value == null ? "" : "(" + value + ")") + " deferred.");
}
savePendingOperationLocked(operationId, value);
return;
}
if (operationId == PO_ON_FACTORY_RESET) {
// Must always persist it, so it's sent again if CarService is crashed before
// the next reboot or suspension-to-ram
savePendingOperationLocked(operationId, value);
}
runLocked(operationId, value);
}
}
@GuardedBy("mLock")
private void runLocked(@PendingOperationId int operationId, @Nullable Object value) {
if (DBG) {
Slogf.d(TAG, "runLocked(): " + pendingOperationToString(operationId) + "/" + value);
}
try {
if (isServiceCrashedLoggedLocked(operationId)) {
return;
}
sendCarServiceActionLocked(operationId, value);
if (operationId == PO_ON_FACTORY_RESET) {
if (DBG) Slogf.d(TAG, "NOT removing " + pendingOperationToString(operationId));
return;
}
if (DBG) Slogf.d(TAG, "removing " + pendingOperationToString(operationId));
mPendingOperations.delete(operationId);
} catch (RemoteException e) {
Slogf.w(TAG, "RemoteException from car service", e);
handleCarServiceCrash();
}
}
@GuardedBy("mLock")
private void savePendingOperationLocked(@PendingOperationId int operationId,
@Nullable Object value) {
PendingOperation pendingOperation = mPendingOperations.get(operationId);
if (pendingOperation == null) {
pendingOperation = new PendingOperation(operationId, value);
if (DBG) Slogf.d(TAG, "Created " + pendingOperation);
mPendingOperations.put(operationId, pendingOperation);
return;
}
switch (operationId) {
case PO_ON_USER_REMOVED:
Preconditions.checkArgument((value instanceof UserHandle),
"invalid value passed to ON_USER_REMOVED", value);
if (pendingOperation.value instanceof ArrayList) {
if (DBG) Slogf.d(TAG, "Adding " + value + " to existing " + pendingOperation);
((ArrayList) pendingOperation.value).add(value);
} else if (pendingOperation.value instanceof UserHandle) {
ArrayList<Object> list = new ArrayList<>(2);
list.add(pendingOperation.value);
list.add(value);
if (DBG) Slogf.d(TAG, "Converting " + pendingOperation.value + " to " + list);
pendingOperation.value = list;
} else {
throw new IllegalStateException("Invalid value for ON_USER_REMOVED: " + value);
}
break;
case PO_ON_FACTORY_RESET:
PendingOperation newOperation = new PendingOperation(operationId, value);
if (DBG) Slogf.d(TAG, "Replacing " + pendingOperation + " by " + newOperation);
mPendingOperations.put(operationId, newOperation);
break;
default:
if (DBG) {
Slogf.d(TAG, "Already saved operation of type "
+ pendingOperationToString(operationId));
}
}
}
@GuardedBy("mLock")
private void sendCarServiceActionLocked(@PendingOperationId int operationId,
@Nullable Object value) throws RemoteException {
if (DBG) {
Slogf.d(TAG, "sendCarServiceActionLocked: Operation "
+ pendingOperationToString(operationId));
}
switch (operationId) {
case PO_INIT_BOOT_USER:
mCarService.initBootUser();
break;
case PO_ON_USER_REMOVED:
if (value instanceof ArrayList) {
ArrayList<Object> list = (ArrayList<Object>) value;
if (DBG) Slogf.d(TAG, "Sending " + list.size() + " onUserRemoved() calls");
for (Object user: list) {
onUserRemovedLocked(user);
}
} else {
onUserRemovedLocked(value);
}
break;
case PO_ON_FACTORY_RESET:
mCarService.onFactoryReset((ICarResultReceiver) value);
break;
default:
Slogf.wtf(TAG, "Invalid Operation. OperationId -" + operationId);
}
}
@GuardedBy("mLock")
private void onUserRemovedLocked(@NonNull Object value) throws RemoteException {
Preconditions.checkArgument((value instanceof UserHandle),
"Invalid value for ON_USER_REMOVED: %s", value);
UserHandle user = (UserHandle) value;
// TODO(235524989): Consolidating logging with other lifecycle events,
// including user metrics.
if (DBG) Slogf.d(TAG, "Sending onUserRemoved(): " + user);
mCarService.onUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_REMOVED,
UserManagerHelper.USER_NULL, user.getIdentifier());
}
/**
* Sends user life-cycle events to CarService.
*/
void sendUserLifecycleEvent(@UserLifecycleEventType int eventType, @UserIdInt int fromId,
@UserIdInt int toId) {
long now = System.currentTimeMillis();
mUserMetrics.onEvent(eventType, now, fromId, toId);
synchronized (mLock) {
if (eventType == USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
mLastSwitchedUser = toId;
mPreviousUserOfLastSwitchedUser = fromId;
mLastUserLifecycle.put(toId, eventType);
} else if (eventType == USER_LIFECYCLE_EVENT_TYPE_STOPPING
|| eventType == USER_LIFECYCLE_EVENT_TYPE_STOPPED) {
mLastUserLifecycle.delete(toId);
} else {
mLastUserLifecycle.put(toId, eventType);
}
if (mCarService == null) {
if (DBG) {
Slogf.d(TAG, "CarService null. sendUserLifecycleEvent() deferred for lifecycle"
+ " event " + eventType + " for user " + toId);
}
return;
}
}
sendUserLifecycleEventInternal(eventType, fromId, toId);
}
private void sendUserLifecycleEventInternal(@UserLifecycleEventType int eventType,
@UserIdInt int fromId, @UserIdInt int toId) {
if (DBG) {
Slogf.d(TAG, "sendUserLifecycleEvent():" + " eventType=" + eventType + ", fromId="
+ fromId + ", toId=" + toId);
}
try {
synchronized (mLock) {
if (isServiceCrashedLoggedLocked("sendUserLifecycleEvent")) return;
mCarService.onUserLifecycleEvent(eventType, fromId, toId);
}
} catch (RemoteException e) {
Slogf.w(TAG, "RemoteException from car service", e);
handleCarServiceCrash();
}
}
private void handleCarServiceCrash() {
synchronized (mLock) {
mCarServiceCrashed = true;
mCarService = null;
}
Slogf.w(TAG, "CarServiceCrashed. No more car service calls before reconnection.");
mCarServiceHelperServiceUpdatableImpl.handleCarServiceCrash();
}
private TimingsTraceLog newTimingsTraceLog() {
return new TimingsTraceLog(TAG, TRACE_TAG_SYSTEM_SERVER);
}
@GuardedBy("mLock")
private boolean isServiceCrashedLoggedLocked(@PendingOperationId int operationId) {
return isServiceCrashedLoggedLocked(pendingOperationToString(operationId));
}
@GuardedBy("mLock")
private boolean isServiceCrashedLoggedLocked(@NonNull String operation) {
if (mCarServiceCrashed) {
Slogf.w(TAG, "CarServiceCrashed. " + operation + " will be executed after "
+ "reconnection");
return true;
}
return false;
}
/**
* Dump
*/
void dump(IndentingPrintWriter writer) {
// Do not change the next line, Used in cts test: testCarServiceHelperServiceDump
writer.println("CarServiceProxy");
writer.increaseIndent();
synchronized (mLock) {
writer.printf("mLastSwitchedUser=%s\n", mLastSwitchedUser);
writer.printf("mInitialUser=%s\n", mInitialUser);
writer.printf("mLastUserLifecycle:\n");
int user0Lifecycle = mLastUserLifecycle.get(USER_SYSTEM, 0);
if (user0Lifecycle != 0) {
writer.printf("SystemUser Lifecycle Event:%s\n", user0Lifecycle);
} else {
writer.println("SystemUser not initialized");
}
int lastUserLifecycle = mLastUserLifecycle.get(mLastSwitchedUser, 0);
if (mLastSwitchedUser != USER_SYSTEM && user0Lifecycle != 0) {
writer.printf("last user (%s) Lifecycle Event:%s\n",
mLastSwitchedUser, lastUserLifecycle);
}
int size = mPendingOperations.size();
if (size == 0) {
writer.println("No pending operations");
} else {
writer.printf("%d pending operation%s:\n", size, size == 1 ? "" : "s");
writer.increaseIndent();
for (int i = 0; i < size; i++) {
writer.println(mPendingOperations.valueAt(i));
}
writer.decreaseIndent();
}
}
writer.decreaseIndent();
dumpUserMetrics(writer);
}
/**
* Dump User metrics
*/
void dumpUserMetrics(IndentingPrintWriter writer) {
mUserMetrics.dump(writer);
}
private final class PendingOperation {
public final int id;
public @Nullable Object value;
PendingOperation(int id, @Nullable Object value) {
this.id = id;
this.value = value;
}
@Override
public String toString() {
return "PendingOperation[" + pendingOperationToString(id)
+ (value == null ? "" : ": " + value) + "]";
}
}
@NonNull
private String pendingOperationToString(@PendingOperationId int operationType) {
return DebugUtils.constantToString(CarServiceProxy.class, "PO_" , operationType);
}
}