summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2021-10-06 22:53:37 +0000
committerXin Li <delphij@google.com>2021-10-06 22:53:37 +0000
commit4ff5a633fe1257922a6171fce69e0be5d8c668c6 (patch)
tree25b75eb6f8866a57e23f4b4eadc8121e71e9fd60
parentaf2f99eec3ae37659da0d3edc70e37c08a9ccc5a (diff)
parent3d90e911fd5eabc54fe998a4a36dd8c80033521e (diff)
downloadservices-android-s-v2-preview-1.tar.gz
Bug: 202323961 Merged-In: I7452a5f39d9b55361153edf7919c59de59c8656c Change-Id: I5d02df6aa32f956881f6b57ae094000827a2c532
-rw-r--r--Android.bp10
-rw-r--r--src/com/android/internal/car/CarDevicePolicySafetyChecker.java131
-rw-r--r--src/com/android/internal/car/CarServiceHelperService.java1054
-rw-r--r--src/com/android/internal/car/CarServiceProxy.java512
-rw-r--r--src/com/android/internal/car/ExternalConstants.java62
-rw-r--r--src/com/android/internal/car/ICarServiceHelper.aidl48
-rw-r--r--src/com/android/internal/car/UserMetrics.java355
-rw-r--r--src/com/android/server/wm/CarLaunchParamsModifier.java147
-rw-r--r--tests/Android.mk7
-rw-r--r--tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java150
-rw-r--r--tests/src/com/android/internal/car/CarHelperServiceTest.java1232
-rw-r--r--tests/src/com/android/internal/car/CarServiceHelperServiceTest.java292
-rw-r--r--tests/src/com/android/internal/car/CarServiceProxyTest.java224
-rw-r--r--tests/src/com/android/internal/car/UserMetricsTest.java239
-rw-r--r--tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java176
15 files changed, 2423 insertions, 2216 deletions
diff --git a/Android.bp b/Android.bp
index cb13569..d26195e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5,21 +5,19 @@ package {
java_library {
name: "car-frameworks-service",
installable: true,
-
libs: [
"services",
- "android.car.internal.event-log-tags",
+ "android.hardware.automotive.vehicle-V2.0-java",
+ "com.android.car.internal.common",
],
required: ["libcar-framework-service-jni"],
srcs: [
"src/**/*.java",
- "src/com/android/internal/car/ICarServiceHelper.aidl",
],
static_libs: [
- "android.hardware.automotive.vehicle-V2.0-java",
- "android.car.userlib",
"android.car.watchdoglib",
- "carwatchdog_aidl_interface-V2-java",
+ "com.android.car.internal.system",
+ "android.automotive.watchdog.internal-java",
],
}
diff --git a/src/com/android/internal/car/CarDevicePolicySafetyChecker.java b/src/com/android/internal/car/CarDevicePolicySafetyChecker.java
new file mode 100644
index 0000000..0bc0c21
--- /dev/null
+++ b/src/com/android/internal/car/CarDevicePolicySafetyChecker.java
@@ -0,0 +1,131 @@
+/*
+ * 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 android.app.admin.DevicePolicyManager.OPERATION_CLEAR_APPLICATION_USER_DATA;
+import static android.app.admin.DevicePolicyManager.OPERATION_LOGOUT_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REBOOT;
+import static android.app.admin.DevicePolicyManager.OPERATION_REQUEST_BUGREPORT;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_APPLICATION_HIDDEN;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_KEYGUARD_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_STATUS_BAR_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING;
+import static android.app.admin.DevicePolicyManager.OPERATION_SWITCH_USER;
+import static android.app.admin.DevicePolicyManager.operationToString;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManagerLiteInternal;
+import android.app.admin.DevicePolicySafetyChecker;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+/**
+ * Integrates {@link android.app.admin.DevicePolicyManager} operations with car UX restrictions.
+ */
+final class CarDevicePolicySafetyChecker {
+
+ private static final String TAG = CarDevicePolicySafetyChecker.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ private static final int[] UNSAFE_OPERATIONS = new int[] {
+ OPERATION_CLEAR_APPLICATION_USER_DATA,
+ OPERATION_LOGOUT_USER,
+ OPERATION_REBOOT,
+ OPERATION_REQUEST_BUGREPORT,
+ OPERATION_SET_APPLICATION_HIDDEN,
+ OPERATION_SET_APPLICATION_RESTRICTIONS,
+ OPERATION_SET_KEYGUARD_DISABLED,
+ OPERATION_SET_LOCK_TASK_FEATURES,
+ OPERATION_SET_LOCK_TASK_PACKAGES,
+ OPERATION_SET_PACKAGES_SUSPENDED,
+ OPERATION_SET_STATUS_BAR_DISABLED,
+ OPERATION_SET_SYSTEM_SETTING,
+ OPERATION_SWITCH_USER
+ };
+
+ private final AtomicBoolean mSafe = new AtomicBoolean(true);
+
+ private final DevicePolicySafetyChecker mCheckerImplementation;
+ private final DevicePolicyManagerLiteInternal mDpmi;
+
+ CarDevicePolicySafetyChecker(DevicePolicySafetyChecker checkerImplementation) {
+ this(checkerImplementation,
+ LocalServices.getService(DevicePolicyManagerLiteInternal.class));
+ }
+
+ @VisibleForTesting
+ CarDevicePolicySafetyChecker(DevicePolicySafetyChecker checkerImplementation,
+ DevicePolicyManagerLiteInternal dpmi) {
+ mCheckerImplementation = Objects.requireNonNull(checkerImplementation,
+ "DevicePolicySafetyChecker cannot be null");
+ mDpmi = Objects.requireNonNull(dpmi, "DevicePolicyManagerLiteInternal cannot be null");
+ }
+
+ boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation) {
+ boolean safe = true;
+ boolean globalSafe = mSafe.get();
+ if (!globalSafe) {
+ for (int unsafeOperation : UNSAFE_OPERATIONS) {
+ if (unsafeOperation == operation) {
+ safe = false;
+ break;
+ }
+ }
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "isDevicePolicyOperationSafe(" + operationToString(operation)
+ + "): " + safe + " (mSafe=" + globalSafe + ")");
+ }
+ return safe;
+ }
+
+ // TODO(b/172376923): override getUnsafeStateException to show error message explaining how to
+ // wrap it under CarDevicePolicyManager
+
+ void setSafe(boolean safe) {
+ Slog.i(TAG, "Setting safe to " + safe);
+ mSafe.set(safe);
+
+ mDpmi.notifyUnsafeOperationStateChanged(mCheckerImplementation,
+ OPERATION_SAFETY_REASON_DRIVING_DISTRACTION, /* isSafe= */ safe);
+ }
+
+ boolean isSafe() {
+ return mSafe.get();
+ }
+
+ void dump(@NonNull IndentingPrintWriter pw) {
+ pw.printf("Safe to run device policy operations: %b\n", mSafe.get());
+ pw.printf("Unsafe operations: %s\n", Arrays.stream(UNSAFE_OPERATIONS)
+ .mapToObj(o -> operationToString(o)).collect(Collectors.toList()));
+ }
+}
diff --git a/src/com/android/internal/car/CarServiceHelperService.java b/src/com/android/internal/car/CarServiceHelperService.java
index da96391..8e82316 100644
--- a/src/com/android/internal/car/CarServiceHelperService.java
+++ b/src/com/android/internal/car/CarServiceHelperService.java
@@ -16,30 +16,26 @@
package com.android.internal.car;
-import static android.car.userlib.UserHelper.safeName;
-
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
+import static com.android.car.internal.SystemConstants.ICAR_SYSTEM_SERVER_CLIENT;
+import static com.android.car.internal.common.CommonConstants.CAR_SERVICE_INTERFACE;
+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.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager;
-import android.automotive.watchdog.ICarWatchdogMonitor;
-import android.automotive.watchdog.PowerCycle;
-import android.automotive.watchdog.StateType;
-import android.car.userlib.CarUserManagerHelper;
-import android.car.userlib.CommonConstants.CarUserServiceConstants;
-import android.car.userlib.HalCallback;
-import android.car.userlib.InitialUserSetter;
-import android.car.userlib.InitialUserSetter.InitialUserInfoType;
-import android.car.userlib.UserHalHelper;
-import android.car.userlib.UserHelper;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
+import android.app.admin.DevicePolicySafetyChecker;
+import android.automotive.watchdog.internal.ICarWatchdogMonitor;
+import android.automotive.watchdog.internal.PowerCycle;
+import android.automotive.watchdog.internal.StateType;
import android.car.watchdoglib.CarWatchdogDaemonHelper;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -48,37 +44,40 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.UserInfo;
-import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
-import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
import android.hidl.manager.V1_0.IServiceManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.sysprop.CarProperties;
import android.util.EventLog;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
+import android.util.IndentingPrintWriter;
import android.util.TimeUtils;
+import com.android.car.internal.ICarServiceHelper;
+import com.android.car.internal.ICarSystemServerClient;
+import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
+import com.android.car.internal.common.EventLogTags;
+import com.android.car.internal.common.UserHelperLite;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.car.ExternalConstants.ICarConstants;
import com.android.internal.os.IResultReceiver;
+import com.android.server.Dumpable;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
+import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.CarLaunchParamsModifier;
@@ -101,8 +100,9 @@ import java.util.concurrent.Executors;
* System service side companion service for CarService. Starts car service and provide necessary
* API for CarService. Only for car product.
*/
-public class CarServiceHelperService extends SystemService {
- // Place holder for user name of the first user created.
+public class CarServiceHelperService extends SystemService
+ implements Dumpable, DevicePolicySafetyChecker {
+
private static final String TAG = "CarServiceHelper";
// TODO(b/154033860): STOPSHIP if they're still true
@@ -117,74 +117,40 @@ public class CarServiceHelperService extends SystemService {
"android.hardware.automotive.audiocontrol@2.0::IAudioControl"
);
- // Message ID representing HAL timeout handling.
- private static final int WHAT_HAL_TIMEOUT = 1;
// Message ID representing post-processing of process dumping.
- private static final int WHAT_POST_PROCESS_DUMPING = 2;
+ private static final int WHAT_POST_PROCESS_DUMPING = 1;
// Message ID representing process killing.
- private static final int WHAT_PROCESS_KILL = 3;
-
- private static final long LIFECYCLE_TIMESTAMP_IGNORE = 0;
+ private static final int WHAT_PROCESS_KILL = 2;
+ // Message ID representing service unresponsiveness.
+ private static final int WHAT_SERVICE_UNRESPONSIVE = 3;
- // Typically there are ~2-5 ops while system and non-system users are starting.
- private final int NUMBER_PENDING_OPERATIONS = 5;
+ private static final long CAR_SERVICE_BINDER_CALL_TIMEOUT = 15_000;
- @UserIdInt
- @GuardedBy("mLock")
- private int mLastSwitchedUser = UserHandle.USER_NULL;
+ private static final long LIFECYCLE_TIMESTAMP_IGNORE = 0;
private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl();
private final Context mContext;
private final Object mLock = new Object();
@GuardedBy("mLock")
- private IBinder mCarService;
+ private IBinder mCarServiceBinder;
@GuardedBy("mLock")
private boolean mSystemBootCompleted;
- // Key: user id, value: lifecycle
- @GuardedBy("mLock")
- private final SparseIntArray mLastUserLifecycle = new SparseIntArray();
-
- private final CarUserManagerHelper mCarUserManagerHelper;
- private final InitialUserSetter mInitialUserSetter;
- private final UserManager mUserManager;
private final CarLaunchParamsModifier mCarLaunchParamsModifier;
- private final boolean mHalEnabled;
- private final int mHalTimeoutMs;
-
- // Handler is currently only used for handleHalTimedout(), which is removed once received.
- private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler;
+ private final HandlerThread mHandlerThread = new HandlerThread("CarServiceHelperService");
private final ProcessTerminator mProcessTerminator = new ProcessTerminator();
-
- @GuardedBy("mLock")
- private boolean mInitialized;
+ private final CarServiceConnectedCallback mCarServiceConnectedCallback =
+ new CarServiceConnectedCallback();
+ private final CarServiceProxy mCarServiceProxy;
/**
* End-to-end time (from process start) for unlocking the first non-system user.
*/
private long mFirstUnlockedUserDuration;
- /**
- * Used to calculate how long it took to get the {@code INITIAL_USER_INFO} response from HAL:
- *
- * <ul>
- * <li>{@code 0}: HAL not called yet
- * <li>{@code <0}: stores the time HAL was called (multiplied by -1)
- * <li>{@code >0}: contains the duration (in ms)
- * </ul>
- */
- private int mHalResponseTime;
-
- // TODO(b/150413515): rather than store Runnables, it would be more efficient to store some
- // parcelables representing the operation, then pass them to setCarServiceHelper
- @GuardedBy("mLock")
- private ArrayList<Runnable> mPendingOperations;
-
- @GuardedBy("mLock")
- private boolean mCarServiceHasCrashed;
-
private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
private final ICarWatchdogMonitorImpl mCarWatchdogMonitor = new ICarWatchdogMonitorImpl(this);
private final CarWatchdogDaemonHelper.OnConnectionChangeListener mConnectionListener =
@@ -198,7 +164,7 @@ public class CarServiceHelperService extends SystemService {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
if (DBG) {
- Slog.d(TAG, "onServiceConnected:" + iBinder);
+ Slogf.d(TAG, "onServiceConnected: %s", iBinder);
}
handleCarServiceConnection(iBinder);
}
@@ -220,89 +186,76 @@ public class CarServiceHelperService extends SystemService {
|| (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) == 0) {
return;
}
- int powerCycle = PowerCycle.POWER_CYCLE_SUSPEND;
+ int powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
try {
mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
powerCycle, /* arg2= */ 0);
if (DBG) {
- Slog.d(TAG, "Notified car watchdog daemon a power cycle(" + powerCycle + ")");
+ Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle);
}
} catch (RemoteException | RuntimeException e) {
- Slog.w(TAG, "Notifying system state change failed: " + e);
+ Slogf.w(TAG, "Notifying power cycle state change failed: %s", e);
}
}
};
+ private final CarDevicePolicySafetyChecker mCarDevicePolicySafetyChecker;
+
public CarServiceHelperService(Context context) {
this(context,
- new CarUserManagerHelper(context),
- /* initialUserSetter= */ null,
- UserManager.get(context),
new CarLaunchParamsModifier(context),
new CarWatchdogDaemonHelper(TAG),
- CarProperties.user_hal_enabled().orElse(false),
- CarProperties.user_hal_timeout().orElse(5_000)
+ null
);
}
@VisibleForTesting
CarServiceHelperService(
Context context,
- CarUserManagerHelper userManagerHelper,
- InitialUserSetter initialUserSetter,
- UserManager userManager,
CarLaunchParamsModifier carLaunchParamsModifier,
CarWatchdogDaemonHelper carWatchdogDaemonHelper,
- boolean halEnabled,
- int halTimeoutMs) {
+ CarServiceProxy carServiceOperationManager) {
super(context);
+
mContext = context;
- mCarUserManagerHelper = userManagerHelper;
- mUserManager = userManager;
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
mCarLaunchParamsModifier = carLaunchParamsModifier;
mCarWatchdogDaemonHelper = carWatchdogDaemonHelper;
- boolean halValidUserHalSettings = false;
- if (halEnabled) {
- if (halTimeoutMs > 0) {
- Slog.i(TAG, "User HAL enabled with timeout of " + halTimeoutMs + "ms");
- halValidUserHalSettings = true;
- } else {
- Slog.w(TAG, "Not using User HAL due to invalid value on userHalTimeoutMs config: "
- + halTimeoutMs);
- }
- }
- if (halValidUserHalSettings) {
- mHalEnabled = true;
- mHalTimeoutMs = halTimeoutMs;
- } else {
- mHalEnabled = false;
- mHalTimeoutMs = -1;
- Slog.i(TAG, "Not using User HAL");
- }
- if (initialUserSetter == null) {
- // Called from main constructor, which cannot pass a lambda referencing itself
- mInitialUserSetter = new InitialUserSetter(context, (u) -> setInitialUser(u));
+ mCarServiceProxy =
+ carServiceOperationManager == null ? new CarServiceProxy(this)
+ : carServiceOperationManager;
+ UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ if (umi != null) {
+ umi.addUserLifecycleListener(new UserLifecycleListener() {
+ @Override
+ public void onUserCreated(UserInfo user, Object token) {
+ if (DBG) Slogf.d(TAG, "onUserCreated(): %s", user.toFullString());
+ }
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ if (DBG) Slogf.d(TAG, "onUserRemoved(): $s", user.toFullString());
+ mCarServiceProxy.onUserRemoved(user);
+ }
+ });
} else {
- mInitialUserSetter = initialUserSetter;
+ Slogf.e(TAG, "UserManagerInternal not available - should only happen on unit tests");
}
+ mCarDevicePolicySafetyChecker = new CarDevicePolicySafetyChecker(this);
}
-
@Override
public void onBootPhase(int phase) {
EventLog.writeEvent(EventLogTags.CAR_HELPER_BOOT_PHASE, phase);
- if (DBG) Slog.d(TAG, "onBootPhase:" + phase);
+ if (DBG) Slogf.d(TAG, "onBootPhase: %d", phase);
TimingsTraceAndSlog t = newTimingsTraceAndSlog();
if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
t.traceBegin("onBootPhase.3pApps");
mCarLaunchParamsModifier.init();
- checkForCarServiceConnection(t);
setupAndStartUsers(t);
- checkForCarServiceConnection(t);
t.traceEnd();
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
t.traceBegin("onBootPhase.completed");
- managePreCreatedUsers();
synchronized (mLock) {
mSystemBootCompleted = true;
}
@@ -310,7 +263,7 @@ public class CarServiceHelperService extends SystemService {
mCarWatchdogDaemonHelper.notifySystemStateChange(
StateType.BOOT_PHASE, phase, /* arg2= */ 0);
} catch (RemoteException | RuntimeException e) {
- Slog.w(TAG, "Failed to notify boot phase change: " + e);
+ Slogf.w(TAG, "Failed to notify boot phase change: %s", e);
}
t.traceEnd();
}
@@ -318,7 +271,7 @@ public class CarServiceHelperService extends SystemService {
@Override
public void onStart() {
- EventLog.writeEvent(EventLogTags.CAR_HELPER_START, mHalEnabled ? 1 : 0);
+ EventLog.writeEvent(EventLogTags.CAR_HELPER_START);
IntentFilter filter = new IntentFilter(Intent.ACTION_REBOOT);
filter.addAction(Intent.ACTION_SHUTDOWN);
@@ -327,19 +280,62 @@ public class CarServiceHelperService extends SystemService {
mCarWatchdogDaemonHelper.connect();
Intent intent = new Intent();
intent.setPackage("com.android.car");
- intent.setAction(ICarConstants.CAR_SERVICE_INTERFACE);
+ intent.setAction(CAR_SERVICE_INTERFACE);
if (!mContext.bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,
- UserHandle.SYSTEM)) {
- Slog.wtf(TAG, "cannot start car service");
+ mHandler, UserHandle.SYSTEM)) {
+ Slogf.wtf(TAG, "cannot start car service");
}
loadNativeLibrary();
}
@Override
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ if (args == null || args.length == 0 || args[0].equals("-a")) {
+ pw.printf("System boot completed: %b\n", mSystemBootCompleted);
+ pw.print("First unlocked user duration: ");
+ TimeUtils.formatDuration(mFirstUnlockedUserDuration, pw); pw.println();
+ pw.printf("Queued tasks: %d\n", mProcessTerminator.mQueuedTask);
+ mCarServiceProxy.dump(pw);
+ mCarDevicePolicySafetyChecker.dump(pw);
+ return;
+ }
+
+ if ("--user-metrics-only".equals(args[0])) {
+ mCarServiceProxy.dumpUserMetrics(pw);
+ return;
+ }
+
+ if ("--is-operation-safe".equals(args[0]) & args.length > 1) {
+ String arg1 = args[1];
+ int operation = 0;
+ try {
+ operation = Integer.parseInt(arg1);
+ } catch (Exception e) {
+ pw.printf("Invalid operation type: %s\n", arg1);
+ return;
+
+ }
+ int reason = getUnsafeOperationReason(operation);
+ boolean safe = reason == DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
+ pw.printf("Operation %s is %s. Reason: %s\n",
+ DevicePolicyManager.operationToString(operation),
+ safe ? "SAFE" : "UNSAFE",
+ DevicePolicyManager.operationSafetyReasonToString(reason));
+ return;
+ }
+ pw.printf("Invalid args: %s\n", Arrays.toString(args));
+ }
+
+ @Override
+ public String getDumpableName() {
+ return "CarServiceHelper";
+ }
+
+ @Override
public void onUserUnlocking(@NonNull TargetUser user) {
if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)) return;
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_UNLOCKING, user.getUserIdentifier());
- if (DBG) Slog.d(TAG, "onUserUnlocking(" + user + ")");
+ if (DBG) Slogf.d(TAG, "onUserUnlocking(%s)", user);
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, user);
}
@@ -349,25 +345,13 @@ public class CarServiceHelperService extends SystemService {
if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)) return;
int userId = user.getUserIdentifier();
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_UNLOCKED, userId);
- if (DBG) Slog.d(TAG, "onUserUnlocked(" + user + ")");
+ if (DBG) Slogf.d(TAG, "onUserUnlocked(%s)", user);
- if (mFirstUnlockedUserDuration == 0 && !UserHelper.isHeadlessSystemUser(userId)) {
+ if (mFirstUnlockedUserDuration == 0 && !UserHelperLite.isHeadlessSystemUser(userId)) {
mFirstUnlockedUserDuration = SystemClock.elapsedRealtime()
- Process.getStartElapsedRealtime();
- Slog.i(TAG, "Time to unlock 1st user(" + user + "): "
- + TimeUtils.formatDuration(mFirstUnlockedUserDuration));
- boolean operationQueued = false;
- synchronized (mLock) {
- mLastUserLifecycle.put(userId, USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
- if (mCarService == null) {
- operationQueued = true;
- if (DBG) Slog.d(TAG, "Queuing first user unlock for user " + user);
- queueOperationLocked(() -> sendFirstUserUnlocked(user));
- }
- }
- if (!operationQueued) {
- sendFirstUserUnlocked(user);
- }
+ Slogf.i(TAG, "Time to unlock 1st user(%s): %s", user,
+ TimeUtils.formatDuration(mFirstUnlockedUserDuration));
}
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, user);
}
@@ -376,7 +360,7 @@ public class CarServiceHelperService extends SystemService {
public void onUserStarting(@NonNull TargetUser user) {
if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STARTING)) return;
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STARTING, user.getUserIdentifier());
- if (DBG) Slog.d(TAG, "onUserStarting(" + user + ")");
+ if (DBG) Slogf.d(TAG, "onUserStarting(%s)", user);
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, user);
}
@@ -385,7 +369,7 @@ public class CarServiceHelperService extends SystemService {
public void onUserStopping(@NonNull TargetUser user) {
if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STOPPING)) return;
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STOPPING, user.getUserIdentifier());
- if (DBG) Slog.d(TAG, "onUserStopping(" + user + ")");
+ if (DBG) Slogf.d(TAG, "onUserStopping(%s)", user);
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, user);
int userId = user.getUserIdentifier();
@@ -396,7 +380,7 @@ public class CarServiceHelperService extends SystemService {
public void onUserStopped(@NonNull TargetUser user) {
if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STOPPED)) return;
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STOPPED, user.getUserIdentifier());
- if (DBG) Slog.d(TAG, "onUserStopped(" + user + ")");
+ if (DBG) Slogf.d(TAG, "onUserStopped(%s)", user);
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, user);
}
@@ -407,168 +391,65 @@ public class CarServiceHelperService extends SystemService {
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_SWITCHING,
from == null ? UserHandle.USER_NULL : from.getUserIdentifier(),
to.getUserIdentifier());
- if (DBG) Slog.d(TAG, "onUserSwitching(" + from + ">>" + to + ")");
+ if (DBG) Slogf.d(TAG, "onUserSwitching(%s>>%s)", from, to);
- sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, from, to);
+ mCarServiceProxy.sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ from, to);
int userId = to.getUserIdentifier();
mCarLaunchParamsModifier.handleCurrentUserSwitching(userId);
}
- @VisibleForTesting
- void loadNativeLibrary() {
- System.loadLibrary("car-framework-service-jni");
+ @Override // from DevicePolicySafetyChecker
+ @OperationSafetyReason
+ public int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
+ return mCarDevicePolicySafetyChecker.isDevicePolicyOperationSafe(operation)
+ ? DevicePolicyManager.OPERATION_SAFETY_REASON_NONE
+ : DevicePolicyManager.OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
}
- private boolean isPreCreated(@NonNull TargetUser user, int eventType) {
- UserInfo userInfo = user.getUserInfo();
- if (userInfo == null) {
- Slog.wtf(TAG, "no UserInfo on " + user + " on eventType " + eventType);
- return false;
- }
- if (!userInfo.preCreated) return false;
-
- if (DBG) {
- Slog.d(TAG, "Ignoring event of type " + eventType + " for pre-created user "
- + userInfo.toFullString());
- }
- return true;
+ @Override // from DevicePolicySafetyChecker
+ public boolean isSafeOperation(@OperationSafetyReason int reason) {
+ return mCarDevicePolicySafetyChecker.isSafe();
}
- /**
- * Queues a binder operation so it's called when the service is connected.
- */
- private void queueOperationLocked(@NonNull Runnable operation) {
- if (mPendingOperations == null) {
- mPendingOperations = new ArrayList<>(NUMBER_PENDING_OPERATIONS);
- }
- mPendingOperations.add(operation);
- }
+ @Override // from DevicePolicySafetyChecker
+ public void onFactoryReset(IResultReceiver callback) {
+ if (DBG) Slogf.d(TAG, "onFactoryReset: %s", callback);
- // Sometimes car service onConnected call is delayed a lot. car service binder can be
- // found from ServiceManager directly. So do some polling during boot-up to connect to
- // car service ASAP.
- private void checkForCarServiceConnection(@NonNull TimingsTraceAndSlog t) {
- synchronized (mLock) {
- if (mCarService != null) {
- return;
- }
- }
- t.traceBegin("checkForCarServiceConnection");
- IBinder iBinder = ServiceManager.checkService("car_service");
- if (iBinder != null) {
- if (DBG) {
- Slog.d(TAG, "Car service found through ServiceManager:" + iBinder);
- }
- handleCarServiceConnection(iBinder);
- }
- t.traceEnd();
+ mCarServiceProxy.onFactoryReset(callback);
}
@VisibleForTesting
- int getHalResponseTime() {
- return mHalResponseTime;
+ void loadNativeLibrary() {
+ System.loadLibrary("car-framework-service-jni");
}
- @VisibleForTesting
- void setInitialHalResponseTime() {
- mHalResponseTime = -((int) SystemClock.uptimeMillis());
- }
+ private boolean isPreCreated(@NonNull TargetUser user, @UserLifecycleEventType int eventType) {
+ if (!user.isPreCreated()) return false;
- @VisibleForTesting
- void setFinalHalResponseTime() {
- mHalResponseTime += (int) SystemClock.uptimeMillis();
+ if (DBG) {
+ Slogf.d(TAG, "Ignoring event of type %d for pre-created user %s", eventType, user);
+ }
+ return true;
}
@VisibleForTesting
void handleCarServiceConnection(IBinder iBinder) {
- boolean carServiceHasCrashed;
- int lastSwitchedUser;
- ArrayList<Runnable> pendingOperations;
- SparseIntArray lastUserLifecycle = null;
synchronized (mLock) {
- if (mCarService == iBinder) {
+ if (mCarServiceBinder == iBinder) {
return; // already connected.
}
- Slog.i(TAG, "car service binder changed, was:" + mCarService + " new:" + iBinder);
- mCarService = iBinder;
- carServiceHasCrashed = mCarServiceHasCrashed;
- mCarServiceHasCrashed = false;
- lastSwitchedUser = mLastSwitchedUser;
- pendingOperations = mPendingOperations;
- mPendingOperations = null;
- if (carServiceHasCrashed) {
- lastUserLifecycle = mLastUserLifecycle.clone();
- }
- }
- int numberOperations = pendingOperations == null ? 0 : pendingOperations.size();
- EventLog.writeEvent(EventLogTags.CAR_HELPER_SVC_CONNECTED, numberOperations);
-
- Slog.i(TAG, "**CarService connected**");
-
- sendSetCarServiceHelperBinderCall();
- if (carServiceHasCrashed) {
- int numUsers = lastUserLifecycle.size();
- TimingsTraceAndSlog t = newTimingsTraceAndSlog();
- t.traceBegin("send-uses-after-reconnect-" + numUsers);
- // Send user0 events first
- int user0Lifecycle = lastUserLifecycle.get(UserHandle.USER_SYSTEM,
- USER_LIFECYCLE_EVENT_TYPE_STARTING);
- lastUserLifecycle.delete(UserHandle.USER_SYSTEM);
- boolean user0IsCurrent = lastSwitchedUser == UserHandle.USER_SYSTEM;
- sendAllLifecyleToUser(UserHandle.USER_SYSTEM, user0Lifecycle, user0IsCurrent);
- // Send current user events next
- if (!user0IsCurrent) {
- int currentUserLifecycle = lastUserLifecycle.get(lastSwitchedUser,
- USER_LIFECYCLE_EVENT_TYPE_STARTING);
- lastUserLifecycle.delete(lastSwitchedUser);
- sendAllLifecyleToUser(lastSwitchedUser, currentUserLifecycle,
- /* isCurrentUser= */ true);
- }
- // 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);
- }
- t.traceEnd();
- } else if (pendingOperations != null) {
- if (DBG) Slog.d(TAG, "Running " + numberOperations + " pending operations");
- TimingsTraceAndSlog t = newTimingsTraceAndSlog();
- t.traceBegin("send-pending-ops-" + numberOperations);
- for (int i = 0; i < numberOperations; i++) {
- Runnable operation = pendingOperations.get(i);
- try {
- operation.run();
- } catch (RuntimeException e) {
- Slog.w(TAG, "exception running operation #" + i + ": " + e);
- }
- }
- t.traceEnd();
+ Slogf.i(TAG, "car service binder changed, was %s new: %s", mCarServiceBinder, iBinder);
+ mCarServiceBinder = iBinder;
+ Slogf.i(TAG, "**CarService connected**");
}
- }
- 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, LIFECYCLE_TIMESTAMP_IGNORE,
- UserHandle.USER_NULL, userId);
- }
- if (isCurrentUser && userId != UserHandle.USER_SYSTEM) {
- // Do not care about actual previous user.
- sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, LIFECYCLE_TIMESTAMP_IGNORE,
- UserHandle.USER_SYSTEM, userId);
- }
- if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKING) {
- sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, LIFECYCLE_TIMESTAMP_IGNORE,
- UserHandle.USER_NULL, userId);
- }
- if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) {
- sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, LIFECYCLE_TIMESTAMP_IGNORE,
- UserHandle.USER_NULL, userId);
- }
+ sendSetSystemServerConnectionsCall();
+
+ mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE);
+ mHandler.sendMessageDelayed(
+ obtainMessage(CarServiceHelperService::handleCarServiceUnresponsive, this)
+ .setWhat(WHAT_SERVICE_UNRESPONSIVE), CAR_SERVICE_BINDER_CALL_TIMEOUT);
}
private TimingsTraceAndSlog newTimingsTraceAndSlog() {
@@ -576,528 +457,52 @@ public class CarServiceHelperService extends SystemService {
}
private void setupAndStartUsers(@NonNull TimingsTraceAndSlog t) {
- DevicePolicyManager devicePolicyManager =
- mContext.getSystemService(DevicePolicyManager.class);
- if (devicePolicyManager != null && devicePolicyManager.getUserProvisioningState()
- != DevicePolicyManager.STATE_USER_UNMANAGED) {
- Slog.i(TAG, "DevicePolicyManager active, skip user unlock/switch");
- return;
- }
+ // TODO(b/156263735): decide if it should return in case the device's on Retail Mode
t.traceBegin("setupAndStartUsers");
- if (mHalEnabled) {
- Slog.i(TAG, "Delegating initial switching to HAL");
- setupAndStartUsersUsingHal();
- } else {
- setupAndStartUsersDirectly(t, /* userLocales= */ null);
- }
- t.traceEnd();
- }
-
- private void handleHalTimedout() {
- synchronized (mLock) {
- if (mInitialized) return;
- }
-
- Slog.w(TAG, "HAL didn't respond in " + mHalTimeoutMs + "ms; using default behavior");
- setupAndStartUsersDirectly();
- }
-
- private void setupAndStartUsersUsingHal() {
- mHandler.sendMessageDelayed(obtainMessage(CarServiceHelperService::handleHalTimedout, this)
- .setWhat(WHAT_HAL_TIMEOUT), mHalTimeoutMs);
-
- // TODO(b/150413515): get rid of receiver once returned?
- IResultReceiver receiver = new IResultReceiver.Stub() {
- @Override
- public void send(int resultCode, Bundle resultData) {
- EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_RESPONSE, resultCode);
-
- setFinalHalResponseTime();
- if (DBG) {
- Slog.d(TAG, "Got result from HAL (" +
- UserHalHelper.halCallbackStatusToString(resultCode) + ") in "
- + TimeUtils.formatDuration(mHalResponseTime));
- }
-
- mHandler.removeMessages(WHAT_HAL_TIMEOUT);
- // TODO(b/150222501): log how long it took to receive the response
- // TODO(b/150413515): print resultData as well on 2 logging calls below
- synchronized (mLock) {
- if (mInitialized) {
- Slog.w(TAG, "Result from HAL came too late, ignoring: "
- + UserHalHelper.halCallbackStatusToString(resultCode));
- return;
- }
- }
-
- if (resultCode != HalCallback.STATUS_OK) {
- Slog.w(TAG, "Service returned non-ok status ("
- + UserHalHelper.halCallbackStatusToString(resultCode)
- + "); using default behavior");
- fallbackToDefaultInitialUserBehavior();
- return;
- }
-
- if (resultData == null) {
- Slog.w(TAG, "Service returned null bundle");
- fallbackToDefaultInitialUserBehavior();
- return;
- }
-
- int action = resultData.getInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
- InitialUserInfoResponseAction.DEFAULT);
-
- String userLocales = resultData
- .getString(CarUserServiceConstants.BUNDLE_USER_LOCALES);
- if (userLocales != null) {
- Slog.i(TAG, "Changing user locales to " + userLocales);
- }
-
- switch (action) {
- case InitialUserInfoResponseAction.DEFAULT:
- EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_DEFAULT_BEHAVIOR,
- /* fallback= */ 0, userLocales);
- if (DBG) Slog.d(TAG, "User HAL returned DEFAULT behavior");
- setupAndStartUsersDirectly(newTimingsTraceAndSlog(), userLocales);
- return;
- case InitialUserInfoResponseAction.SWITCH:
- int userId = resultData.getInt(CarUserServiceConstants.BUNDLE_USER_ID);
- startUserByHalRequest(userId, userLocales);
- return;
- case InitialUserInfoResponseAction.CREATE:
- String name = resultData
- .getString(CarUserServiceConstants.BUNDLE_USER_NAME);
- int flags = resultData.getInt(CarUserServiceConstants.BUNDLE_USER_FLAGS);
- createUserByHalRequest(name, userLocales, flags);
- return;
- default:
- Slog.w(TAG, "Invalid InitialUserInfoResponseAction action: " + action);
- }
- fallbackToDefaultInitialUserBehavior();
- }
- };
- int initialUserInfoRequestType = getInitialUserInfoRequestType();
- EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_REQUEST, initialUserInfoRequestType);
-
- setInitialHalResponseTime();
- sendOrQueueGetInitialUserInfo(initialUserInfoRequestType, receiver);
- }
-
- @VisibleForTesting
- int getInitialUserInfoRequestType() {
- if (!mCarUserManagerHelper.hasInitialUser()) {
- return InitialUserInfoRequestType.FIRST_BOOT;
- }
- if (mContext.getPackageManager().isDeviceUpgrading()) {
- return InitialUserInfoRequestType.FIRST_BOOT_AFTER_OTA;
- }
- return InitialUserInfoRequestType.COLD_BOOT;
- }
-
- private void startUserByHalRequest(@UserIdInt int userId, @Nullable String userLocales) {
- if (userId <= 0) {
- Slog.w(TAG, "invalid (or missing) user id sent by HAL: " + userId);
- fallbackToDefaultInitialUserBehavior();
- return;
- }
-
- EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_START_USER, userId, userLocales);
- if (DBG) Slog.d(TAG, "Starting user " + userId + " as requested by HAL");
-
- // It doesn't need to replace guest, as the switch would fail anyways if the requested user
- // was a guest because it wouldn't exist.
- mInitialUserSetter.set(newInitialUserInfoBuilder(InitialUserSetter.TYPE_SWITCH)
- .setUserLocales(userLocales)
- .setSwitchUserId(userId).build());
- }
-
- private InitialUserSetter.Builder newInitialUserInfoBuilder(@InitialUserInfoType int type) {
- return new InitialUserSetter.Builder(type)
- .setSupportsOverrideUserIdProperty(!CarProperties.user_hal_enabled().orElse(false));
- }
-
- private void createUserByHalRequest(@Nullable String name, @Nullable String userLocales,
- int halFlags) {
- String friendlyName = "user with name '" + safeName(name) + "', locales " + userLocales
- + ", and flags " + UserHalHelper.userFlagsToString(halFlags);
- EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_CREATE_USER, halFlags, safeName(name),
- userLocales);
- if (DBG) Slog.d(TAG, "HAL request creation of " + friendlyName);
-
- mInitialUserSetter.set(newInitialUserInfoBuilder(InitialUserSetter.TYPE_CREATE)
- .setUserLocales(userLocales)
- .setNewUserName(name)
- .setNewUserFlags(halFlags).build());
-
- }
-
- private void fallbackToDefaultInitialUserBehavior() {
- EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_DEFAULT_BEHAVIOR, /* fallback= */ 1);
- if (DBG) Slog.d(TAG, "Falling back to DEFAULT initial user behavior");
- setupAndStartUsersDirectly();
- }
-
- private void setupAndStartUsersDirectly() {
- setupAndStartUsersDirectly(newTimingsTraceAndSlog(), /* userLocales= */ null);
- }
-
- private void setupAndStartUsersDirectly(@NonNull TimingsTraceAndSlog t,
- @Nullable String userLocales) {
- synchronized (mLock) {
- if (mInitialized) {
- Slog.wtf(TAG, "Already initialized", new Exception());
- return;
- }
- mInitialized = true;
- }
-
- mInitialUserSetter.set(newInitialUserInfoBuilder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
- .setUserLocales(userLocales)
- .build());
- }
-
- @VisibleForTesting
- void managePreCreatedUsers() {
- // First gets how many pre-createad users are defined by the OEM
- int numberRequestedGuests = CarProperties.number_pre_created_guests().orElse(0);
- int numberRequestedUsers = CarProperties.number_pre_created_users().orElse(0);
- EventLog.writeEvent(EventLogTags.CAR_HELPER_PRE_CREATION_REQUESTED, numberRequestedUsers,
- numberRequestedGuests);
- if (DBG) {
- Slog.d(TAG, "managePreCreatedUsers(): OEM asked for " + numberRequestedGuests
- + " guests and " + numberRequestedUsers + " users");
- }
-
- if (numberRequestedGuests < 0 || numberRequestedUsers < 0) {
- Slog.w(TAG, "preCreateUsers(): invalid values provided by OEM; "
- + "number_pre_created_guests=" + numberRequestedGuests
- + ", number_pre_created_users=" + numberRequestedUsers);
- return;
- }
-
- // Then checks how many exist already
- List<UserInfo> allUsers = mUserManager.getUsers(/* excludePartial= */ true,
- /* excludeDying= */ true, /* excludePreCreated= */ false);
-
- int allUsersSize = allUsers.size();
- if (DBG) Slog.d(TAG, "preCreateUsers: total users size is " + allUsersSize);
-
- int numberExistingGuests = 0;
- int numberExistingUsers = 0;
-
- // List of pre-created users that were not properly initialized. Typically happens when
- // the system crashed / rebooted before they were fully started.
- SparseBooleanArray invalidPreCreatedUsers = new SparseBooleanArray();
-
- // List of all pre-created users - it will be used to remove unused ones (when needed)
- SparseBooleanArray existingPrecreatedUsers = new SparseBooleanArray();
-
- // List of extra pre-created users and guests - they will be removed
- List<Integer> extraPreCreatedUsers = new ArrayList<>();
-
- for (int i = 0; i < allUsersSize; i++) {
- UserInfo user = allUsers.get(i);
- if (!user.preCreated) continue;
- if (!user.isInitialized()) {
- Slog.w(TAG, "Found invalid pre-created user that needs to be removed: "
- + user.toFullString());
- invalidPreCreatedUsers.append(user.id, /* notUsed=*/ true);
- continue;
- }
- boolean isGuest = user.isGuest();
- existingPrecreatedUsers.put(user.id, isGuest);
- if (isGuest) {
- numberExistingGuests++;
- if (numberExistingGuests > numberRequestedGuests) {
- extraPreCreatedUsers.add(user.id);
- }
- } else {
- numberExistingUsers++;
- if (numberExistingUsers > numberRequestedUsers) {
- extraPreCreatedUsers.add(user.id);
- }
- }
- }
- if (DBG) {
- Slog.d(TAG, "managePreCreatedUsers(): system already has " + numberExistingGuests
- + " pre-created guests," + numberExistingUsers + " pre-created users, and these"
- + " invalid users: " + invalidPreCreatedUsers
- + " extra pre-created users: " + extraPreCreatedUsers);
- }
-
- int numberGuestsToAdd = numberRequestedGuests - numberExistingGuests;
- int numberUsersToAdd = numberRequestedUsers - numberExistingUsers;
- int numberGuestsToRemove = numberExistingGuests - numberRequestedGuests;
- int numberUsersToRemove = numberExistingUsers - numberRequestedUsers;
- int numberInvalidUsersToRemove = invalidPreCreatedUsers.size();
-
- EventLog.writeEvent(EventLogTags.CAR_HELPER_PRE_CREATION_STATUS,
- numberExistingUsers, numberUsersToAdd, numberUsersToRemove,
- numberExistingGuests, numberGuestsToAdd, numberGuestsToRemove,
- numberInvalidUsersToRemove);
-
- if (numberGuestsToAdd == 0 && numberUsersToAdd == 0 && numberInvalidUsersToRemove == 0) {
- if (DBG) Slog.d(TAG, "managePreCreatedUsers(): everything in sync");
- return;
- }
-
- // Finally, manage them....
-
- // In theory, we could submit multiple user pre-creations in parallel, but we're
- // submitting just 1 task, for 2 reasons:
- // 1.To minimize it's effect on other system server initialization tasks.
- // 2.The pre-created users will be unlocked in parallel anyways.
- runAsync(() -> {
- TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Async",
- Trace.TRACE_TAG_SYSTEM_SERVER);
-
- t.traceBegin("preCreateUsers");
- if (numberUsersToAdd > 0) {
- preCreateUsers(t, numberUsersToAdd, /* isGuest= */ false);
- }
- if (numberGuestsToAdd > 0) {
- preCreateUsers(t, numberGuestsToAdd, /* isGuest= */ true);
- }
-
- int totalNumberToRemove = extraPreCreatedUsers.size();
- if (DBG) Slog.d(TAG, "Must delete " + totalNumberToRemove + " pre-created users");
- if (totalNumberToRemove > 0) {
- int[] usersToRemove = new int[totalNumberToRemove];
- for (int i = 0; i < totalNumberToRemove; i++) {
- usersToRemove[i] = extraPreCreatedUsers.get(i);
- }
- removePreCreatedUsers(usersToRemove);
- }
-
- t.traceEnd();
-
- if (numberInvalidUsersToRemove > 0) {
- t.traceBegin("removeInvalidPreCreatedUsers");
- for (int i = 0; i < numberInvalidUsersToRemove; i++) {
- int userId = invalidPreCreatedUsers.keyAt(i);
- Slog.i(TAG, "removing invalid pre-created user " + userId);
- mUserManager.removeUser(userId);
- }
- t.traceEnd();
- }
- });
- }
-
- private void preCreateUsers(@NonNull TimingsTraceAndSlog t, int size, boolean isGuest) {
- String msg = isGuest ? "preCreateGuests-" + size : "preCreateUsers-" + size;
- if (DBG) Slog.d(TAG, "preCreateUsers: " + msg);
- t.traceBegin(msg);
- for (int i = 1; i <= size; i++) {
- UserInfo preCreated = preCreateUsers(t, isGuest);
- if (preCreated == null) {
- Slog.w(TAG, "Could not pre-create" + (isGuest ? " guest" : "")
- + " user #" + i);
- continue;
- }
- }
+ mCarServiceProxy.initBootUser();
t.traceEnd();
}
- @VisibleForTesting
- void runAsync(Runnable r) {
- // We cannot use SystemServerInitThreadPool because user pre-creation can take too long,
- // which would crash the SystemServer on SystemServerInitThreadPool.shutdown();
- String threadName = TAG + ".AsyncTask";
- Slog.i(TAG, "Starting thread " + threadName);
- new Thread(() -> {
- try {
- r.run();
- Slog.i(TAG, "Finishing thread " + threadName);
- } catch (RuntimeException e) {
- Slog.e(TAG, "runAsync() failed", e);
- throw e;
- }
- }, threadName).start();
- }
-
- @Nullable
- public UserInfo preCreateUsers(@NonNull TimingsTraceAndSlog t, boolean isGuest) {
- String traceMsg = "pre-create" + (isGuest ? "-guest" : "-user");
- t.traceBegin(traceMsg);
- // NOTE: we want to get rid of UserManagerHelper, so let's call UserManager directly
- String userType =
- isGuest ? UserManager.USER_TYPE_FULL_GUEST : UserManager.USER_TYPE_FULL_SECONDARY;
- UserInfo user = null;
- try {
- user = mUserManager.preCreateUser(userType);
- if (user == null) {
- logPrecreationFailure(traceMsg, /* cause= */ null);
- }
- } catch (Exception e) {
- logPrecreationFailure(traceMsg, e);
- } finally {
- t.traceEnd();
- }
- return user;
- }
-
- private void removePreCreatedUsers(int[] usersToRemove) {
- for (int userId : usersToRemove) {
- Slog.i(TAG, "removing pre-created user with id " + userId);
- mUserManager.removeUser(userId);
- }
+ private void handleCarServiceUnresponsive() {
+ // This should not happen. Calling this method means ICarSystemServerClient binder is not
+ // returned after service connection. and CarService has not connected in the given time.
+ Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService unresponsive.");
+ Slogf.w(TAG, "*** GOODBYE!");
+ Process.killProcess(Process.myPid());
+ System.exit(10);
}
- /**
- * Logs proper message when user pre-creation fails (most likely because there are too many).
- */
- @VisibleForTesting
- void logPrecreationFailure(@NonNull String operation, @Nullable Exception cause) {
- int maxNumberUsers = UserManager.getMaxSupportedUsers();
- int currentNumberUsers = mUserManager.getUserCount();
- StringBuilder message = new StringBuilder(operation.length() + 100)
- .append(operation).append(" failed. Number users: ").append(currentNumberUsers)
- .append(" Max: ").append(maxNumberUsers);
- if (cause == null) {
- Slog.w(TAG, message.toString());
- } else {
- Slog.w(TAG, message.toString(), cause);
- }
- }
-
- private void sendSetCarServiceHelperBinderCall() {
+ private void sendSetSystemServerConnectionsCall() {
Parcel data = Parcel.obtain();
- data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE);
+ data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
data.writeStrongBinder(mHelper.asBinder());
- // void setCarServiceHelper(in IBinder helper)
- sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_SET_CAR_SERVICE_HELPER);
- }
-
- private void sendUserLifecycleEvent(int eventType, @NonNull TargetUser user) {
- sendUserLifecycleEvent(eventType, /* from= */ null, user);
- }
-
- private void sendUserLifecycleEvent(int eventType, @Nullable TargetUser from,
- @NonNull TargetUser to) {
- long now = System.currentTimeMillis();
- synchronized (mLock) {
- if (eventType == USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
- mLastSwitchedUser = to.getUserIdentifier();
- } 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, "Queuing lifecycle event " + eventType + " for user " + to);
- queueOperationLocked(() -> sendUserLifecycleEvent(eventType, now, from, to));
- return;
- }
- }
- TimingsTraceAndSlog t = newTimingsTraceAndSlog();
- t.traceBegin("send-lifecycle-" + eventType + "-" + to.getUserIdentifier());
- sendUserLifecycleEvent(eventType, now, from, to);
- t.traceEnd();
- }
-
- private void sendUserLifecycleEvent(int eventType, long timestamp, @Nullable TargetUser from,
- @NonNull TargetUser to) {
- int fromId = from == null ? UserHandle.USER_NULL : from.getUserIdentifier();
- int toId = to.getUserIdentifier();
- sendUserLifecycleEvent(eventType, timestamp, fromId, toId);
- }
-
- private void sendUserLifecycleEvent(int eventType, long timestamp, @UserIdInt int fromId,
- @UserIdInt int toId) {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE);
- data.writeInt(eventType);
- data.writeLong(timestamp);
- data.writeInt(fromId);
- data.writeInt(toId);
- // void onUserLifecycleEvent(int eventType, long timestamp, int from, int to)
- sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE);
- }
-
- private void sendOrQueueGetInitialUserInfo(int requestType, @NonNull IResultReceiver receiver) {
- synchronized (mLock) {
- if (mCarService == null) {
- if (DBG) Slog.d(TAG, "Queuing GetInitialUserInfo call for type " + requestType);
- queueOperationLocked(() -> sendGetInitialUserInfo(requestType, receiver));
- return;
- }
- }
- sendGetInitialUserInfo(requestType, receiver);
- }
-
- private void sendGetInitialUserInfo(int requestType, @NonNull IResultReceiver receiver) {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE);
- data.writeInt(requestType);
- data.writeInt(mHalTimeoutMs);
- data.writeStrongBinder(receiver.asBinder());
- // void getInitialUserInfo(int requestType, int timeoutMs, in IResultReceiver receiver)
- sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_GET_INITIAL_USER_INFO);
- }
-
- @VisibleForTesting
- void setInitialUser(@Nullable UserInfo user) {
- synchronized (mLock) {
- if (mCarService == null) {
- if (DBG) Slog.d(TAG, "Queuing setInitialUser() call");
- queueOperationLocked(() -> sendSetInitialUser(user));
- return;
- }
- }
- sendSetInitialUser(user);
- }
-
- private void sendSetInitialUser(@Nullable UserInfo user) {
- if (DBG) Slog.d(TAG, "sendSetInitialUser(): " + user);
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE);
- data.writeInt(user != null ? user.id : UserHandle.USER_NULL);
- // void setInitialUser(int userId)
- sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_SET_INITIAL_USER);
- }
-
- private void sendFirstUserUnlocked(@NonNull TargetUser user) {
- long now = System.currentTimeMillis();
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE);
- data.writeInt(user.getUserIdentifier());
- data.writeLong(now);
- data.writeLong(mFirstUnlockedUserDuration);
- data.writeInt(mHalResponseTime);
- // void onFirstUserUnlocked(int userId, long timestamp, long duration, int halResponseTime)
- sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_FIRST_USER_UNLOCKED);
- }
-
- private void sendBinderCallToCarService(Parcel data, int callNumber) {
- // Cannot depend on ICar which is defined in CarService, so handle binder call directly
- // instead.
- IBinder carService;
+ data.writeStrongBinder(mCarServiceConnectedCallback.asBinder());
+ IBinder binder;
synchronized (mLock) {
- carService = mCarService;
+ binder = mCarServiceBinder;
}
- if (carService == null) {
- Slog.w(TAG, "Not calling txn " + callNumber + " because service is not bound yet",
- new Exception());
- return;
- }
- int code = IBinder.FIRST_CALL_TRANSACTION + callNumber;
+ int code = IBinder.FIRST_CALL_TRANSACTION;
try {
- if (VERBOSE) Slog.v(TAG, "calling one-way binder transaction with code " + code);
- carService.transact(code, data, null, Binder.FLAG_ONEWAY);
- if (VERBOSE) Slog.v(TAG, "finished one-way binder transaction with code " + code);
+ if (VERBOSE) Slogf.v(TAG, "calling one-way binder transaction with code %d", code);
+ // oneway void setSystemServerConnections(in IBinder helper, in IBinder receiver) = 0;
+ binder.transact(code, data, null, Binder.FLAG_ONEWAY);
+ if (VERBOSE) Slogf.v(TAG, "finished one-way binder transaction with code %d", code);
} catch (RemoteException e) {
- Slog.w(TAG, "RemoteException from car service", e);
+ Slogf.w(TAG, "RemoteException from car service", e);
handleCarServiceCrash();
} catch (RuntimeException e) {
- Slog.wtf(TAG, "Exception calling binder transaction " + callNumber + " (real code: "
- + code + ")", e);
+ Slogf.wtf(TAG, e, "Exception calling binder transaction (real code: %d)", code);
throw e;
} finally {
data.recycle();
}
}
+ private void sendUserLifecycleEvent(@UserLifecycleEventType int eventType,
+ @NonNull TargetUser user) {
+ mCarServiceProxy.sendUserLifecycleEvent(eventType, /* from= */ null, user);
+ }
+
// Adapted from frameworks/base/services/core/java/com/android/server/Watchdog.java
// TODO(b/131861630) use implementation common with Watchdog.java
//
@@ -1157,17 +562,16 @@ public class CarServiceHelperService extends SystemService {
// everything if enabled by the property.
boolean restartOnServiceCrash = SystemProperties.getBoolean(PROP_RESTART_RUNTIME, false);
+ mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE);
+
dumpServiceStacks();
if (restartOnServiceCrash) {
- Slog.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: " + "CarService crash");
- Slog.w(TAG, "*** GOODBYE!");
+ Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService crash");
+ Slogf.w(TAG, "*** GOODBYE!");
Process.killProcess(Process.myPid());
System.exit(10);
} else {
- Slog.w(TAG, "*** CARHELPER ignoring: " + "CarService crash");
- }
- synchronized (mLock) {
- mCarServiceHasCrashed = true;
+ Slogf.w(TAG, "*** CARHELPER ignoring: CarService crash");
}
}
@@ -1179,18 +583,18 @@ public class CarServiceHelperService extends SystemService {
try {
mCarWatchdogDaemonHelper.registerMonitor(mCarWatchdogMonitor);
} catch (RemoteException | RuntimeException e) {
- Slog.w(TAG, "Cannot register to car watchdog daemon: " + e);
+ Slogf.w(TAG, "Cannot register to car watchdog daemon: %s", e);
}
}
private void killProcessAndReportToMonitor(int pid) {
String processName = getProcessName(pid);
Process.killProcess(pid);
- Slog.w(TAG, "carwatchdog killed " + processName + " (pid: " + pid + ")");
+ Slogf.w(TAG, "carwatchdog killed %s (pid: %d)", processName, pid);
try {
mCarWatchdogDaemonHelper.tellDumpFinished(mCarWatchdogMonitor, pid);
} catch (RemoteException | RuntimeException e) {
- Slog.w(TAG, "Cannot report monitor result to car watchdog daemon: " + e);
+ Slogf.w(TAG, "Cannot report monitor result to car watchdog daemon: %s", e);
}
}
@@ -1205,13 +609,15 @@ public class CarServiceHelperService extends SystemService {
}
return Paths.get(line).getFileName().toString();
} catch (IOException e) {
- Slog.w(TAG, "Cannot read " + filename);
+ Slogf.w(TAG, "Cannot read %s", filename);
return unknownProcessName;
}
}
private static native int nativeForceSuspend(int timeoutMs);
+ // TODO(b/173664653): it's missing unit tests (for example, to make sure that
+ // when its setSafetyMode() is called, mCarDevicePolicySafetyChecker is updated).
private class ICarServiceHelperImpl extends ICarServiceHelper.Stub {
/**
* Force device to suspend
@@ -1230,8 +636,8 @@ public class CarServiceHelperService extends SystemService {
}
@Override
- public void setDisplayWhitelistForUser(@UserIdInt int userId, int[] displayIds) {
- mCarLaunchParamsModifier.setDisplayWhitelistForUser(userId, displayIds);
+ public void setDisplayAllowlistForUser(@UserIdInt int userId, int[] displayIds) {
+ mCarLaunchParamsModifier.setDisplayAllowListForUser(userId, displayIds);
}
@Override
@@ -1245,6 +651,32 @@ public class CarServiceHelperService extends SystemService {
mCarLaunchParamsModifier.setSourcePreferredComponents(
enableSourcePreferred, sourcePreferredComponents);
}
+
+ @Override
+ public void setSafetyMode(boolean safe) {
+ mCarDevicePolicySafetyChecker.setSafe(safe);
+ }
+
+ @Override
+ public UserInfo createUserEvenWhenDisallowed(String name, String userType, int flags) {
+ if (DBG) {
+ Slogf.d(TAG, "createUserEvenWhenDisallowed(): name=%s, type=%s, flags=%s",
+ UserHelperLite.safeName(name), userType, UserInfo.flagsToString(flags));
+ }
+ UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ try {
+ UserInfo user = umi.createUserEvenWhenDisallowed(name, userType, flags,
+ /* disallowedPackages= */ null, /* token= */ null);
+ if (DBG) {
+ Slogf.d(TAG, "User created: %s", (user == null ? "null" : user.toFullString()));
+ }
+ // TODO(b/172691310): decide if user should be affiliated when DeviceOwner is set
+ return user;
+ } catch (UserManager.CheckedUserOperationException e) {
+ Slogf.e(TAG, "Error creating user", e);
+ return null;
+ }
+ }
}
private class ICarWatchdogMonitorImpl extends ICarWatchdogMonitor.Stub {
@@ -1262,16 +694,6 @@ public class CarServiceHelperService extends SystemService {
}
service.handleClientsNotResponding(pids);
}
-
- @Override
- public int getInterfaceVersion() {
- return this.VERSION;
- }
-
- @Override
- public String getInterfaceHash() {
- return this.HASH;
- }
}
private final class ProcessTerminator {
@@ -1313,7 +735,7 @@ public class CarServiceHelperService extends SystemService {
private void dumpAndKillProcess(int pid) {
if (DBG) {
- Slog.d(TAG, "Dumping and killing process(pid: " + pid + ")");
+ Slogf.d(TAG, "Dumping and killing process(pid: %d)", pid);
}
ArrayList<Integer> javaPids = new ArrayList<>(1);
ArrayList<Integer> nativePids = new ArrayList<>();
@@ -1324,7 +746,7 @@ public class CarServiceHelperService extends SystemService {
nativePids.add(pid);
}
} catch (IOException e) {
- Slog.w(TAG, "Cannot get process information: " + e);
+ Slogf.w(TAG, "Cannot get process information: %s", e);
return;
}
nativePids.addAll(getInterestingNativePids());
@@ -1332,7 +754,7 @@ public class CarServiceHelperService extends SystemService {
ActivityManagerService.dumpStackTraces(javaPids, null, null, nativePids, null);
long dumpTime = SystemClock.uptimeMillis() - startDumpTime;
if (DBG) {
- Slog.d(TAG, "Dumping process took " + dumpTime + "ms");
+ Slogf.d(TAG, "Dumping process took %dms", dumpTime);
}
// To give clients a chance of wrapping up before the termination.
if (dumpTime < ONE_SECOND_MS) {
@@ -1353,4 +775,22 @@ public class CarServiceHelperService extends SystemService {
return target == "/system/bin/app_process32" || target == "/system/bin/app_process64";
}
}
+
+ private final class CarServiceConnectedCallback extends IResultReceiver.Stub {
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE);
+
+ IBinder binder;
+ if (resultData == null
+ || (binder = resultData.getBinder(ICAR_SYSTEM_SERVER_CLIENT)) == null) {
+ Slogf.wtf(TAG, "setSystemServerConnections return NULL Binder.");
+ handleCarServiceUnresponsive();
+ return;
+ }
+
+ ICarSystemServerClient carService = ICarSystemServerClient.Stub.asInterface(binder);
+ mCarServiceProxy.handleCarServiceConnection(carService);
+ }
+ }
}
diff --git a/src/com/android/internal/car/CarServiceProxy.java b/src/com/android/internal/car/CarServiceProxy.java
new file mode 100644
index 0000000..42c344b
--- /dev/null
+++ b/src/com/android/internal/car/CarServiceProxy.java
@@ -0,0 +1,512 @@
+/*
+ * 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);
+ }
+}
diff --git a/src/com/android/internal/car/ExternalConstants.java b/src/com/android/internal/car/ExternalConstants.java
deleted file mode 100644
index d9d6345..0000000
--- a/src/com/android/internal/car/ExternalConstants.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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;
-
-/**
- * Provides constants that are defined somewhere else and must be cloned here
- */
-final class ExternalConstants {
-
- private ExternalConstants() {
- throw new UnsupportedOperationException("contains only static constants");
- }
-
- // TODO(b/149797595): remove once ICar.aidl is split in 2
- static final class ICarConstants {
- static final String CAR_SERVICE_INTERFACE = "android.car.ICar";
-
- // These numbers should match with binder call order of
- // packages/services/Car/car-lib/src/android/car/ICar.aidl
- static final int ICAR_CALL_SET_CAR_SERVICE_HELPER = 0;
- static final int ICAR_CALL_ON_USER_LIFECYCLE = 1;
- static final int ICAR_CALL_FIRST_USER_UNLOCKED = 2;
- static final int ICAR_CALL_GET_INITIAL_USER_INFO = 3;
- static final int ICAR_CALL_SET_INITIAL_USER = 4;
-
- private ICarConstants() {
- throw new UnsupportedOperationException("contains only static constants");
- }
- }
-
- /**
- * Constants used by {@link android.user.user.CarUserManager} - they cannot be defined on
- * {@link android.car.userlib.CommonConstants} to avoid an extra dependency in the
- * {@code android.car} project
- */
- static final class CarUserManagerConstants {
-
- static final int USER_LIFECYCLE_EVENT_TYPE_STARTING = 1;
- static final int USER_LIFECYCLE_EVENT_TYPE_SWITCHING = 2;
- static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKING = 3;
- static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKED = 4;
- static final int USER_LIFECYCLE_EVENT_TYPE_STOPPING = 5;
- static final int USER_LIFECYCLE_EVENT_TYPE_STOPPED = 6;
-
- private CarUserManagerConstants() {
- throw new UnsupportedOperationException("contains only static constants");
- }
- }
-}
diff --git a/src/com/android/internal/car/ICarServiceHelper.aidl b/src/com/android/internal/car/ICarServiceHelper.aidl
deleted file mode 100644
index 8145229..0000000
--- a/src/com/android/internal/car/ICarServiceHelper.aidl
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.content.ComponentName;
-
-import java.util.List;
-
-/**
- * Helper API for car service. Only for interaction between system server and car service.
- * @hide
- */
-interface ICarServiceHelper {
- int forceSuspend(int timeoutMs);
- /**
- * Check
- * {@link com.android.server.wm.CarLaunchParamsModifier#setDisplayWhitelistForUser(int, int[]).
- */
- void setDisplayWhitelistForUser(in int userId, in int[] displayIds);
-
- /**
- * Check
- * {@link com.android.server.wm.CarLaunchParamsModifier#setPassengerDisplays(int[])}.
- */
- void setPassengerDisplays(in int[] displayIds);
-
- /**
- * Check
- * {@link com.android.server.wm.CarLaunchParamsModifier#setSourcePreferredComponents(
- * boolean, List<ComponentName>)}.
- */
- void setSourcePreferredComponents(
- in boolean enableSourcePreferred, in List<ComponentName> sourcePreferredComponents);
-}
diff --git a/src/com/android/internal/car/UserMetrics.java b/src/com/android/internal/car/UserMetrics.java
new file mode 100644
index 0000000..ff5fa75
--- /dev/null
+++ b/src/com/android/internal/car/UserMetrics.java
@@ -0,0 +1,355 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Metrics for user switches.
+ *
+ * <p>It stores 2 types of metrics:
+ *
+ * <ol>
+ * <li>Time to start a user (from start to unlock)
+ * <li>Time to stop a user (from stop to shutdown)
+ * </ol>
+ *
+ * <p>It keeps track of the users being started and stopped, then logs the last
+ * {{@link #INITIAL_CAPACITY}} occurrences of each when the operation finished (so it can be dumped
+ * later).
+ */
+final class UserMetrics {
+
+ private static final String TAG = UserMetrics.class.getSimpleName();
+
+ /**
+ * Initial capacity for the current operations.
+ */
+ // Typically there are at most 2 users (system and 1st full), although it could be higher on
+ // garage mode
+ private static final int INITIAL_CAPACITY = 2;
+
+ // TODO(b/150413515): read from resources
+ private static final int LOG_SIZE = 10;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private SparseArray<UserStartingMetric> mUserStartingMetrics;
+ @GuardedBy("mLock")
+ private SparseArray<UserStoppingMetric> mUserStoppingMetrics;
+
+ @GuardedBy("mLock")
+ private final LocalLog mUserStartedLogs = new LocalLog(LOG_SIZE);
+ @GuardedBy("mLock")
+ private final LocalLog mUserStoppedLogs = new LocalLog(LOG_SIZE);
+
+ /**
+ * Logs a user lifecycle event.
+ */
+ public void onEvent(@UserLifecycleEventType int eventType, long timestampMs,
+ @UserIdInt int fromUserId, @UserIdInt int toUserId) {
+ synchronized (mLock) {
+ switch(eventType) {
+ case USER_LIFECYCLE_EVENT_TYPE_STARTING:
+ onUserStartingEventLocked(timestampMs, toUserId);
+ return;
+ case USER_LIFECYCLE_EVENT_TYPE_SWITCHING:
+ onUserSwitchingEventLocked(timestampMs, fromUserId, toUserId);
+ return;
+ case USER_LIFECYCLE_EVENT_TYPE_UNLOCKING:
+ onUserUnlockingEventLocked(timestampMs, toUserId);
+ return;
+ case USER_LIFECYCLE_EVENT_TYPE_UNLOCKED:
+ onUserUnlockedEventLocked(timestampMs, toUserId);
+ return;
+ case USER_LIFECYCLE_EVENT_TYPE_STOPPING:
+ onUserStoppingEventLocked(timestampMs, toUserId);
+ return;
+ case USER_LIFECYCLE_EVENT_TYPE_STOPPED:
+ onUserStoppedEventLocked(timestampMs, toUserId);
+ return;
+ default:
+ Slog.w(TAG, "Invalid event: " + eventType);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ SparseArray<UserStartingMetric> getUserStartMetrics() {
+ synchronized (mLock) {
+ return mUserStartingMetrics;
+ }
+ }
+
+ @VisibleForTesting
+ SparseArray<UserStoppingMetric> getUserStopMetrics() {
+ synchronized (mLock) {
+ return mUserStoppingMetrics;
+ }
+ }
+
+ private void onUserStartingEventLocked(long timestampMs, @UserIdInt int userId) {
+ if (mUserStartingMetrics == null) {
+ mUserStartingMetrics = new SparseArray<>(INITIAL_CAPACITY);
+ }
+
+ UserStartingMetric existingMetrics = mUserStartingMetrics.get(userId);
+ if (existingMetrics != null) {
+ Slog.w(TAG, "user re-started: " + existingMetrics);
+ finishUserStartingLocked(existingMetrics, /* removeMetric= */ false);
+ }
+
+ mUserStartingMetrics.put(userId, new UserStartingMetric(userId, timestampMs));
+ }
+
+ private void onUserSwitchingEventLocked(long timestampMs, @UserIdInt int fromUserId,
+ @UserIdInt int toUserId) {
+ UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, toUserId);
+ if (metrics == null) return;
+
+ metrics.switchFromUserId = fromUserId;
+ metrics.switchTime = timestampMs;
+ }
+
+ private void onUserUnlockingEventLocked(long timestampMs, @UserIdInt int userId) {
+ UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, userId);
+ if (metrics == null) return;
+
+ metrics.unlockingTime = timestampMs;
+ }
+
+ private void onUserUnlockedEventLocked(long timestampMs, @UserIdInt int userId) {
+ UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, userId);
+ if (metrics == null) return;
+
+ metrics.unlockedTime = timestampMs;
+
+ finishUserStartingLocked(metrics, /* removeMetric= */ true);
+ }
+
+ private void onUserStoppingEventLocked(long timestampMs, @UserIdInt int userId) {
+ if (mUserStoppingMetrics == null) {
+ mUserStoppingMetrics = new SparseArray<>(INITIAL_CAPACITY);
+ }
+ UserStoppingMetric existingMetrics = mUserStoppingMetrics.get(userId);
+ if (existingMetrics != null) {
+ Slog.w(TAG, "user re-stopped: " + existingMetrics);
+ finishUserStoppingLocked(existingMetrics, /* removeMetric= */ false);
+ }
+ mUserStoppingMetrics.put(userId, new UserStoppingMetric(userId, timestampMs));
+ }
+
+ private void onUserStoppedEventLocked(long timestampMs, @UserIdInt int userId) {
+ UserStoppingMetric metrics = getExistingMetricsLocked(mUserStoppingMetrics, userId);
+ if (metrics == null) return;
+
+ metrics.shutdownTime = timestampMs;
+ finishUserStoppingLocked(metrics, /* removeMetric= */ true);
+ }
+
+ @Nullable
+ private <T extends BaseUserMetric> T getExistingMetricsLocked(
+ @NonNull SparseArray<? extends BaseUserMetric> metrics, @UserIdInt int userId) {
+ if (metrics == null) {
+ Slog.w(TAG, "getExistingMetricsLocked() should not pass null metrics, except on tests");
+ return null;
+ }
+ @SuppressWarnings("unchecked")
+ T metric = (T) metrics.get(userId);
+ if (metric == null) {
+ String name = metrics == mUserStartingMetrics ? "starting" : "stopping";
+ Slog.w(TAG, "no " + name + " metrics for user " + userId);
+ }
+ return metric;
+ }
+
+ private void removeExistingMetricsLogged(@NonNull SparseArray<? extends BaseUserMetric> metrics,
+ @UserIdInt int userId) {
+ metrics.remove(userId);
+ if (metrics.size() != 0) return;
+
+ if (metrics == mUserStartingMetrics) {
+ mUserStartingMetrics = null;
+ } else {
+ mUserStoppingMetrics = null;
+ }
+ }
+
+ private void finishUserStartingLocked(@NonNull UserStartingMetric metrics,
+ boolean removeMetric) {
+ mUserStartedLogs.log(metrics.toString());
+ if (removeMetric) {
+ removeExistingMetricsLogged(mUserStartingMetrics, metrics.userId);
+ }
+ }
+
+ private void finishUserStoppingLocked(@NonNull UserStoppingMetric metrics,
+ boolean removeMetric) {
+ mUserStoppedLogs.log(metrics.toString());
+ if (removeMetric) {
+ removeExistingMetricsLogged(mUserStoppingMetrics, metrics.userId);
+ }
+ }
+
+ /**
+ * Dumps its contents.
+ */
+ public void dump(@NonNull IndentingPrintWriter pw) {
+ pw.println("* User Metrics *");
+ synchronized (mLock) {
+
+ dump(pw, "starting", mUserStartingMetrics);
+ dump(pw, "stopping", mUserStoppingMetrics);
+
+ pw.printf("Last %d started users\n", LOG_SIZE);
+ mUserStartedLogs.dump(" ", pw);
+
+ pw.printf("Last %d stopped users\n", LOG_SIZE);
+ mUserStoppedLogs.dump(" ", pw);
+
+ pw.println();
+ }
+ }
+
+ private void dump(@NonNull IndentingPrintWriter pw, @NonNull String message,
+ @NonNull SparseArray<? extends BaseUserMetric> metrics) {
+ pw.increaseIndent();
+ try {
+ if (metrics == null) {
+ pw.printf("no users %s\n", message);
+ return;
+ }
+ int size = metrics.size();
+ pw.printf("%d users %s\n", size, message);
+ for (int i = 0; i < size; i++) {
+ BaseUserMetric metric = metrics.valueAt(i);
+ pw.printf("%d: ", i);
+ metric.dump(pw);
+ pw.println();
+ }
+ } finally {
+ pw.decreaseIndent();
+ }
+ }
+
+ private abstract class BaseUserMetric {
+ public final @UserIdInt int userId;
+
+ protected BaseUserMetric(@UserIdInt int userId) {
+ this.userId = userId;
+ }
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
+ dump(ipw);
+ }
+ pw.flush();
+ return sw.toString();
+ }
+
+ abstract void dump(@NonNull IndentingPrintWriter pw);
+ }
+
+ @VisibleForTesting
+ final class UserStartingMetric extends BaseUserMetric {
+ public final long startTime;
+ public long switchTime;
+ public long unlockingTime;
+ public long unlockedTime;
+ public @UserIdInt int switchFromUserId;
+
+ UserStartingMetric(@UserIdInt int userId, long startTime) {
+ super(userId);
+ this.startTime = startTime;
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter pw) {
+ pw.printf("user=%d start=", userId);
+ TimeUtils.dumpTime(pw, startTime);
+
+ if (switchTime > 0) {
+ long delta = switchTime - startTime;
+ pw.print(" switch");
+ if (switchFromUserId != 0) {
+ pw.printf("(from %d)", switchFromUserId);
+ }
+ pw.print('=');
+ TimeUtils.formatDuration(delta, pw);
+ }
+
+ if (unlockingTime > 0) {
+ long delta = unlockingTime - startTime;
+ pw.print(" unlocking=");
+ TimeUtils.formatDuration(delta, pw);
+ }
+ if (unlockedTime > 0) {
+ long delta = unlockedTime - startTime;
+ pw.print(" unlocked=");
+ TimeUtils.formatDuration(delta, pw);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ final class UserStoppingMetric extends BaseUserMetric {
+ public final long stopTime;
+ public long shutdownTime;
+
+ UserStoppingMetric(@UserIdInt int userId, long stopTime) {
+ super(userId);
+ this.stopTime = stopTime;
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter pw) {
+ pw.printf("user=%d stop=", userId);
+ TimeUtils.dumpTime(pw, stopTime);
+
+ if (shutdownTime > 0) {
+ long delta = shutdownTime - stopTime;
+ pw.print(" shutdown=");
+ TimeUtils.formatDuration(delta, pw);
+ }
+ }
+ }
+}
diff --git a/src/com/android/server/wm/CarLaunchParamsModifier.java b/src/com/android/server/wm/CarLaunchParamsModifier.java
index 78bba9b..0997ff2 100644
--- a/src/com/android/server/wm/CarLaunchParamsModifier.java
+++ b/src/com/android/server/wm/CarLaunchParamsModifier.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static com.android.server.wm.ActivityStarter.Request;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -132,7 +135,7 @@ public final class CarLaunchParamsModifier implements LaunchParamsController.Lau
*/
public void init() {
mAtm = (ActivityTaskManagerService) ActivityTaskManager.getService();
- LaunchParamsController controller = mAtm.mStackSupervisor.getLaunchParamsController();
+ LaunchParamsController controller = mAtm.mTaskSupervisor.getLaunchParamsController();
controller.registerModifier(this);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mDisplayManager.registerDisplayListener(mDisplayListener,
@@ -168,7 +171,7 @@ public final class CarLaunchParamsModifier implements LaunchParamsController.Lau
}
}
- private void removeUserFromWhitelistsLocked(int userId) {
+ private void removeUserFromAllowlistsLocked(int userId) {
for (int i = mDisplayToProfileUserMapping.size() - 1; i >= 0; i--) {
if (mDisplayToProfileUserMapping.valueAt(i) == userId) {
mDisplayToProfileUserMapping.removeAt(i);
@@ -182,27 +185,27 @@ public final class CarLaunchParamsModifier implements LaunchParamsController.Lau
// Note that the current user is never stopped. It always takes switching into
// non-current user before stopping the user.
synchronized (mLock) {
- removeUserFromWhitelistsLocked(stoppedUser);
+ removeUserFromAllowlistsLocked(stoppedUser);
}
}
/**
- * Sets display whiltelist for the userId. For passenger user, activity will be always launched
- * to a display in the whitelist. If requested display is not in the whitelist, the 1st display
- * in the whitelist will be selected as target display.
+ * Sets display allowlist for the userId. For passenger user, activity will be always launched
+ * to a display in the allowlist. If requested display is not in the allowlist, the 1st display
+ * in the allowlist will be selected as target display.
*
- * <p>The whitelist is kept only for profile user. Assigning the current user unassigns users
+ * <p>The allowlist is kept only for profile user. Assigning the current user unassigns users
* for the given displays.
*/
- public void setDisplayWhitelistForUser(int userId, int[] displayIds) {
+ public void setDisplayAllowListForUser(int userId, int[] displayIds) {
if (DBG) {
- Slog.d(TAG, "setDisplayWhitelistForUser userId:" + userId
+ Slog.d(TAG, "setDisplayAllowlistForUser userId:" + userId
+ " displays:" + displayIds);
}
synchronized (mLock) {
for (int displayId : displayIds) {
if (!mPassengerDisplays.contains(displayId)) {
- Slog.w(TAG, "setDisplayWhitelistForUser called with display:" + displayId
+ Slog.w(TAG, "setDisplayAllowlistForUser called with display:" + displayId
+ " not in passenger display list:" + mPassengerDisplays);
continue;
}
@@ -220,7 +223,7 @@ public final class CarLaunchParamsModifier implements LaunchParamsController.Lau
if (displayIds.length > 0) {
mDefaultDisplayForProfileUser.put(userId, displayIds[0]);
} else {
- removeUserFromWhitelistsLocked(userId);
+ removeUserFromAllowlistsLocked(userId);
}
}
}
@@ -265,8 +268,10 @@ public final class CarLaunchParamsModifier implements LaunchParamsController.Lau
* allowed, change to the 1st allowed display.</p>
*/
@Override
- public int onCalculate(Task task, ActivityInfo.WindowLayout layout, ActivityRecord activity,
- ActivityRecord source, ActivityOptions options, int phase,
+ @Result
+ public int onCalculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,
+ @Nullable ActivityRecord activity, @Nullable ActivityRecord source,
+ ActivityOptions options, @Nullable Request request, int phase,
LaunchParamsController.LaunchParams currentParams,
LaunchParamsController.LaunchParams outParams) {
int userId;
@@ -357,7 +362,7 @@ public final class CarLaunchParamsModifier implements LaunchParamsController.Lau
break decision;
}
targetDisplayArea = getAlternativeDisplayAreaForPassengerLocked(
- userId, targetDisplayArea);
+ userId, activity, request);
}
if (targetDisplayArea != null && originalDisplayArea != targetDisplayArea) {
Slog.i(TAG, "Changed launching display, user:" + userId
@@ -372,21 +377,10 @@ public final class CarLaunchParamsModifier implements LaunchParamsController.Lau
@Nullable
private TaskDisplayArea getAlternativeDisplayAreaForPassengerLocked(int userId,
- TaskDisplayArea originalDisplayArea) {
- int displayId = mDefaultDisplayForProfileUser.get(userId, Display.INVALID_DISPLAY);
- if (displayId != Display.INVALID_DISPLAY) {
- return getDefaultTaskDisplayAreaOnDisplay(displayId);
- }
- // return the 1st passenger display area if it exists
- if (!mPassengerDisplays.isEmpty()) {
- Slog.w(TAG, "No default display area for user:" + userId
- + " reassign to 1st passenger display area");
- return getDefaultTaskDisplayAreaOnDisplay(mPassengerDisplays.get(0));
- }
- Slog.w(TAG, "No default display for user:" + userId
- + " and no passenger display, keep the requested display area:"
- + originalDisplayArea);
- return originalDisplayArea;
+ @NonNull ActivityRecord activityRecord, @Nullable Request request) {
+ TaskDisplayArea sourceDisplayArea = sourceDisplayArea(userId, activityRecord, request);
+
+ return sourceDisplayArea != null ? sourceDisplayArea : fallbackDisplayArea(userId);
}
@VisibleForTesting
@@ -398,4 +392,99 @@ public final class CarLaunchParamsModifier implements LaunchParamsController.Lau
}
return dc.getDefaultTaskDisplayArea();
}
+
+ /**
+ * Calculates the {@link TaskDisplayArea} for the source of the request. The source is
+ * calculated implicitly from the request or the activity record.
+ *
+ * @param userId ID of the current active user
+ * @param activityRecord {@link ActivityRecord} that is to be shown
+ * @param request {@link Request} data for showing the {@link ActivityRecord}
+ * @return {@link TaskDisplayArea} First non {@code null} candidate display area that is allowed
+ * for the user. It is allowed if the display has been added to the profile mapping.
+ */
+ @Nullable
+ private TaskDisplayArea sourceDisplayArea(int userId, @NonNull ActivityRecord activityRecord,
+ @Nullable Request request) {
+ List<WindowProcessController> candidateControllers = candidateControllers(activityRecord,
+ request);
+
+ for (int i = 0; i < candidateControllers.size(); i++) {
+ WindowProcessController controller = candidateControllers.get(i);
+ TaskDisplayArea candidate = controller.getTopActivityDisplayArea();
+ int displayId = candidate != null ? candidate.getDisplayId() : Display.INVALID_DISPLAY;
+ int userForDisplay = mDisplayToProfileUserMapping.get(displayId, UserHandle.USER_NULL);
+ if (userForDisplay == userId) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Calculates a list of {@link WindowProcessController} that can calculate the
+ * {@link TaskDisplayArea} to house the {@link ActivityRecord}. Controllers are calculated since
+ * calculating the display can be expensive. The list is ordered in the
+ * following way
+ * <ol>
+ * <li>Controller for the activity record from the process name and app uid</li>
+ * <li>Controller for the activity that is launching the given record</li>
+ * <li>Controller for the actual process that is launching the record</li>
+ * </ol>
+ *
+ * @param activityRecord {@link ActivityRecord} that is to be shown
+ * @param request {@link Request} data for showing the {@link ActivityRecord}
+ * @return {@link List} of {@link WindowProcessController} ordered by preference to be shown
+ */
+ private List<WindowProcessController> candidateControllers(
+ @NonNull ActivityRecord activityRecord, @Nullable Request request) {
+ WindowProcessController firstController = mAtm.getProcessController(
+ activityRecord.getProcessName(), activityRecord.getUid());
+
+ WindowProcessController secondController = mAtm.getProcessController(
+ activityRecord.getLaunchedFromPid(), activityRecord.getLaunchedFromUid());
+
+ WindowProcessController thirdController = request == null ? null :
+ mAtm.getProcessController(request.realCallingPid, request.realCallingUid);
+
+ List<WindowProcessController> candidates = new ArrayList<>(3);
+
+ if (firstController != null) {
+ candidates.add(firstController);
+ }
+ if (secondController != null) {
+ candidates.add(secondController);
+ }
+ if (thirdController != null) {
+ candidates.add(thirdController);
+ }
+
+ return candidates;
+ }
+
+ /**
+ * Return a {@link TaskDisplayArea} that can be used if a source display area is not found.
+ * First check the default display for the user. If it is absent select the first passenger
+ * display if present. If both are absent return {@code null}
+ *
+ * @param userId ID of the active user
+ * @return {@link TaskDisplayArea} that is recommended when a display area is not specified
+ */
+ @Nullable
+ private TaskDisplayArea fallbackDisplayArea(int userId) {
+ int displayIdForUserProfile = mDefaultDisplayForProfileUser.get(userId,
+ Display.INVALID_DISPLAY);
+ if (displayIdForUserProfile != Display.INVALID_DISPLAY) {
+ int displayId = mDefaultDisplayForProfileUser.get(userId);
+ return getDefaultTaskDisplayAreaOnDisplay(displayId);
+ }
+
+ if (!mPassengerDisplays.isEmpty()) {
+ int displayId = mPassengerDisplays.get(0);
+ return getDefaultTaskDisplayAreaOnDisplay(displayId);
+ }
+
+ return null;
+ }
+
}
diff --git a/tests/Android.mk b/tests/Android.mk
index 79cddee..1cf4dcc 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -25,17 +25,18 @@ LOCAL_PROGUARD_ENABLED := disabled
LOCAL_JAVA_LIBRARIES += \
android.test.runner \
android.test.base \
- android.hardware.automotive.vehicle-V2.0-java
+ android.hardware.automotive.vehicle-V2.0-java \
+ com.android.car.internal.common
LOCAL_STATIC_JAVA_LIBRARIES := \
- android.car.internal.event-log-tags \
android.car.test.utils \
- android.car.userlib \
android.car.watchdoglib \
androidx.test.ext.junit \
androidx.test.rules \
+ com.android.car.internal.system \
mockito-target-extended-minus-junit4 \
services.core \
+ testng \
truth-prebuilt
# mockito-target-extended dependencies
diff --git a/tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java b/tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java
new file mode 100644
index 0000000..ef50f92
--- /dev/null
+++ b/tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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 android.app.admin.DevicePolicyManager.operationToString;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManagerLiteInternal;
+import android.app.admin.DevicePolicySafetyChecker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public final class CarDevicePolicySafetyCheckerTest {
+
+ @Rule
+ public final MockitoRule rule = MockitoJUnit.rule();
+
+ @Mock
+ private DevicePolicySafetyChecker mCheckerImplementation;
+
+ @Mock
+ private DevicePolicyManagerLiteInternal mDpmi;
+
+ private CarDevicePolicySafetyChecker mChecker;
+
+ private final @DevicePolicyOperation int mOperation;
+ private final boolean mSafe;
+
+ @Before
+ public void setFixtures() {
+ mChecker = new CarDevicePolicySafetyChecker(mCheckerImplementation, mDpmi);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<?> packageManagers() {
+ return Arrays.asList(new Object[][] {
+ // unsafe operations
+ { DevicePolicyManager.OPERATION_CLEAR_APPLICATION_USER_DATA, false },
+ { DevicePolicyManager.OPERATION_LOGOUT_USER, false },
+ { DevicePolicyManager.OPERATION_REBOOT, false },
+ { DevicePolicyManager.OPERATION_REQUEST_BUGREPORT, false },
+ { DevicePolicyManager.OPERATION_SET_APPLICATION_HIDDEN, false },
+ { DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS, false },
+ { DevicePolicyManager.OPERATION_SET_KEYGUARD_DISABLED, false },
+ { DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED, false },
+ { DevicePolicyManager.OPERATION_SET_STATUS_BAR_DISABLED, false },
+ { DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING, false },
+ { DevicePolicyManager.OPERATION_SWITCH_USER, false },
+ { DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES, false },
+ { DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES, false },
+
+ // safe operations
+ { DevicePolicyManager.OPERATION_WIPE_DATA, true }, // Safe because it'll be delayed
+ { DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER, true },
+ { DevicePolicyManager.OPERATION_INSTALL_CA_CERT, true },
+ { DevicePolicyManager.OPERATION_INSTALL_KEY_PAIR, true },
+ { DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE, true },
+ { DevicePolicyManager.OPERATION_LOCK_NOW, true },
+ { DevicePolicyManager.OPERATION_REMOVE_ACTIVE_ADMIN, true },
+ { DevicePolicyManager.OPERATION_REMOVE_KEY_PAIR, true },
+ { DevicePolicyManager.OPERATION_REMOVE_USER, true },
+ { DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE, true },
+ { DevicePolicyManager.OPERATION_SET_CAMERA_DISABLED, true },
+ { DevicePolicyManager.OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY, true },
+ { DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS, true },
+ { DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES, true },
+ { DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED, true },
+ { DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED, true },
+ { DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED, true },
+ { DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE, true },
+ { DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY, true },
+ { DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER, true },
+ { DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY, true },
+ { DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION, true },
+ { DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES, true },
+ { DevicePolicyManager.OPERATION_SET_USER_RESTRICTION, true },
+ { DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND, true },
+ { DevicePolicyManager.OPERATION_STOP_USER, true },
+ { DevicePolicyManager.OPERATION_UNINSTALL_CA_CERT, true }
+ });
+ }
+
+ public CarDevicePolicySafetyCheckerTest(@DevicePolicyOperation int operation, boolean safe) {
+ mOperation = operation;
+ mSafe = safe;
+ }
+
+ @Test
+ public void testSafe() throws Exception {
+ boolean isSafe = true;
+ mChecker.setSafe(isSafe);
+
+ assertWithMessage("safety of %s when car is safe", operationToString(mOperation))
+ .that(mChecker.isDevicePolicyOperationSafe(mOperation)).isTrue();
+
+ verifySafetyNofiticationSend(isSafe);
+ }
+
+ @Test
+ public void testUnsafe() throws Exception {
+ boolean isSafe = false;
+ mChecker.setSafe(isSafe);
+
+ if (mSafe) {
+ assertWithMessage("safety of %s EVEN when car isn't safe",
+ operationToString(mOperation))
+ .that(mChecker.isDevicePolicyOperationSafe(mOperation)).isTrue();
+ } else {
+ assertWithMessage("safety of %s when car isn't safe",
+ operationToString(mOperation))
+ .that(mChecker.isDevicePolicyOperationSafe(mOperation)).isFalse();
+ }
+
+ verifySafetyNofiticationSend(isSafe);
+ }
+
+ private void verifySafetyNofiticationSend(boolean isSafe) {
+ verify(mDpmi).notifyUnsafeOperationStateChanged(mCheckerImplementation,
+ DevicePolicyManager.OPERATION_SAFETY_REASON_DRIVING_DISTRACTION, isSafe);
+ }
+}
diff --git a/tests/src/com/android/internal/car/CarHelperServiceTest.java b/tests/src/com/android/internal/car/CarHelperServiceTest.java
deleted file mode 100644
index e20009d..0000000
--- a/tests/src/com/android/internal/car/CarHelperServiceTest.java
+++ /dev/null
@@ -1,1232 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.car.test.util.UserTestingHelper.getDefaultUserType;
-import static android.car.test.util.UserTestingHelper.newGuestUser;
-import static android.car.test.util.UserTestingHelper.newSecondaryUser;
-import static android.car.test.util.UserTestingHelper.UserInfoBuilder;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.fail;
-import static org.mockito.AdditionalAnswers.answerVoid;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.ArgumentMatchers.notNull;
-import static org.mockito.ArgumentMatchers.nullable;
-
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
-import android.car.test.mocks.SyncAnswer;
-import android.car.userlib.CarUserManagerHelper;
-import android.car.userlib.HalCallback;
-import android.car.userlib.CommonConstants.CarUserServiceConstants;
-import android.car.userlib.InitialUserSetter;
-import android.car.userlib.UserHalHelper;
-import android.car.watchdoglib.CarWatchdogDaemonHelper;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.hardware.automotive.vehicle.V2_0.UserFlags;
-import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
-import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import android.sysprop.CarProperties;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.car.ExternalConstants.CarUserManagerConstants;
-import com.android.internal.car.ExternalConstants.ICarConstants;
-import com.android.internal.os.IResultReceiver;
-import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
-import com.android.server.wm.CarLaunchParamsModifier;
-import com.android.server.utils.TimingsTraceAndSlog;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.quality.Strictness;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * This class contains unit tests for the {@link CarServiceHelperService}.
- */
-@RunWith(AndroidJUnit4.class)
-public class CarHelperServiceTest extends AbstractExtendedMockitoTestCase {
-
- private static final String TAG = CarHelperServiceTest.class.getSimpleName();
-
- private static final int PRE_CREATED_USER_ID = 24;
- private static final int PRE_CREATED_GUEST_ID = 25;
- private static final int USER_MANAGER_TIMEOUT_MS = 100;
-
- private static final String HAL_USER_NAME = "HAL 9000";
- private static final int HAL_USER_ID = 42;
- private static final int HAL_USER_FLAGS = 108;
-
- private static final String USER_LOCALES = "LOL";
-
- private static final int HAL_TIMEOUT_MS = 500;
-
- private static final int ADDITIONAL_TIME_MS = 200;
-
- private static final int HAL_NOT_REPLYING_TIMEOUT_MS = HAL_TIMEOUT_MS + ADDITIONAL_TIME_MS;
-
- private static final long POST_HAL_NOT_REPLYING_TIMEOUT_MS = HAL_NOT_REPLYING_TIMEOUT_MS
- + ADDITIONAL_TIME_MS;
-
-
- // Spy used in tests that need to verify folloing method:
- // managePreCreatedUsers, postAsyncPreCreatedUser, preCreateUsers
- private CarServiceHelperService mHelper;
-
- @Mock
- private Context mMockContext;
- @Mock
- private PackageManager mPackageManager;
- @Mock
- private Context mApplicationContext;
- @Mock
- private CarUserManagerHelper mUserManagerHelper;
- @Mock
- private UserManager mUserManager;
- @Mock
- private CarLaunchParamsModifier mCarLaunchParamsModifier;
- @Mock
- private CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
- @Mock
- private IBinder mICarBinder;
- @Mock
- private InitialUserSetter mInitialUserSetter;
-
- @Captor
- private ArgumentCaptor<Parcel> mBinderCallData;
-
- private Exception mBinderCallException;
-
- /**
- * Initialize objects and setup testing environment.
- */
- @Override
- protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
- session
- .spyStatic(CarProperties.class)
- .spyStatic(UserManager.class);
- }
-
- @Before
- public void setUpMocks() {
- mHelper = spy(new CarServiceHelperService(
- mMockContext,
- mUserManagerHelper,
- mInitialUserSetter,
- mUserManager,
- mCarLaunchParamsModifier,
- mCarWatchdogDaemonHelper,
- /* halEnabled= */ true,
- HAL_TIMEOUT_MS));
-
- when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
- }
-
- @Test
- public void testCarServiceLaunched() throws Exception {
- mockRegisterReceiver();
- mockBindService();
- mockLoadLibrary();
-
- mHelper.onStart();
-
- verifyBindService();
- }
-
- @Test
- public void testHandleCarServiceCrash() throws Exception {
- mockHandleCarServiceCrash();
- mockCarServiceException();
-
- mHelper.handleCarServiceConnection(mICarBinder);
-
- verify(mHelper).handleCarServiceCrash();
- }
-
- /**
- * Test that the {@link CarServiceHelperService} starts up a secondary admin user upon first
- * run.
- */
- @Test
- public void testInitialInfo_noHal() throws Exception {
- CarServiceHelperService halLessHelper = new CarServiceHelperService(
- mMockContext,
- mUserManagerHelper,
- mInitialUserSetter,
- mUserManager,
- mCarLaunchParamsModifier,
- mCarWatchdogDaemonHelper,
- /* halEnabled= */ false,
- HAL_TIMEOUT_MS);
-
- halLessHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
- verifyDefaultBootBehavior();
- }
-
- @Test
- public void testInitialInfo_halReturnedDefault() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo(InitialUserInfoAction.DEFAULT);
-
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
- verifyDefaultBootBehavior();
- }
-
- @Test
- public void testInitialInfo_halReturnedDefault_withLocale() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo(InitialUserInfoAction.DEFAULT_WITH_LOCALE);
-
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
- verifyDefaultBootBehaviorWithLocale();
- }
-
- @Test
- public void testInitialInfo_halServiceNeverReturned() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo(InitialUserInfoAction.DO_NOT_REPLY);
-
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
- sleep("before asserting DEFAULT behavior", POST_HAL_NOT_REPLYING_TIMEOUT_MS);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isLessThan(0);
-
- verifyDefaultBootBehavior();
- }
-
- @Test
- public void testInitialInfo_halServiceReturnedTooLate() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo(InitialUserInfoAction.DELAYED_REPLY);
-
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
- sleep("before asserting DEFAULT behavior", POST_HAL_NOT_REPLYING_TIMEOUT_MS);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
- sleep("to make sure not called again", POST_HAL_NOT_REPLYING_TIMEOUT_MS);
-
- verifyDefaultBootBehavior();
- }
-
- @Test
- public void testInitialInfo_halReturnedNonOkResultCode() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo(InitialUserInfoAction.NON_OK_RESULT_CODE);
-
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
- verifyDefaultBootBehavior();
- }
-
- @Test
- public void testInitialInfo_halReturnedOkWithNoBundle() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo(InitialUserInfoAction.NULL_BUNDLE);
-
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
- verifyDefaultBootBehavior();
- }
-
- @Test
- public void testInitialInfo_halReturnedSwitch_ok() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo(InitialUserInfoAction.SWITCH_OK);
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
- verifyUserSwitchedByHal();
- }
-
- @Test
- public void testInitialInfo_halReturnedSwitch_ok_withLocale() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo(InitialUserInfoAction.SWITCH_OK_WITH_LOCALE);
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
- verifyUserSwitchedByHalWithLocale();
- }
-
- @Test
- public void testInitialInfo_halReturnedSwitch_switchMissingUserId() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo(InitialUserInfoAction.SWITCH_MISSING_USER_ID);
-
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
- verifyUserNotSwitchedByHal();
- verifyDefaultBootBehavior();
- }
-
- @Test
- public void testInitialInfo_halReturnedCreateOk() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo((r) -> sendCreateDefaultHalUserAction(r));
-
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
- verifyUserCreatedByHal();
- }
-
- @Test
- public void testInitialInfo_halReturnedCreateOk_withLocale() throws Exception {
- bindMockICar();
-
- expectICarGetInitialUserInfo(
- (r) -> sendCreateAction(r, HAL_USER_NAME, HAL_USER_FLAGS, USER_LOCALES));
-
- mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
- assertNoICarCallExceptions();
- verifyICarGetInitialUserInfoCalled();
- assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
- verifyUserCreatedByHalWithLocale();
- }
-
- @Test
- public void testOnUserStarting_notifiesICar() throws Exception {
- bindMockICar();
-
- int userId = 10;
- expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING,
- userId);
-
- mHelper.onUserStarting(newTargetUser(userId));
-
- assertNoICarCallExceptions();
- verifyICarOnUserLifecycleEventCalled();
- }
-
- @Test
- public void testOnUserStarting_preCreatedDoesntNotifyICar() throws Exception {
- bindMockICar();
-
- mHelper.onUserStarting(newTargetUser(10, /* preCreated= */ true));
-
- verifyICarOnUserLifecycleEventNeverCalled();
- }
-
- @Test
- public void testOnUserSwitching_notifiesICar() throws Exception {
- bindMockICar();
-
- int currentUserId = 10;
- int targetUserId = 11;
- expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
- currentUserId, targetUserId);
-
- mHelper.onUserSwitching(newTargetUser(currentUserId),
- newTargetUser(targetUserId));
-
- assertNoICarCallExceptions();
- }
-
- @Test
- public void testOnUserSwitching_preCreatedDoesntNotifyICar() throws Exception {
- bindMockICar();
-
- mHelper.onUserSwitching(newTargetUser(10), newTargetUser(11, /* preCreated= */ true));
-
- verifyICarOnUserLifecycleEventNeverCalled();
- }
-
- @Test
- public void testOnUserUnlocking_notifiesICar() throws Exception {
- bindMockICar();
-
- int userId = 10;
- expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
- userId);
-
- mHelper.onUserUnlocking(newTargetUser(userId));
-
- assertNoICarCallExceptions();
- }
-
- @Test
- public void testOnUserUnlocking_preCreatedDoesntNotifyICar() throws Exception {
- bindMockICar();
-
- mHelper.onUserUnlocking(newTargetUser(10, /* preCreated= */ true));
-
- verifyICarOnUserLifecycleEventNeverCalled();
- }
-
- @Test
- public void testOnUserUnlocked_notifiesICar_systemUserFirst() throws Exception {
- bindMockICar();
-
- int systemUserId = UserHandle.USER_SYSTEM;
- expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
- systemUserId);
-
- int firstUserId = 10;
- expectICarFirstUserUnlocked(firstUserId);
-
- setHalResponseTime();
- mHelper.onUserUnlocked(newTargetUser(systemUserId));
- mHelper.onUserUnlocked(newTargetUser(firstUserId));
-
- assertNoICarCallExceptions();
-
- verifyICarOnUserLifecycleEventCalled(); // system user
- verifyICarFirstUserUnlockedCalled(); // first user
- }
-
- @Test
- public void testOnUserUnlocked_notifiesICar_firstUserReportedJustOnce() throws Exception {
- bindMockICar();
-
- int firstUserId = 10;
- expectICarFirstUserUnlocked(firstUserId);
-
- int secondUserId = 11;
- expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
- secondUserId);
-
- setHalResponseTime();
- mHelper.onUserUnlocked(newTargetUser(firstUserId));
- mHelper.onUserUnlocked(newTargetUser(secondUserId));
-
- assertNoICarCallExceptions();
-
- verifyICarFirstUserUnlockedCalled(); // first user
- verifyICarOnUserLifecycleEventCalled(); // second user
- }
-
- @Test
- public void testOnUserStopping_notifiesICar() throws Exception {
- bindMockICar();
-
- int userId = 10;
- expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING,
- userId);
-
- mHelper.onUserStopping(newTargetUser(userId));
-
- assertNoICarCallExceptions();
- }
-
- @Test
- public void testOnUserStopping_preCreatedDoesntNotifyICar() throws Exception {
- bindMockICar();
-
- mHelper.onUserStopping(newTargetUser(10, /* preCreated= */ true));
-
- verifyICarOnUserLifecycleEventNeverCalled();
- }
-
- @Test
- public void testOnUserStopped_notifiesICar() throws Exception {
- bindMockICar();
-
- int userId = 10;
- expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED,
- userId);
-
- mHelper.onUserStopped(newTargetUser(userId));
-
- assertNoICarCallExceptions();
- }
-
- @Test
- public void testOnUserStopped_preCreatedDoesntNotifyICar() throws Exception {
- bindMockICar();
-
- mHelper.onUserStopped(newTargetUser(10, /* preCreated= */ true));
-
- verifyICarOnUserLifecycleEventNeverCalled();
- }
-
- @Test
- public void testSendSetInitialUserInfoNotifiesICar() throws Exception {
- bindMockICar();
-
- UserInfo user = new UserInfo(42, "Dude", UserInfo.FLAG_ADMIN);
- expectICarSetInitialUserInfo(user);
-
- mHelper.setInitialUser(user);
-
- verifyICarSetInitialUserCalled();
- assertNoICarCallExceptions();
- }
-
- @Test
- public void testInitialUserInfoRequestType_FirstBoot() throws Exception {
- when(mUserManagerHelper.hasInitialUser()).thenReturn(false);
- when(mPackageManager.isDeviceUpgrading()).thenReturn(true);
- assertThat(mHelper.getInitialUserInfoRequestType())
- .isEqualTo(InitialUserInfoRequestType.FIRST_BOOT);
- }
-
- @Test
- public void testInitialUserInfoRequestType_FirstBootAfterOTA() throws Exception {
- when(mUserManagerHelper.hasInitialUser()).thenReturn(true);
- when(mPackageManager.isDeviceUpgrading()).thenReturn(true);
- assertThat(mHelper.getInitialUserInfoRequestType())
- .isEqualTo(InitialUserInfoRequestType.FIRST_BOOT_AFTER_OTA);
- }
-
- @Test
- public void testInitialUserInfoRequestType_ColdBoot() throws Exception {
- when(mUserManagerHelper.hasInitialUser()).thenReturn(true);
- when(mPackageManager.isDeviceUpgrading()).thenReturn(false);
- assertThat(mHelper.getInitialUserInfoRequestType())
- .isEqualTo(InitialUserInfoRequestType.COLD_BOOT);
- }
-
- @Test
- public void testPreCreatedUsersLessThanRequested() throws Exception {
- // Set existing user
- expectNoPreCreatedUser();
- // Set number of requested user
- setNumberRequestedUsersProperty(1);
- setNumberRequestedGuestsProperty(0);
- mockRunAsync();
- SyncAnswer syncUserInfo = mockPreCreateUser(/* isGuest= */ false);
-
- mHelper.managePreCreatedUsers();
- syncUserInfo.await(USER_MANAGER_TIMEOUT_MS);
-
- verifyUserCreated(/* isGuest= */ false);
- }
-
- @Test
- public void testPreCreatedGuestsLessThanRequested() throws Exception {
- // Set existing user
- expectNoPreCreatedUser();
- // Set number of requested user
- setNumberRequestedUsersProperty(0);
- setNumberRequestedGuestsProperty(1);
- mockRunAsync();
- SyncAnswer syncUserInfo = mockPreCreateUser(/* isGuest= */ true);
-
- mHelper.managePreCreatedUsers();
- syncUserInfo.await(USER_MANAGER_TIMEOUT_MS);
-
- verifyUserCreated(/* isGuest= */ true);
- }
-
- @Test
- public void testRemovePreCreatedUser() throws Exception {
- UserInfo user = expectPreCreatedUser(/* isGuest= */ false,
- /* isInitialized= */ true);
- setNumberRequestedUsersProperty(0);
- setNumberRequestedGuestsProperty(0);
- mockRunAsync();
-
- SyncAnswer syncRemoveStatus = mockRemoveUser(PRE_CREATED_USER_ID);
-
- mHelper.managePreCreatedUsers();
- syncRemoveStatus.await(USER_MANAGER_TIMEOUT_MS);
-
- verifyUserRemoved(user);
- }
-
- @Test
- public void testRemovePreCreatedGuest() throws Exception {
- UserInfo user = expectPreCreatedUser(/* isGuest= */ true,
- /* isInitialized= */ true);
- setNumberRequestedUsersProperty(0);
- setNumberRequestedGuestsProperty(0);
- mockRunAsync();
- SyncAnswer syncRemoveStatus = mockRemoveUser(PRE_CREATED_GUEST_ID);
-
- mHelper.managePreCreatedUsers();
- syncRemoveStatus.await(USER_MANAGER_TIMEOUT_MS);
-
- verifyUserRemoved(user);
- }
-
- @Test
- public void testRemoveInvalidPreCreatedUser() throws Exception {
- UserInfo user = expectPreCreatedUser(/* isGuest= */ false,
- /* isInitialized= */ false);
- setNumberRequestedUsersProperty(0);
- setNumberRequestedGuestsProperty(0);
- mockRunAsync();
- SyncAnswer syncRemoveStatus = mockRemoveUser(PRE_CREATED_USER_ID);
-
- mHelper.managePreCreatedUsers();
- syncRemoveStatus.await(ADDITIONAL_TIME_MS);
-
- verifyUserRemoved(user);
- }
-
- @Test
- public void testManagePreCreatedUsersDoNothing() throws Exception {
- UserInfo user = expectPreCreatedUser(/* isGuest= */ false,
- /* isInitialized= */ true);
- setNumberRequestedUsersProperty(1);
- setNumberRequestedGuestsProperty(0);
- mockPreCreateUser(/* isGuest= */ false);
- mockRemoveUser(PRE_CREATED_USER_ID);
-
- mHelper.managePreCreatedUsers();
-
- verifyPostPreCreatedUserSkipped();
- }
-
- @Test
- public void testManagePreCreatedUsersOnBootCompleted() throws Exception {
- mockRunAsync();
-
- mHelper.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-
- verifyManagePreCreatedUsers();
- }
-
- @Test
- public void testPreCreateUserExceptionLogged() throws Exception {
- SyncAnswer syncException = mockPreCreateUserException();
- TimingsTraceAndSlog trace = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
- mHelper.preCreateUsers(trace, false);
-
- verifyPostPreCreatedUserException();
- assertThat(trace.getUnfinishedTracesForDebug()).isEmpty();
- }
-
- private void setHalResponseTime() {
- mHelper.setInitialHalResponseTime();
- SystemClock.sleep(1); // must sleep at least 1ms so it's not 0
- mHelper.setFinalHalResponseTime();
- }
-
- /**
- * Used in cases where the result of calling HAL for the initial info should be the same as
- * not using HAL.
- */
- private void verifyDefaultBootBehavior() throws Exception {
- verify(mInitialUserSetter).set(argThat((info) -> {
- return info.type == InitialUserSetter.TYPE_DEFAULT_BEHAVIOR && info.userLocales == null;
- }));
- }
-
- private void verifyDefaultBootBehaviorWithLocale() {
- verify(mInitialUserSetter).set(argThat((info) -> {
- return info.type == InitialUserSetter.TYPE_DEFAULT_BEHAVIOR
- && USER_LOCALES.equals(info.userLocales);
- }));
- }
-
- private TargetUser newTargetUser(int userId) {
- return newTargetUser(userId, /* preCreated= */ false);
- }
-
- private TargetUser newTargetUser(int userId, boolean preCreated) {
- TargetUser targetUser = mock(TargetUser.class);
- when(targetUser.getUserIdentifier()).thenReturn(userId);
- UserInfo userInfo = new UserInfo();
- userInfo.id = userId;
- userInfo.preCreated = preCreated;
- when(targetUser.getUserInfo()).thenReturn(userInfo);
- return targetUser;
- }
-
- private void bindMockICar() throws Exception {
- // Must set the binder expectation, otherwise checks for other transactions would fail
- expectICarSetCarServiceHelper();
- mHelper.handleCarServiceConnection(mICarBinder);
- }
-
- private void verifyUserCreatedByHal() throws Exception {
- verify(mInitialUserSetter).set(argThat((info) -> {
- return info.type == InitialUserSetter.TYPE_CREATE
- && info.newUserName == HAL_USER_NAME
- && info.newUserFlags == HAL_USER_FLAGS
- && info.userLocales == null;
- }));
- }
-
- private void verifyUserCreatedByHalWithLocale() throws Exception {
- verify(mInitialUserSetter).set(argThat((info) -> {
- return info.type == InitialUserSetter.TYPE_CREATE
- && info.newUserName == HAL_USER_NAME
- && info.newUserFlags == HAL_USER_FLAGS
- && info.userLocales == USER_LOCALES;
- }));
- }
-
- private void verifyUserSwitchedByHal() {
- verify(mInitialUserSetter).set(argThat((info) -> {
- return info.type == InitialUserSetter.TYPE_SWITCH
- && info.switchUserId == HAL_USER_ID
- && info.userLocales == null;
- }));
- }
-
- private void verifyUserSwitchedByHalWithLocale() {
- verify(mInitialUserSetter).set(argThat((info) -> {
- return info.type == InitialUserSetter.TYPE_SWITCH
- && info.switchUserId == HAL_USER_ID
- && info.userLocales == USER_LOCALES;
- }));
- }
-
- private void verifyUserNotSwitchedByHal() {
- verify(mInitialUserSetter, never()).set(argThat((info) -> {
- return info.type == InitialUserSetter.TYPE_SWITCH;
- }));
- }
-
- private void verifyBindService () throws Exception {
- verify(mMockContext).bindServiceAsUser(
- argThat(intent -> intent.getAction().equals(ICarConstants.CAR_SERVICE_INTERFACE)),
- any(), eq(Context.BIND_AUTO_CREATE), eq(UserHandle.SYSTEM));
- }
-
- private void verifyHandleCarServiceCrash() throws Exception {
- verify(mHelper).handleCarServiceCrash();
- }
-
- private void mockRegisterReceiver() {
- when(mMockContext.registerReceiverForAllUsers(any(), any(), any(), any()))
- .thenReturn(new Intent());
- }
-
- private void mockBindService() {
- when(mMockContext.bindServiceAsUser(any(), any(),
- eq(Context.BIND_AUTO_CREATE), eq(UserHandle.SYSTEM)))
- .thenReturn(true);
- }
-
- private void mockLoadLibrary() {
- doNothing().when(mHelper).loadNativeLibrary();
- }
-
- private void mockCarServiceException() throws Exception {
- when(mICarBinder.transact(anyInt(), notNull(), isNull(), eq(Binder.FLAG_ONEWAY)))
- .thenThrow(new RemoteException("mock car service Crash"));
- }
-
- private void mockHandleCarServiceCrash() throws Exception {
- doNothing().when(mHelper).handleCarServiceCrash();
- }
-
- // TODO: create a custom matcher / verifier for binder calls
- private void expectICarOnUserLifecycleEvent(int eventType, int expectedUserId)
- throws Exception {
- expectICarOnUserLifecycleEvent(eventType, UserHandle.USER_NULL, expectedUserId);
- }
-
- private void expectICarSetCarServiceHelper() throws Exception {
- int txn = IBinder.FIRST_CALL_TRANSACTION
- + ICarConstants.ICAR_CALL_SET_CAR_SERVICE_HELPER;
- when(mICarBinder.transact(eq(txn), notNull(), isNull(), eq(Binder.FLAG_ONEWAY)))
- .thenReturn(true);
- }
-
- private void expectICarOnUserLifecycleEvent(int expectedEventType, int expectedFromUserId,
- int expectedToUserId) throws Exception {
- int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE;
- long before = System.currentTimeMillis();
-
- when(mICarBinder.transact(eq(txn), notNull(), isNull(),
- eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
- try {
- long after = System.currentTimeMillis();
- Log.d(TAG, "Answering txn " + txn);
- Parcel data = (Parcel) invocation.getArguments()[1];
- data.setDataPosition(0);
- data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
- int actualEventType = data.readInt();
- long actualTimestamp = data.readLong();
- int actualFromUserId = data.readInt();
- int actualToUserId = data.readInt();
- Log.d(TAG, "Unmarshalled data: eventType=" + actualEventType
- + ", timestamp= " + actualTimestamp
- + ", fromUserId= " + actualFromUserId
- + ", toUserId= " + actualToUserId);
- List<String> errors = new ArrayList<>();
- assertParcelValueInRange(errors, "timestamp", before, actualTimestamp, after);
- assertParcelValue(errors, "eventType", expectedEventType, actualEventType);
- assertParcelValue(errors, "fromUserId", expectedFromUserId,
- actualFromUserId);
- assertParcelValue(errors, "toUserId", expectedToUserId, actualToUserId);
- assertNoParcelErrors(errors);
- return true;
- } catch (Exception e) {
- Log.e(TAG, "Exception answering binder call", e);
- mBinderCallException = e;
- return false;
- }
- });
- }
-
- private void expectICarFirstUserUnlocked(int expectedUserId) throws Exception {
- int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_FIRST_USER_UNLOCKED;
- long before = System.currentTimeMillis();
- long minDuration = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime();
-
- when(mICarBinder.transact(eq(txn), notNull(), isNull(),
- eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
- try {
- long after = System.currentTimeMillis();
- Log.d(TAG, "Answering txn " + txn);
- Parcel data = (Parcel) invocation.getArguments()[1];
- data.setDataPosition(0);
- data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
- int actualUserId = data.readInt();
- long actualTimestamp = data.readLong();
- long actualDuration = data.readLong();
- int actualHalResponseTime = data.readInt();
- Log.d(TAG, "Unmarshalled data: userId= " + actualUserId
- + ", timestamp= " + actualTimestamp
- + ", duration=" + actualDuration
- + ", halResponseTime=" + actualHalResponseTime);
- List<String> errors = new ArrayList<>();
- assertParcelValue(errors, "userId", expectedUserId, actualUserId);
- assertParcelValueInRange(errors, "timestamp", before, actualTimestamp,
- after);
- assertMinimumParcelValue(errors, "duration", minDuration, actualDuration);
- assertMinimumParcelValue(errors, "halResponseTime", 1, actualHalResponseTime);
- assertNoParcelErrors(errors);
- return true;
- } catch (Exception e) {
- Log.e(TAG, "Exception answering binder call", e);
- mBinderCallException = e;
- return false;
- }
- });
-
- }
-
- enum InitialUserInfoAction {
- DEFAULT,
- DEFAULT_WITH_LOCALE,
- DO_NOT_REPLY,
- DELAYED_REPLY,
- NON_OK_RESULT_CODE,
- NULL_BUNDLE,
- SWITCH_OK,
- SWITCH_OK_WITH_LOCALE,
- SWITCH_MISSING_USER_ID
- }
-
- private void expectICarGetInitialUserInfo(InitialUserInfoAction action) throws Exception {
- expectICarGetInitialUserInfo((receiver) ->{
- switch (action) {
- case DEFAULT:
- sendDefaultAction(receiver);
- break;
- case DEFAULT_WITH_LOCALE:
- sendDefaultAction(receiver, USER_LOCALES);
- break;
- case DO_NOT_REPLY:
- Log.d(TAG, "NOT replying to bind call");
- break;
- case DELAYED_REPLY:
- sleep("before sending result", HAL_NOT_REPLYING_TIMEOUT_MS);
- sendDefaultAction(receiver);
- break;
- case NON_OK_RESULT_CODE:
- Log.d(TAG, "sending bad result code");
- receiver.send(-1, null);
- break;
- case NULL_BUNDLE:
- Log.d(TAG, "sending OK without bundle");
- receiver.send(HalCallback.STATUS_OK, null);
- break;
- case SWITCH_OK:
- sendValidSwitchAction(receiver, /* userLocales= */ null);
- break;
- case SWITCH_OK_WITH_LOCALE:
- sendValidSwitchAction(receiver, USER_LOCALES);
- break;
- case SWITCH_MISSING_USER_ID:
- Log.d(TAG, "sending Switch without user Id");
- sendSwitchAction(receiver, /* id= */ null, /* userLocales= */ null);
- break;
- default:
- throw new IllegalArgumentException("invalid action: " + action);
- }
- });
- }
-
- private void expectICarGetInitialUserInfo(GetInitialUserInfoAction action) throws Exception {
- int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_GET_INITIAL_USER_INFO;
- when(mICarBinder.transact(eq(txn), notNull(), isNull(),
- eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
- try {
- Log.d(TAG, "Answering txn " + txn);
- Parcel data = (Parcel) invocation.getArguments()[1];
- data.setDataPosition(0);
- data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
- int actualRequestType = data.readInt();
- int actualTimeoutMs = data.readInt();
- IResultReceiver receiver = IResultReceiver.Stub
- .asInterface(data.readStrongBinder());
-
- Log.d(TAG, "Unmarshalled data: requestType= " + actualRequestType
- + ", timeout=" + actualTimeoutMs
- + ", receiver =" + receiver);
- action.onReceiver(receiver);
- return true;
- } catch (Exception e) {
- Log.e(TAG, "Exception answering binder call", e);
- mBinderCallException = e;
- return false;
- }
- });
- }
-
- private void expectICarSetInitialUserInfo(UserInfo user) throws RemoteException {
- int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_SET_INITIAL_USER;
- when(mICarBinder.transact(eq(txn), notNull(), isNull(),
- eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
- try {
- Log.d(TAG, "Answering txn " + txn);
- Parcel data = (Parcel) invocation.getArguments()[1];
- data.setDataPosition(0);
- data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
- int actualUserId = data.readInt();
- Log.d(TAG, "Unmarshalled data: user= " + actualUserId);
- assertThat(actualUserId).isEqualTo(user.id);
- return true;
- } catch (Exception e) {
- Log.e(TAG, "Exception answering binder call", e);
- mBinderCallException = e;
- return false;
- }
- });
- }
-
- private void expectNoPreCreatedUser() throws Exception {
- when(mUserManager.getUsers(/* excludePartial= */ true,
- /* excludeDying= */ true, /* excludePreCreated= */ false))
- .thenReturn(new ArrayList<UserInfo> ());
- }
-
- private UserInfo expectPreCreatedUser(boolean isGuest, boolean isInitialized)
- throws Exception {
- int userId = isGuest ? PRE_CREATED_GUEST_ID : PRE_CREATED_USER_ID;
- UserInfo user = new UserInfoBuilder(userId)
- .setGuest(isGuest)
- .setPreCreated(true)
- .setInitialized(isInitialized)
- .build();
-
- when(mUserManager.getUsers(/* excludePartial= */ true,
- /* excludeDying= */ true, /* excludePreCreated= */ false))
- .thenReturn(Arrays.asList(user));
- return user;
- }
-
- private interface GetInitialUserInfoAction {
- void onReceiver(IResultReceiver receiver) throws Exception;
- }
-
- private void sendDefaultAction(IResultReceiver receiver) throws Exception {
- sendDefaultAction(receiver, /* userLocales= */ null);
- }
-
- private void sendDefaultAction(IResultReceiver receiver, String userLocales) throws Exception {
- Log.d(TAG, "Sending DEFAULT action to receiver " + receiver);
- Bundle data = new Bundle();
- data.putInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
- InitialUserInfoResponseAction.DEFAULT);
- if (userLocales != null) {
- data.putString(CarUserServiceConstants.BUNDLE_USER_LOCALES, userLocales);
- }
- receiver.send(HalCallback.STATUS_OK, data);
- }
-
- private void sendValidSwitchAction(IResultReceiver receiver, String userLocales)
- throws Exception {
- Log.d(TAG, "Sending SWITCH (" + HAL_USER_ID + ") action to receiver " + receiver);
- sendSwitchAction(receiver, HAL_USER_ID, userLocales);
- }
-
- private void sendSwitchAction(IResultReceiver receiver, Integer id, String userLocales)
- throws Exception {
- Bundle data = new Bundle();
- data.putInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
- InitialUserInfoResponseAction.SWITCH);
- if (id != null) {
- data.putInt(CarUserServiceConstants.BUNDLE_USER_ID, id);
- }
- if (userLocales != null) {
- data.putString(CarUserServiceConstants.BUNDLE_USER_LOCALES, userLocales);
- }
- receiver.send(HalCallback.STATUS_OK, data);
- }
-
- private void sendCreateDefaultHalUserAction(IResultReceiver receiver) throws Exception {
- sendCreateAction(receiver, HAL_USER_NAME, HAL_USER_FLAGS, /* userLocales= */ null);
- }
-
- private void sendCreateAction(IResultReceiver receiver, String name, Integer flags,
- String userLocales) throws Exception {
- Bundle data = new Bundle();
- data.putInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
- InitialUserInfoResponseAction.CREATE);
- if (name != null) {
- data.putString(CarUserServiceConstants.BUNDLE_USER_NAME, name);
- }
- if (flags != null) {
- data.putInt(CarUserServiceConstants.BUNDLE_USER_FLAGS, flags);
- }
- if (userLocales != null) {
- data.putString(CarUserServiceConstants.BUNDLE_USER_LOCALES, userLocales);
- }
- receiver.send(HalCallback.STATUS_OK, data);
- }
-
- private void sleep(String reason, long napTimeMs) {
- Log.d(TAG, "Sleeping for " + napTimeMs + "ms: " + reason);
- SystemClock.sleep(napTimeMs);
- Log.d(TAG, "Woke up (from '" + reason + "')");
- }
-
- private void verifyICarOnUserLifecycleEventCalled() throws Exception {
- verifyICarTxnCalled(ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE);
- }
-
- private void verifyICarOnUserLifecycleEventNeverCalled() throws Exception {
- verifyICarTxnNeverCalled(ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE);
- }
-
- private void verifyICarFirstUserUnlockedCalled() throws Exception {
- verifyICarTxnCalled(ICarConstants.ICAR_CALL_FIRST_USER_UNLOCKED);
- }
-
- private void verifyICarGetInitialUserInfoCalled() throws Exception {
- verifyICarTxnCalled(ICarConstants.ICAR_CALL_GET_INITIAL_USER_INFO);
- }
-
- private void verifyICarSetInitialUserCalled() throws Exception {
- verifyICarTxnCalled(ICarConstants.ICAR_CALL_SET_INITIAL_USER);
- }
-
- private void verifyICarTxnCalled(int txnId) throws Exception {
- int txn = IBinder.FIRST_CALL_TRANSACTION + txnId;
- verify(mICarBinder).transact(eq(txn), notNull(), isNull(), eq(Binder.FLAG_ONEWAY));
- }
-
- private void verifyICarTxnNeverCalled(int txnId) throws Exception {
- int txn = IBinder.FIRST_CALL_TRANSACTION + txnId;
- verify(mICarBinder, never()).transact(eq(txn), notNull(), isNull(), eq(Binder.FLAG_ONEWAY));
- }
-
- private void setNumberRequestedUsersProperty(int numberUser) {
- doReturn(Optional.of(numberUser)).when(() -> CarProperties.number_pre_created_users());
- }
-
- private void setNumberRequestedGuestsProperty(int numberGuest) {
- doReturn(Optional.of(numberGuest)).when(() -> CarProperties.number_pre_created_guests());
- }
-
- private void mockRunAsync() {
- doAnswer(answerVoid(Runnable::run)).when(mHelper).runAsync(any(Runnable.class));
- }
-
- private SyncAnswer mockPreCreateUser(boolean isGuest) {
- UserInfo newUser = isGuest ? newGuestUser(PRE_CREATED_GUEST_ID) :
- newSecondaryUser(PRE_CREATED_USER_ID);
- SyncAnswer<UserInfo> syncUserInfo = SyncAnswer.forReturn(newUser);
- when(mUserManager.preCreateUser(getDefaultUserType(isGuest)))
- .thenAnswer(syncUserInfo);
-
- return syncUserInfo;
- }
-
- private SyncAnswer mockRemoveUser(@UserIdInt int userId) {
- SyncAnswer<Boolean> syncRemoveStatus = SyncAnswer.forReturn(true);
- when(mUserManager.removeUser(userId)).thenAnswer(syncRemoveStatus);
-
- return syncRemoveStatus;
- }
-
- private SyncAnswer mockPreCreateUserException() {
- SyncAnswer<UserInfo> syncException = SyncAnswer.forException(new Exception());
- when(mUserManager.preCreateUser(anyString()))
- .thenAnswer(syncException);
- return syncException;
- }
-
- private void verifyUserCreated(boolean isGuest) throws Exception {
- String userType =
- isGuest ? UserManager.USER_TYPE_FULL_GUEST : UserManager.USER_TYPE_FULL_SECONDARY;
- verify(mUserManager).preCreateUser(eq(userType));
- }
-
- private void verifyUserRemoved(UserInfo user) throws Exception {
- verify(mUserManager).removeUser(user.id);
- }
-
- private void verifyPostPreCreatedUserSkipped() throws Exception {
- verify(mHelper, never()).runAsync(any());
- }
-
- private void verifyPostPreCreatedUserException() throws Exception {
- verify(mHelper).logPrecreationFailure(anyString(), any());
- }
-
- private void verifyManagePreCreatedUsers() throws Exception {
- verify(mHelper).managePreCreatedUsers();
- }
-
- private void assertParcelValue(List<String> errors, String field, int expected,
- int actual) {
- if (expected != actual) {
- errors.add(String.format("%s mismatch: expected=%d, actual=%d",
- field, expected, actual));
- }
- }
-
- private void assertParcelValueInRange(List<String> errors, String field, long before,
- long actual, long after) {
- if (actual < before || actual> after) {
- errors.add(field + " (" + actual+ ") not in range [" + before + ", " + after + "]");
- }
- }
-
- private void assertMinimumParcelValue(List<String> errors, String field, long min,
- long actual) {
- if (actual < min) {
- errors.add("Minimum " + field + " should be " + min + " (was " + actual + ")");
- }
- }
-
- private void assertNoParcelErrors(List<String> errors) {
- int size = errors.size();
- if (size == 0) return;
-
- StringBuilder msg = new StringBuilder().append(size).append(" errors on parcel: ");
- for (String error : errors) {
- msg.append("\n\t").append(error);
- }
- msg.append('\n');
- throw new IllegalArgumentException(msg.toString());
- }
-
- /**
- * Asserts that no exception was thrown when answering to a mocked {@code ICar} binder call.
- * <p>
- * This method should be called before asserting the expected results of a test, so it makes
- * clear why the test failed when the call was not made as expected.
- */
- private void assertNoICarCallExceptions() throws Exception {
- if (mBinderCallException != null)
- throw mBinderCallException;
-
- }
-}
diff --git a/tests/src/com/android/internal/car/CarServiceHelperServiceTest.java b/tests/src/com/android/internal/car/CarServiceHelperServiceTest.java
new file mode 100644
index 0000000..a2da2a5
--- /dev/null
+++ b/tests/src/com/android/internal/car/CarServiceHelperServiceTest.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2018 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.CAR_SERVICE_INTERFACE;
+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_UNLOCKING;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+
+import android.annotation.UserIdInt;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.watchdoglib.CarWatchdogDaemonHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
+import com.android.server.wm.CarLaunchParamsModifier;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * This class contains unit tests for the {@link CarServiceHelperService}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarServiceHelperServiceTest extends AbstractExtendedMockitoTestCase {
+
+ private static final String TAG = CarServiceHelperServiceTest.class.getSimpleName();
+
+ private CarServiceHelperService mHelperSpy;
+ private CarServiceHelperService mHelper;
+
+ @Mock
+ private Context mMockContext;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private CarLaunchParamsModifier mCarLaunchParamsModifier;
+ @Mock
+ private CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
+ @Mock
+ private IBinder mICarBinder;
+ @Mock
+ private CarServiceProxy mCarServiceProxy;
+
+ /**
+ * Initialize objects and setup testing environment.
+ */
+ @Override
+ protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+ session.spyStatic(ServiceManager.class);
+ }
+
+ @Before
+ public void setTestFixtures() {
+ mHelper = new CarServiceHelperService(
+ mMockContext,
+ mCarLaunchParamsModifier,
+ mCarWatchdogDaemonHelper,
+ mCarServiceProxy);
+ mHelperSpy = spy(mHelper);
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+ }
+
+ @Test
+ public void testCarServiceLaunched() throws Exception {
+ mockRegisterReceiver();
+ mockBindService();
+ mockLoadLibrary();
+
+ mHelperSpy.onStart();
+
+ verifyBindService();
+ }
+
+ @Test
+ public void testHandleCarServiceCrash() throws Exception {
+ mockHandleCarServiceCrash();
+ mockCarServiceException();
+
+ mHelperSpy.handleCarServiceConnection(mICarBinder);
+
+ verify(mHelperSpy).handleCarServiceCrash();
+ }
+
+ @Test
+ public void testOnUserStarting_notifiesICar() throws Exception {
+ int userId = 10;
+
+ mHelper.onUserStarting(newTargetUser(userId));
+
+ verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STARTING,
+ UserHandle.USER_NULL, userId);
+ }
+
+ @Test
+ public void testOnUserStarting_preCreatedDoesntNotifyICar() throws Exception {
+ mHelper.onUserStarting(newTargetUser(10, /* preCreated= */ true));
+
+ verifyICarOnUserLifecycleEventNeverCalled();
+ }
+
+ @Test
+ public void testOnUserSwitching_notifiesICar() throws Exception {
+ int currentUserId = 10;
+ int targetUserId = 11;
+
+ mHelper.onUserSwitching(newTargetUser(currentUserId),
+ newTargetUser(targetUserId));
+
+ verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ currentUserId, targetUserId);
+ }
+
+ @Test
+ public void testOnUserSwitching_preCreatedDoesntNotifyICar() throws Exception {
+ mHelper.onUserSwitching(newTargetUser(10), newTargetUser(11, /* preCreated= */ true));
+
+ verifyICarOnUserLifecycleEventNeverCalled();
+ }
+
+ @Test
+ public void testOnUserUnlocking_notifiesICar() throws Exception {
+ int userId = 10;
+
+ mHelper.onUserUnlocking(newTargetUser(userId));
+
+ verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
+ UserHandle.USER_NULL, userId);
+ }
+
+ @Test
+ public void testOnUserUnlocking_preCreatedDoesntNotifyICar() throws Exception {
+ mHelper.onUserUnlocking(newTargetUser(10, /* preCreated= */ true));
+
+ verifyICarOnUserLifecycleEventNeverCalled();
+ }
+
+ @Test
+ public void testOnUserStopping_notifiesICar() throws Exception {
+ int userId = 10;
+
+ mHelper.onUserStopping(newTargetUser(userId));
+
+ verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STOPPING,
+ UserHandle.USER_NULL, userId);
+ }
+
+ @Test
+ public void testOnUserStopping_preCreatedDoesntNotifyICar() throws Exception {
+ mHelper.onUserStopping(newTargetUser(10, /* preCreated= */ true));
+
+ verifyICarOnUserLifecycleEventNeverCalled();
+ }
+
+ @Test
+ public void testOnUserStopped_notifiesICar() throws Exception {
+ int userId = 10;
+
+ mHelper.onUserStopped(newTargetUser(userId));
+
+ verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STOPPED,
+ UserHandle.USER_NULL, userId);
+ }
+
+ @Test
+ public void testOnUserStopped_preCreatedDoesntNotifyICar() throws Exception {
+ mHelper.onUserStopped(newTargetUser(10, /* preCreated= */ true));
+
+ verifyICarOnUserLifecycleEventNeverCalled();
+ }
+
+ @Test
+ public void testOnBootPhase_thirdPartyCanStart_initBootUser() throws Exception {
+ mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+
+ verifyInitBootUser();
+ }
+
+ private TargetUser newTargetUser(int userId) {
+ return newTargetUser(userId, /* preCreated= */ false);
+ }
+
+ private TargetUser newTargetUser(int userId, boolean preCreated) {
+ TargetUser targetUser = mock(TargetUser.class);
+ when(targetUser.getUserIdentifier()).thenReturn(userId);
+ when(targetUser.isPreCreated()).thenReturn(preCreated);
+ return targetUser;
+ }
+
+ private void verifyBindService() throws Exception {
+ verify(mMockContext).bindServiceAsUser(
+ argThat(intent -> intent.getAction().equals(CAR_SERVICE_INTERFACE)),
+ any(), eq(Context.BIND_AUTO_CREATE), any(), eq(UserHandle.SYSTEM));
+ }
+
+ private void mockRegisterReceiver() {
+ when(mMockContext.registerReceiverForAllUsers(any(), any(), any(), any()))
+ .thenReturn(new Intent());
+ }
+
+ private void mockBindService() {
+ when(mMockContext.bindServiceAsUser(any(), any(),
+ eq(Context.BIND_AUTO_CREATE), any(), eq(UserHandle.SYSTEM)))
+ .thenReturn(true);
+ }
+
+ private void mockLoadLibrary() {
+ doNothing().when(mHelperSpy).loadNativeLibrary();
+ }
+
+ private void mockCarServiceException() throws Exception {
+ when(mICarBinder.transact(anyInt(), notNull(), isNull(), eq(Binder.FLAG_ONEWAY)))
+ .thenThrow(new RemoteException("mock car service Crash"));
+ }
+
+ private void mockHandleCarServiceCrash() throws Exception {
+ doNothing().when(mHelperSpy).handleCarServiceCrash();
+ }
+
+ enum InitialUserInfoAction {
+ DEFAULT,
+ DEFAULT_WITH_LOCALE,
+ DO_NOT_REPLY,
+ DELAYED_REPLY,
+ NON_OK_RESULT_CODE,
+ NULL_BUNDLE,
+ SWITCH_OK,
+ SWITCH_OK_WITH_LOCALE,
+ SWITCH_MISSING_USER_ID
+ }
+
+ private void verifyICarOnUserLifecycleEventCalled(int eventType,
+ @UserIdInt int fromId, @UserIdInt int toId) throws Exception {
+ verify(mCarServiceProxy).sendUserLifecycleEvent(eq(eventType),
+ isTargetUser(fromId), isTargetUser(toId));
+ }
+
+ private static TargetUser isTargetUser(@UserIdInt int userId) {
+ return argThat((user) -> {
+ return user == null || user.getUserIdentifier() == userId;
+ });
+ }
+
+ private void verifyICarOnUserLifecycleEventNeverCalled() throws Exception {
+ verify(mCarServiceProxy, never()).sendUserLifecycleEvent(anyInt(), any(), any());
+ }
+
+ private void verifyInitBootUser() throws Exception {
+ verify(mCarServiceProxy).initBootUser();
+ }
+}
diff --git a/tests/src/com/android/internal/car/CarServiceProxyTest.java b/tests/src/com/android/internal/car/CarServiceProxyTest.java
new file mode 100644
index 0000000..74bc8b0
--- /dev/null
+++ b/tests/src/com/android/internal/car/CarServiceProxyTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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_SWITCHING;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.util.UserTestingHelper.UserInfoBuilder;
+import android.content.pm.UserInfo;
+import android.os.RemoteException;
+
+import com.android.car.internal.ICarSystemServerClient;
+import com.android.internal.os.IResultReceiver;
+import com.android.server.SystemService.TargetUser;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class CarServiceProxyTest extends AbstractExtendedMockitoTestCase {
+
+ @Mock
+ private CarServiceHelperService mCarServiceHelperService;
+ @Mock
+ private ICarSystemServerClient mCarService;
+
+ @Mock
+ private IResultReceiver mFactoryResetCallback1;
+
+ @Mock
+ private IResultReceiver mFactoryResetCallback2;
+
+ private final TargetUser mFromUser = new TargetUser(new UserInfo(101, "fromUser", 0));
+ private final TargetUser mToUser = new TargetUser(new UserInfo(102, "toUser", 0));
+
+ private final UserInfo mRemovedUser1 = new UserInfoBuilder(100).build();
+ private final UserInfo mRemovedUser2 = new UserInfoBuilder(200).build();
+ private final UserInfo mRemovedUser3 = new UserInfoBuilder(300).build();
+
+ private CarServiceProxy mCarServiceProxy;
+
+ @Before
+ public void setUpMocks() {
+ mCarServiceProxy = new CarServiceProxy(mCarServiceHelperService);
+ }
+
+ @Test
+ public void testInitBootUser_CarServiceNotNull() throws RemoteException {
+ connectToCarService();
+
+ callInitBootUser();
+
+ verifyInitBootUserCalled();
+ }
+
+ @Test
+ public void testInitBootUser_CarServiceNull() throws RemoteException {
+ callInitBootUser();
+
+ verifyInitBootUserNeverCalled();
+ }
+
+ @Test
+ public void testSendUserLifecycleEvent_CarServiceNotNull() throws RemoteException {
+ connectToCarService();
+
+ callSendLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+
+ verifySendLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ }
+
+ @Test
+ public void testSendUserLifecycleEvent_CarServiceNull() throws RemoteException {
+ callSendLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+
+ verifySendLifecycleEventNeverCalled();
+ }
+
+ @Test
+ public void testHandleCarServiceConnection() throws RemoteException {
+ callInitBootUser();
+ callSendLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ callOnUserRemoved();
+
+ // Call again to make sure only one call is made after the service is connected
+ callInitBootUser();
+
+ verifyInitBootUserNeverCalled();
+ verifySendLifecycleEventNeverCalled();
+ verifyOnUserRemovedNeverCalled();
+
+ connectToCarService();
+
+ verifyInitBootUserCalled();
+ verifySendLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ verifyOnUserRemovedCalled();
+ }
+
+ @Test
+ public void testOnUserRemoved_CarServiceNotNull() throws RemoteException {
+ connectToCarService();
+
+ callOnUserRemoved();
+
+ verifyOnUserRemovedCalled();
+ }
+
+ @Test
+ public void testOnUserRemoved_CarServiceNull() throws RemoteException {
+ callOnUserRemoved();
+
+ verifyOnUserRemovedNeverCalled();
+ }
+
+ @Test
+ public void testOnFactoryReset_CarServiceNotNull() throws RemoteException {
+ connectToCarService();
+
+ callOnFactoryReset(mFactoryResetCallback1);
+ callOnFactoryReset(mFactoryResetCallback2);
+
+ verifyOnFactoryResetCalled(mFactoryResetCallback1);
+ verifyOnFactoryResetCalled(mFactoryResetCallback2);
+ }
+
+ @Test
+ public void testOnFactoryReset_CarServiceNull() throws RemoteException {
+ callOnFactoryReset(mFactoryResetCallback1);
+ callOnFactoryReset(mFactoryResetCallback2);
+
+ verifyOnFactoryResetNeverCalled();
+ }
+
+ @Test
+ public void testOnFactoryReset_CarServiceNullThenConnected() throws RemoteException {
+ callOnFactoryReset(mFactoryResetCallback1);
+ callOnFactoryReset(mFactoryResetCallback2);
+ connectToCarService();
+
+ verifyOnFactoryResetNotCalled(mFactoryResetCallback1);
+ verifyOnFactoryResetCalled(mFactoryResetCallback2);
+ }
+
+ private void connectToCarService() {
+ mCarServiceProxy.handleCarServiceConnection(mCarService);
+ }
+
+ private void callInitBootUser() {
+ mCarServiceProxy.initBootUser();
+ }
+
+ private void callSendLifecycleEvent(int eventType) {
+ mCarServiceProxy.sendUserLifecycleEvent(eventType, mFromUser, mToUser);
+ }
+
+ private void callOnUserRemoved() {
+ mCarServiceProxy.onUserRemoved(mRemovedUser1);
+ mCarServiceProxy.onUserRemoved(mRemovedUser2);
+ mCarServiceProxy.onUserRemoved(mRemovedUser3);
+ }
+
+ private void callOnFactoryReset(IResultReceiver callback) {
+ mCarServiceProxy.onFactoryReset(callback);
+ }
+
+ private void verifyInitBootUserCalled() throws RemoteException {
+ verify(mCarService).initBootUser();
+ }
+
+ private void verifyInitBootUserNeverCalled() throws RemoteException {
+ verify(mCarService, never()).initBootUser();
+ }
+
+ private void verifySendLifecycleEventCalled(int eventType) throws RemoteException {
+ verify(mCarService).onUserLifecycleEvent(eventType,
+ mFromUser.getUserIdentifier(), mToUser.getUserIdentifier());
+ }
+
+ private void verifySendLifecycleEventNeverCalled() throws RemoteException {
+ verify(mCarService, never()).onUserLifecycleEvent(anyInt(), anyInt(), anyInt());
+ }
+
+ private void verifyOnUserRemovedCalled() throws RemoteException {
+ verify(mCarService).onUserRemoved(mRemovedUser1);
+ verify(mCarService).onUserRemoved(mRemovedUser2);
+ verify(mCarService).onUserRemoved(mRemovedUser3);
+ }
+
+ private void verifyOnUserRemovedNeverCalled() throws RemoteException {
+ verify(mCarService, never()).onUserRemoved(any());
+ }
+
+ private void verifyOnFactoryResetCalled(IResultReceiver callback) throws RemoteException {
+ verify(mCarService).onFactoryReset(callback);
+ }
+
+ private void verifyOnFactoryResetNotCalled(IResultReceiver callback) throws RemoteException {
+ verify(mCarService, never()).onFactoryReset(callback);
+ }
+
+ private void verifyOnFactoryResetNeverCalled() throws RemoteException {
+ verify(mCarService, never()).onFactoryReset(any());
+ }
+}
diff --git a/tests/src/com/android/internal/car/UserMetricsTest.java b/tests/src/com/android/internal/car/UserMetricsTest.java
new file mode 100644
index 0000000..67ed9f9
--- /dev/null
+++ b/tests/src/com/android/internal/car/UserMetricsTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.annotation.UserIdInt;
+import android.os.SystemClock;
+import android.util.SparseArray;
+
+import com.android.internal.car.UserMetrics.UserStartingMetric;
+import com.android.internal.car.UserMetrics.UserStoppingMetric;
+
+import org.junit.Test;
+
+public final class UserMetricsTest {
+
+ private final UserMetrics mUserMetrics = new UserMetrics();
+ @UserIdInt
+ private final int mFromUserId = 10;
+ @UserIdInt
+ private final int mUserId = 11;
+
+ @Test
+ public void testStartingEvent_success() {
+ long timestamp = sendStartingEvent(mUserId);
+
+ assertStartTime(timestamp, mUserId);
+ }
+
+ @Test
+ public void testStartingEvent_multipleCallsDifferentUser() {
+ long timestamp1 = sendStartingEvent(mUserId);
+ int userId = 12;
+ long timestamp2 = sendStartingEvent(userId);
+
+ assertStartTime(timestamp1, mUserId);
+ assertStartTime(timestamp2, userId);
+ }
+
+ @Test
+ public void testStartingEvent_multipleCallsSameUser() {
+ long timestamp1 = sendStartingEvent(mUserId);
+ assertStartTime(timestamp1, mUserId);
+ long timestamp2 = sendStartingEvent(mUserId);
+
+ assertStartTime(timestamp2, mUserId);
+ }
+
+ @Test
+ public void testSwitchingEvent_failure() {
+ sendSwitchingEvent(mFromUserId, mUserId);
+
+ assertNoStartingMetric(mUserId);
+ }
+
+ @Test
+ public void testSwitchingEvent_success() {
+ sendStartingEvent(mUserId);
+ long timestamp = sendSwitchingEvent(mFromUserId, mUserId);
+
+ assertSwitchTime(timestamp, mFromUserId, mUserId);
+ }
+
+ @Test
+ public void testUnlockingEvent_failure() {
+ sendUnlockingEvent(mUserId);
+
+ assertNoStartingMetric(mUserId);
+ }
+
+ @Test
+ public void testUnlockingEvent_success() {
+ sendStartingEvent(mUserId);
+ long timestamp = sendUnlockingEvent(mUserId);
+
+ assertUnlockingTime(timestamp, mUserId);
+ }
+
+ @Test
+ public void testUnlockedEvent_failure() {
+ sendUnlockedEvent(mUserId);
+
+ assertNoStartingMetric(mUserId);
+ }
+
+ @Test
+ public void testUnlockedEvent_success() {
+ long timestamp = sendStartingEvent(mUserId);
+ assertStartTime(timestamp, mUserId);
+ sendUnlockedEvent(mUserId);
+
+ // a successful unlocked event would have removed the metric
+ assertNoStartingMetric(mUserId);
+ }
+
+ @Test
+ public void testStopingEvent_success() {
+ long timestamp = sendStopingEvent(mUserId);
+
+ assertStopingTime(timestamp, mUserId);
+ }
+
+ @Test
+ public void testStopingEvent_multipleCallsDifferentUser() {
+ long timestamp1 = sendStopingEvent(mUserId);
+ int userId = 12;
+ long timestamp2 = sendStopingEvent(userId);
+
+ assertStopingTime(timestamp1, mUserId);
+ assertStopingTime(timestamp2, userId);
+ }
+
+ @Test
+ public void testStopingEvent_multipleCallsSameUser() {
+ long timestamp1 = sendStopingEvent(mUserId);
+ assertStopingTime(timestamp1, mUserId);
+ long timestamp2 = sendStopingEvent(mUserId);
+
+ assertStopingTime(timestamp2, mUserId);
+ }
+
+ @Test
+ public void testStoppedEvent_failure() {
+ sendStoppedEvent(mUserId);
+
+ assertNoStoppingMetric(mUserId);
+ }
+
+ @Test
+ public void testStoppedEvent_success() {
+ long timestamp = sendStopingEvent(mUserId);
+ assertStopingTime(timestamp, mUserId);
+ sendStoppedEvent(mUserId);
+
+ // a successful stopped event would have removed the metric
+ assertNoStoppingMetric(mUserId);
+ }
+
+ private long sendStartingEvent(@UserIdInt int userId) {
+ long timestampMs = SystemClock.elapsedRealtimeNanos();
+ mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, timestampMs, /* fromUserId */ -1,
+ userId);
+ return timestampMs;
+ }
+
+ private long sendSwitchingEvent(@UserIdInt int fromUserId, @UserIdInt int userId) {
+ long timestampMs = SystemClock.elapsedRealtimeNanos();
+ mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, timestampMs, fromUserId, userId);
+ return timestampMs;
+ }
+
+ private long sendUnlockingEvent(@UserIdInt int userId) {
+ long timestampMs = SystemClock.elapsedRealtimeNanos();
+ mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, timestampMs, /* fromUserId */ -1,
+ userId);
+ return timestampMs;
+ }
+
+ private long sendUnlockedEvent(@UserIdInt int userId) {
+ long timestampMs = SystemClock.elapsedRealtimeNanos();
+ mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, timestampMs, /* fromUserId */ -1,
+ userId);
+ return timestampMs;
+ }
+
+ private long sendStopingEvent(@UserIdInt int userId) {
+ long timestampMs = SystemClock.elapsedRealtimeNanos();
+ mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, timestampMs, /* fromUserId */ -1,
+ userId);
+ return timestampMs;
+ }
+
+ private long sendStoppedEvent(@UserIdInt int userId) {
+ long timestampMs = SystemClock.elapsedRealtimeNanos();
+ mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, timestampMs, /* fromUserId */ -1,
+ userId);
+ return timestampMs;
+ }
+
+ private void assertStartTime(long timestamp, @UserIdInt int userId) {
+ SparseArray<UserStartingMetric> startArray = mUserMetrics.getUserStartMetrics();
+ UserStartingMetric metric = startArray.get(userId);
+ assertThat(metric.startTime).isEqualTo(timestamp);
+ }
+
+ private void assertSwitchTime(long timestamp, @UserIdInt int fromUserId,
+ @UserIdInt int userId) {
+ SparseArray<UserStartingMetric> startArray = mUserMetrics.getUserStartMetrics();
+ UserStartingMetric metric = startArray.get(userId);
+ assertThat(metric.switchFromUserId).isEqualTo(fromUserId);
+ assertThat(metric.switchTime).isEqualTo(timestamp);
+ }
+
+ private void assertUnlockingTime(long timestamp, int userId) {
+ SparseArray<UserStartingMetric> startArray = mUserMetrics.getUserStartMetrics();
+ UserStartingMetric metric = startArray.get(userId);
+ assertThat(metric.unlockingTime).isEqualTo(timestamp);
+ }
+
+ private void assertNoStartingMetric(@UserIdInt int userId) {
+ SparseArray<UserStartingMetric> startArray = mUserMetrics.getUserStartMetrics();
+ assertThrows(NullPointerException.class, () -> startArray.get(userId));
+ }
+
+ private void assertStopingTime(long timestamp, @UserIdInt int userId) {
+ SparseArray<UserStoppingMetric> stopArray = mUserMetrics.getUserStopMetrics();
+ UserStoppingMetric metric = stopArray.get(userId);
+ assertThat(metric.stopTime).isEqualTo(timestamp);
+ }
+
+ private void assertNoStoppingMetric(@UserIdInt int userId) {
+ SparseArray<UserStoppingMetric> stopArray = mUserMetrics.getUserStopMetrics();
+ assertThrows(NullPointerException.class, () -> stopArray.get(userId));
+ }
+}
diff --git a/tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java b/tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java
index ae6fa06..d641cff 100644
--- a/tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java
+++ b/tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java
@@ -29,6 +29,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import android.annotation.UserIdInt;
@@ -40,7 +42,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.UserHandle;
import android.view.Display;
@@ -48,7 +49,7 @@ import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.server.AttributeCache;
+import com.android.internal.policy.AttributeCache;
import com.android.server.LocalServices;
import com.android.server.display.color.ColorDisplayService;
@@ -62,6 +63,11 @@ import org.mockito.quality.Strictness;
import java.util.Arrays;
+/**
+ * Tests for {@link CarLaunchParamsModifier}
+ * Build/Install/Run:
+ * atest CarServicesTest:CarLaunchParamsModifierTest
+ */
@RunWith(AndroidJUnit4.class)
public class CarLaunchParamsModifierTest {
private static final int PASSENGER_DISPLAY_ID_10 = 10;
@@ -79,7 +85,7 @@ public class CarLaunchParamsModifierTest {
@Mock
private ActivityTaskManagerService mActivityTaskManagerService;
@Mock
- private ActivityStackSupervisor mActivityStackSupervisor;
+ private ActivityTaskSupervisor mActivityTaskSupervisor;
@Mock
private RecentTasks mRecentTasks;
@Mock
@@ -154,8 +160,8 @@ public class CarLaunchParamsModifierTest {
.startMocking();
when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
doReturn(mActivityTaskManagerService).when(() -> ActivityTaskManager.getService());
- mActivityTaskManagerService.mStackSupervisor = mActivityStackSupervisor;
- when(mActivityStackSupervisor.getLaunchParamsController()).thenReturn(
+ mActivityTaskManagerService.mTaskSupervisor = mActivityTaskSupervisor;
+ when(mActivityTaskSupervisor.getLaunchParamsController()).thenReturn(
mLaunchParamsController);
mActivityTaskManagerService.mRootWindowContainer = mRootWindowContainer;
mActivityTaskManagerService.mWindowManager = mWindowManagerService;
@@ -193,7 +199,8 @@ public class CarLaunchParamsModifierTest {
mCurrentParams.mPreferredTaskDisplayArea = mModifier
.getDefaultTaskDisplayAreaOnDisplay(display.getDisplayId());
assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
- mActivityRecordSource, mActivityOptions, 0, mCurrentParams, mOutParams))
+ mActivityRecordSource, mActivityOptions, null /* request */, 0, mCurrentParams,
+ mOutParams))
.isEqualTo(LaunchParamsController.LaunchParamsModifier.RESULT_SKIP);
}
@@ -207,7 +214,8 @@ public class CarLaunchParamsModifierTest {
.getDefaultTaskDisplayAreaOnDisplay(displayAssigned.getDisplayId());
mCurrentParams.mPreferredTaskDisplayArea = requestedTaskDisplayArea;
assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
- mActivityRecordSource, mActivityOptions, 0, mCurrentParams, mOutParams))
+ mActivityRecordSource, mActivityOptions, null /* request */, 0, mCurrentParams,
+ mOutParams))
.isEqualTo(LaunchParamsController.LaunchParamsModifier.RESULT_DONE);
assertThat(mOutParams.mPreferredTaskDisplayArea).isEqualTo(assignedTaskDisplayArea);
}
@@ -219,7 +227,8 @@ public class CarLaunchParamsModifierTest {
}
mCurrentParams.mPreferredTaskDisplayArea = null;
assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
- mActivityRecordSource, mActivityOptions, 0, mCurrentParams, mOutParams))
+ mActivityRecordSource, mActivityOptions, null /* request */, 0, mCurrentParams,
+ mOutParams))
.isEqualTo(LaunchParamsController.LaunchParamsModifier.RESULT_DONE);
assertThat(mOutParams.mPreferredTaskDisplayArea).isEqualTo(expectedDisplayArea);
}
@@ -228,7 +237,8 @@ public class CarLaunchParamsModifierTest {
mTask.mUserId = userId;
mCurrentParams.mPreferredTaskDisplayArea = null;
assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
- mActivityRecordSource, mActivityOptions, 0, mCurrentParams, mOutParams))
+ mActivityRecordSource, mActivityOptions, null /* request */, 0, mCurrentParams,
+ mOutParams))
.isEqualTo(LaunchParamsController.LaunchParamsModifier.RESULT_SKIP);
assertThat(mOutParams.mPreferredTaskDisplayArea).isNull();
}
@@ -241,12 +251,11 @@ public class CarLaunchParamsModifierTest {
info.applicationInfo.packageName = packageName;
Intent intent = new Intent();
intent.setClassName(packageName, className);
- return new ActivityRecord(mActivityTaskManagerService, /* caller */null,
- /* launchedFromPid */ 0, /* launchedFromUid */ 0, /* launchedFromPackage */ null,
- /* launchedFromFeature */ null, intent, /* resolvedType */ null, info,
- new Configuration(), /* resultTo */ null, /* resultWho */ null, /* reqCode */ 0,
- /*componentSpecified*/ false, /* rootVoiceInteraction */ false,
- mActivityStackSupervisor, /* options */ null, /* sourceRecord */ null);
+
+ return new ActivityRecord.Builder(mActivityTaskManagerService)
+ .setIntent(intent)
+ .setActivityInfo(info)
+ .build();
}
@Test
@@ -300,7 +309,7 @@ public class CarLaunchParamsModifierTest {
mDisplay11ForPassenger.getDisplayId()});
final int passengerUserId = 100;
- mModifier.setDisplayWhitelistForUser(passengerUserId,
+ mModifier.setDisplayAllowListForUser(passengerUserId,
new int[]{mDisplay10ForPassenger.getDisplayId()});
assertDisplayIsAllowed(passengerUserId, mDisplay10ForPassenger);
@@ -312,13 +321,13 @@ public class CarLaunchParamsModifierTest {
mDisplay11ForPassenger.getDisplayId()});
int passengerUserId1 = 100;
- mModifier.setDisplayWhitelistForUser(passengerUserId1,
+ mModifier.setDisplayAllowListForUser(passengerUserId1,
new int[]{mDisplay11ForPassenger.getDisplayId()});
assertDisplayIsAllowed(passengerUserId1, mDisplay11ForPassenger);
int passengerUserId2 = 101;
- mModifier.setDisplayWhitelistForUser(passengerUserId2,
+ mModifier.setDisplayAllowListForUser(passengerUserId2,
new int[]{mDisplay11ForPassenger.getDisplayId()});
assertDisplayIsAllowed(passengerUserId2, mDisplay11ForPassenger);
@@ -332,7 +341,7 @@ public class CarLaunchParamsModifierTest {
mDisplay11ForPassenger.getDisplayId()});
final int passengerUserId = 100;
- mModifier.setDisplayWhitelistForUser(
+ mModifier.setDisplayAllowListForUser(
passengerUserId, new int[]{mDisplay10ForPassenger.getDisplayId()});
assertDisplayIsReassigned(passengerUserId, mDisplay0ForDriver, mDisplay10ForPassenger);
@@ -345,7 +354,7 @@ public class CarLaunchParamsModifierTest {
mDisplay11ForPassenger.getDisplayId()});
int passengerUserId = 100;
- mModifier.setDisplayWhitelistForUser(
+ mModifier.setDisplayAllowListForUser(
passengerUserId, new int[]{mDisplay11ForPassenger.getDisplayId()});
assertDisplayIsAllowed(passengerUserId, mDisplay11ForPassenger);
@@ -361,11 +370,11 @@ public class CarLaunchParamsModifierTest {
mDisplay11ForPassenger.getDisplayId()});
int passengerUserId = 100;
- mModifier.setDisplayWhitelistForUser(
+ mModifier.setDisplayAllowListForUser(
passengerUserId, new int[]{mDisplay11ForPassenger.getDisplayId()});
assertDisplayIsAllowed(passengerUserId, mDisplay11ForPassenger);
- mModifier.setDisplayWhitelistForUser(
+ mModifier.setDisplayAllowListForUser(
UserHandle.USER_SYSTEM, new int[]{mDisplay11ForPassenger.getDisplayId()});
assertDisplayIsReassigned(passengerUserId, mDisplay0ForDriver, mDisplay10ForPassenger);
@@ -378,7 +387,7 @@ public class CarLaunchParamsModifierTest {
mDisplay11ForPassenger.getDisplayId()});
final int passengerUserId = 100;
- mModifier.setDisplayWhitelistForUser(passengerUserId,
+ mModifier.setDisplayAllowListForUser(passengerUserId,
new int[]{mDisplay10ForPassenger.getDisplayId(),
mDisplay11ForPassenger.getDisplayId()});
@@ -397,7 +406,7 @@ public class CarLaunchParamsModifierTest {
mDisplay11ForPassenger.getDisplayId()});
final int passengerUserId = 100;
- mModifier.setDisplayWhitelistForUser(passengerUserId,
+ mModifier.setDisplayAllowListForUser(passengerUserId,
new int[]{mDisplay10ForPassenger.getDisplayId(),
mDisplay11ForPassenger.getDisplayId()});
@@ -416,7 +425,7 @@ public class CarLaunchParamsModifierTest {
mDisplay11ForPassenger.getDisplayId()});
final int passengerUserId = 100;
- mModifier.setDisplayWhitelistForUser(passengerUserId,
+ mModifier.setDisplayAllowListForUser(passengerUserId,
new int[]{mDisplay10ForPassenger.getDisplayId(),
mDisplay10ForPassenger.getDisplayId()});
@@ -431,7 +440,7 @@ public class CarLaunchParamsModifierTest {
final int wasDriver = 10;
final int wasPassenger = 11;
mModifier.handleCurrentUserSwitching(wasDriver);
- mModifier.setDisplayWhitelistForUser(wasPassenger,
+ mModifier.setDisplayAllowListForUser(wasPassenger,
new int[]{mDisplay10ForPassenger.getDisplayId(),
mDisplay11ForPassenger.getDisplayId()});
@@ -445,7 +454,7 @@ public class CarLaunchParamsModifierTest {
final int driver = wasPassenger;
final int passenger = wasDriver;
mModifier.handleCurrentUserSwitching(driver);
- mModifier.setDisplayWhitelistForUser(passenger,
+ mModifier.setDisplayAllowListForUser(passenger,
new int[]{mDisplay10ForPassenger.getDisplayId(),
mDisplay11ForPassenger.getDisplayId()});
@@ -472,7 +481,7 @@ public class CarLaunchParamsModifierTest {
public void testPreferSourceForPassenger() {
mModifier.setPassengerDisplays(new int[]{PASSENGER_DISPLAY_ID_10, PASSENGER_DISPLAY_ID_11});
int passengerUserId = 100;
- mModifier.setDisplayWhitelistForUser(passengerUserId,
+ mModifier.setDisplayAllowListForUser(passengerUserId,
new int[]{PASSENGER_DISPLAY_ID_10, PASSENGER_DISPLAY_ID_11});
when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea11ForPassenger);
@@ -505,7 +514,7 @@ public class CarLaunchParamsModifierTest {
@Test
public void testPreferSourceDoNotAssignDisplayForNonSpecifiedActivity() {
when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea0ForDriver);
- mActivityRecordActivity = buildActivityRecord("dummyPackage", "dummyActivity");
+ mActivityRecordActivity = buildActivityRecord("placeholderPackage", "placeholderActivity");
mModifier.setSourcePreferredComponents(true,
Arrays.asList(new ComponentName("testPackage", "testActivity")));
@@ -546,4 +555,113 @@ public class CarLaunchParamsModifierTest {
assertDisplayIsAssigned(UserHandle.USER_SYSTEM, mDisplayArea0ForDriver);
}
+
+ @Test
+ public void testSourceDisplayFromProcessDisplayIfAvailable() {
+ int userId = 10;
+ String processName = "processName";
+ int processUid = 11;
+ when(mActivityRecordActivity.getProcessName())
+ .thenReturn(processName);
+ when(mActivityRecordActivity.getUid())
+ .thenReturn(processUid);
+ mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(),
+ mDisplay10ForPassenger.getDisplayId()});
+ mModifier.setDisplayAllowListForUser(userId,
+ new int[]{mDisplay10ForPassenger.getDisplayId()});
+ WindowProcessController controller = mock(WindowProcessController.class);
+ when(mActivityTaskManagerService.getProcessController(processName, processUid))
+ .thenReturn(controller);
+ when(controller.getTopActivityDisplayArea())
+ .thenReturn(mDisplayArea10ForPassenger);
+ mCurrentParams.mPreferredTaskDisplayArea = null;
+ mTask.mUserId = userId;
+
+ assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
+ mActivityRecordSource, null, null /* request */, 0, mCurrentParams, mOutParams))
+ .isEqualTo(TaskLaunchParamsModifier.RESULT_DONE);
+ assertThat(mOutParams.mPreferredTaskDisplayArea)
+ .isEqualTo(mDisplayArea10ForPassenger);
+ }
+
+ @Test
+ public void testSourceDisplayFromLaunchingDisplayIfAvailable() {
+ int userId = 10;
+ int launchedFromPid = 1324;
+ int launchedFromUid = 325;
+ when(mActivityRecordActivity.getLaunchedFromPid())
+ .thenReturn(launchedFromPid);
+ when(mActivityRecordActivity.getLaunchedFromUid())
+ .thenReturn(launchedFromUid);
+ mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(),
+ mDisplay10ForPassenger.getDisplayId()});
+ mModifier.setDisplayAllowListForUser(userId,
+ new int[]{mDisplay10ForPassenger.getDisplayId()});
+ WindowProcessController controller = mock(WindowProcessController.class);
+ when(mActivityTaskManagerService.getProcessController(launchedFromPid, launchedFromUid))
+ .thenReturn(controller);
+ when(controller.getTopActivityDisplayArea())
+ .thenReturn(mDisplayArea10ForPassenger);
+ mCurrentParams.mPreferredTaskDisplayArea = null;
+ mTask.mUserId = 10;
+
+ assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
+ mActivityRecordSource, null, null /* request */, 0, mCurrentParams, mOutParams))
+ .isEqualTo(TaskLaunchParamsModifier.RESULT_DONE);
+ assertThat(mOutParams.mPreferredTaskDisplayArea)
+ .isEqualTo(mDisplayArea10ForPassenger);
+ }
+
+ @Test
+ public void testSourceDisplayFromCallingDisplayIfAvailable() {
+ int userId = 10;
+ ActivityStarter.Request request = fakeRequest();
+ mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(),
+ mDisplay10ForPassenger.getDisplayId()});
+ mModifier.setDisplayAllowListForUser(userId,
+ new int[]{mDisplay10ForPassenger.getDisplayId()});
+ WindowProcessController controller = mock(WindowProcessController.class);
+ when(mActivityTaskManagerService.getProcessController(request.realCallingPid,
+ request.realCallingUid))
+ .thenReturn(controller);
+ when(controller.getTopActivityDisplayArea())
+ .thenReturn(mDisplayArea10ForPassenger);
+ mCurrentParams.mPreferredTaskDisplayArea = null;
+ mTask.mUserId = userId;
+
+ assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
+ mActivityRecordSource, null, request, 0, mCurrentParams, mOutParams))
+ .isEqualTo(TaskLaunchParamsModifier.RESULT_DONE);
+ assertThat(mOutParams.mPreferredTaskDisplayArea)
+ .isEqualTo(mDisplayArea10ForPassenger);
+ }
+
+ @Test
+ public void testSourceDisplayIgnoredIfNotInAllowList() {
+ ActivityStarter.Request request = fakeRequest();
+ mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(),
+ mDisplay10ForPassenger.getDisplayId()});
+ WindowProcessController controller = mock(WindowProcessController.class);
+ when(mActivityTaskManagerService.getProcessController(anyString(), anyInt()))
+ .thenReturn(controller);
+ when(mActivityTaskManagerService.getProcessController(anyInt(), anyInt()))
+ .thenReturn(controller);
+ when(controller.getTopActivityDisplayArea())
+ .thenReturn(mDisplayArea10ForPassenger);
+ mCurrentParams.mPreferredTaskDisplayArea = null;
+ mTask.mUserId = 10;
+
+ assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
+ mActivityRecordSource, null, request, 0, mCurrentParams, mOutParams))
+ .isEqualTo(TaskLaunchParamsModifier.RESULT_DONE);
+ assertThat(mOutParams.mPreferredTaskDisplayArea)
+ .isEqualTo(mDisplayArea11ForPassenger);
+ }
+
+ private ActivityStarter.Request fakeRequest() {
+ ActivityStarter.Request request = new ActivityStarter.Request();
+ request.realCallingPid = 1324;
+ request.realCallingUid = 235;
+ return request;
+ }
}