blob: 42c344b401a47e6e1e7efee73b2647fd27dd2079 [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;
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 android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
import android.util.Slog;
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.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService.TargetUser;
import com.android.server.utils.TimingsTraceAndSlog;
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{}
private static final boolean DBG = false;
private static final String TAG = CarServiceProxy.class.getSimpleName();
private static final long LIFECYCLE_TIMESTAMP_IGNORE = 0;
private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mCarServiceCrashed;
@UserIdInt
@GuardedBy("mLock")
private int mLastSwitchedUser = UserHandle.USER_NULL;
@UserIdInt
@GuardedBy("mLock")
private int mPreviousUserOfLastSwitchedUser = UserHandle.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 CarServiceHelperService mCarServiceHelperService;
private final UserMetrics mUserMetrics = new UserMetrics();
CarServiceProxy(CarServiceHelperService carServiceHelperService) {
mCarServiceHelperService = carServiceHelperService;
}
/**
* Handles new CarService Connection.
*/
void handleCarServiceConnection(ICarSystemServerClient carService) {
Slog.i(TAG, "CarService connected.");
TimingsTraceAndSlog t = newTimingsTraceAndSlog();
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();
t.traceEnd();
}
@GuardedBy("mLock")
private void runQueuedOperationLocked(@PendingOperationId int operationId) {
PendingOperation pendingOperation = mPendingOperations.get(operationId);
if (pendingOperation != null) {
runLocked(operationId, pendingOperation.value);
return;
}
if (DBG) {
Slog.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(UserHandle.USER_SYSTEM);
boolean user0IsCurrent = lastSwitchedUser == UserHandle.USER_SYSTEM;
// If user0Lifecycle is 0, then no life-cycle event received yet.
if (user0Lifecycle != 0) {
sendAllLifecyleToUser(UserHandle.USER_SYSTEM, user0Lifecycle, user0IsCurrent);
}
lastUserLifecycle.delete(UserHandle.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) {
sendAllLifecyleToUser(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);
sendAllLifecyleToUser(userId, lifecycle, /* isCurrentUser= */ false);
}
}
private void sendAllLifecyleToUser(@UserIdInt int userId, int lifecycle,
boolean isCurrentUser) {
if (DBG) {
Slog.d(TAG, "sendAllLifecyleToUser, user:" + userId + " lifecycle:" + lifecycle);
}
if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_STARTING) {
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, UserHandle.USER_NULL,
userId);
}
if (isCurrentUser && userId != UserHandle.USER_SYSTEM) {
synchronized (mLock) {
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
mPreviousUserOfLastSwitchedUser, userId);
}
}
if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKING) {
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, UserHandle.USER_NULL,
userId);
}
if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) {
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, UserHandle.USER_NULL,
userId);
}
}
/**
* Initializes boot user.
*/
void initBootUser() {
if (DBG) Slog.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 UserInfo user) {
if (DBG) Slog.d(TAG, "onUserRemoved(): " + user.toFullString());
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 IResultReceiver callback) {
if (DBG) Slog.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) {
Slog.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) Slog.d(TAG, "runLocked(): " + pendingOperationToString(operationId) + "/" + value);
try {
if (isServiceCrashedLoggedLocked(operationId)) {
return;
}
sendCarServiceActionLocked(operationId, value);
if (operationId == PO_ON_FACTORY_RESET) {
if (DBG) Slog.d(TAG, "NOT removing " + pendingOperationToString(operationId));
return;
}
if (DBG) Slog.d(TAG, "removing " + pendingOperationToString(operationId));
mPendingOperations.delete(operationId);
} catch (RemoteException e) {
Slog.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) Slog.d(TAG, "Created " + pendingOperation);
mPendingOperations.put(operationId, pendingOperation);
return;
}
switch (operationId) {
case PO_ON_USER_REMOVED:
Preconditions.checkArgument((value instanceof UserInfo),
"invalid value passed to ON_USER_REMOVED", value);
if (pendingOperation.value instanceof ArrayList) {
if (DBG) Slog.d(TAG, "Adding " + value + " to existing " + pendingOperation);
((ArrayList) pendingOperation.value).add(value);
} else if (pendingOperation.value instanceof UserInfo) {
ArrayList<Object> list = new ArrayList<>(2);
list.add(pendingOperation.value);
list.add(value);
if (DBG) Slog.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) Slog.d(TAG, "Replacing " + pendingOperation + " by " + newOperation);
mPendingOperations.put(operationId, newOperation);
break;
default:
if (DBG) {
Slog.d(TAG, "Already saved operation of type "
+ pendingOperationToString(operationId));
}
}
}
@GuardedBy("mLock")
private void sendCarServiceActionLocked(@PendingOperationId int operationId,
@Nullable Object value) throws RemoteException {
if (DBG) {
Slog.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) Slog.d(TAG, "Sending " + list.size() + " onUserRemoved() calls");
for (Object user: list) {
onUserRemovedLocked(user);
}
} else {
onUserRemovedLocked(value);
}
break;
case PO_ON_FACTORY_RESET:
mCarService.onFactoryReset((IResultReceiver) value);
break;
default:
Slog.wtf(TAG, "Invalid Operation. OperationId -" + operationId);
}
}
@GuardedBy("mLock")
private void onUserRemovedLocked(@NonNull Object value) throws RemoteException {
Preconditions.checkArgument((value instanceof UserInfo),
"Invalid value for ON_USER_REMOVED: %s", value);
UserInfo user = (UserInfo) value;
if (DBG) Slog.d(TAG, "Sending onUserRemoved(): " + user.toFullString());
mCarService.onUserRemoved(user);
}
/**
* Sends user life-cycle events to CarService.
*/
void sendUserLifecycleEvent(@UserLifecycleEventType int eventType, @Nullable TargetUser from,
@NonNull TargetUser to) {
long now = System.currentTimeMillis();
int fromId = from == null ? UserHandle.USER_NULL : from.getUserIdentifier();
int toId = to.getUserIdentifier();
mUserMetrics.onEvent(eventType, now, fromId, toId);
synchronized (mLock) {
if (eventType == USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
mLastSwitchedUser = to.getUserIdentifier();
mPreviousUserOfLastSwitchedUser = from.getUserIdentifier();
mLastUserLifecycle.put(to.getUserIdentifier(), eventType);
} else if (eventType == USER_LIFECYCLE_EVENT_TYPE_STOPPING
|| eventType == USER_LIFECYCLE_EVENT_TYPE_STOPPED) {
mLastUserLifecycle.delete(to.getUserIdentifier());
} else {
mLastUserLifecycle.put(to.getUserIdentifier(), eventType);
}
if (mCarService == null) {
if (DBG) {
Slog.d(TAG, "CarService null. sendUserLifecycleEvent() deferred for lifecycle"
+ " event " + eventType + " for user " + to);
}
return;
}
}
sendUserLifecycleEvent(eventType, fromId, toId);
}
private void sendUserLifecycleEvent(@UserLifecycleEventType int eventType,
@UserIdInt int fromId, @UserIdInt int toId) {
if (DBG) {
Slog.d(TAG, "sendUserLifecycleEvent():" + " eventType=" + eventType + ", fromId="
+ fromId + ", toId=" + toId);
}
try {
synchronized (mLock) {
if (isServiceCrashedLoggedLocked("sendUserLifecycleEvent")) return;
mCarService.onUserLifecycleEvent(eventType, fromId, toId);
}
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException from car service", e);
handleCarServiceCrash();
}
}
private void handleCarServiceCrash() {
synchronized (mLock) {
mCarServiceCrashed = true;
mCarService = null;
}
Slog.w(TAG, "CarServiceCrashed. No more car service calls before reconnection.");
mCarServiceHelperService.handleCarServiceCrash();
}
private TimingsTraceAndSlog newTimingsTraceAndSlog() {
return new TimingsTraceAndSlog(TAG, Trace.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) {
Slog.w(TAG, "CarServiceCrashed. " + operation + " will be executed after reconnection");
return true;
}
return false;
}
/**
* Dump
*/
void dump(IndentingPrintWriter writer) {
writer.println("CarServiceProxy");
writer.increaseIndent();
writer.printf("mLastSwitchedUser=%s\n", mLastSwitchedUser);
writer.printf("mLastUserLifecycle:\n");
int user0Lifecycle = mLastUserLifecycle.get(UserHandle.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 != UserHandle.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);
}
}