diff options
author | Xin Li <delphij@google.com> | 2021-10-06 22:53:37 +0000 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2021-10-06 22:53:37 +0000 |
commit | 4ff5a633fe1257922a6171fce69e0be5d8c668c6 (patch) | |
tree | 25b75eb6f8866a57e23f4b4eadc8121e71e9fd60 | |
parent | af2f99eec3ae37659da0d3edc70e37c08a9ccc5a (diff) | |
parent | 3d90e911fd5eabc54fe998a4a36dd8c80033521e (diff) | |
download | services-android-s-v2-preview-1.tar.gz |
Merge Android 12android-s-v2-preview-2android-s-v2-preview-1android-s-v2-beta-2android-s-v2-preview-1
Bug: 202323961
Merged-In: I7452a5f39d9b55361153edf7919c59de59c8656c
Change-Id: I5d02df6aa32f956881f6b57ae094000827a2c532
-rw-r--r-- | Android.bp | 10 | ||||
-rw-r--r-- | src/com/android/internal/car/CarDevicePolicySafetyChecker.java | 131 | ||||
-rw-r--r-- | src/com/android/internal/car/CarServiceHelperService.java | 1054 | ||||
-rw-r--r-- | src/com/android/internal/car/CarServiceProxy.java | 512 | ||||
-rw-r--r-- | src/com/android/internal/car/ExternalConstants.java | 62 | ||||
-rw-r--r-- | src/com/android/internal/car/ICarServiceHelper.aidl | 48 | ||||
-rw-r--r-- | src/com/android/internal/car/UserMetrics.java | 355 | ||||
-rw-r--r-- | src/com/android/server/wm/CarLaunchParamsModifier.java | 147 | ||||
-rw-r--r-- | tests/Android.mk | 7 | ||||
-rw-r--r-- | tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java | 150 | ||||
-rw-r--r-- | tests/src/com/android/internal/car/CarHelperServiceTest.java | 1232 | ||||
-rw-r--r-- | tests/src/com/android/internal/car/CarServiceHelperServiceTest.java | 292 | ||||
-rw-r--r-- | tests/src/com/android/internal/car/CarServiceProxyTest.java | 224 | ||||
-rw-r--r-- | tests/src/com/android/internal/car/UserMetricsTest.java | 239 | ||||
-rw-r--r-- | tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java | 176 |
15 files changed, 2423 insertions, 2216 deletions
@@ -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; + } } |