Merge "Implement User Switch using User Hal for Car user service" into rvc-dev
diff --git a/OWNERS b/OWNERS
index e728889..47f451a 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,5 +1,5 @@
# Each subdirectory should have its OWNERS.
-# Owned by Android Automotive Embedded (go/aae).
+# Owned by Android Automotive Embedded team.
felipeal@google.com
gurunagarajan@google.com
keunyoung@google.com
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 4a76b5c..f87c869 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -69,6 +69,8 @@
PRODUCT_DEVICE := generic
PRODUCT_NAME := generic_car_no_telephony
+PRODUCT_IS_AUTOMOTIVE := true
+
PRODUCT_PROPERTY_OVERRIDES := \
ro.config.ringtone=Girtab.ogg \
ro.config.notification_sound=Tethys.ogg \
diff --git a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java
index afbe50c..6d4707d 100644
--- a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java
+++ b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java
@@ -45,6 +45,7 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.input.InputManager;
import android.os.Handler;
@@ -135,9 +136,6 @@
};
@Mock
- private Context mContext;
-
- @Mock
private InputManager mInputManager;
@Mock
@@ -152,6 +150,9 @@
@Rule
public final ServiceTestRule serviceRule = new ServiceTestRule();
+ private final Context mSpyContext = spy(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
private DriverDistractionExperimentalFeatureService mService;
private CarDriverDistractionManager mManager;
private FakeTimeSource mTimeSource;
@@ -173,10 +174,13 @@
mQueuedRunnables.add(((Runnable) i.getArguments()[0]));
return true;
});
- mService = new DriverDistractionExperimentalFeatureService(mContext, mTimeSource,
- mExpiredAwarenessTimer, Looper.myLooper(), mHandler);
- mManager = new CarDriverDistractionManager(Car.createCar(mContext), mService);
mDistractionEventHistory = new ArrayList<>();
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mSpyContext).checkCallingOrSelfPermission(any());
+ mService = new DriverDistractionExperimentalFeatureService(mSpyContext, mTimeSource,
+ mExpiredAwarenessTimer, Looper.myLooper(), mHandler);
+ // Car must not be created with a mock context (otherwise CarService may crash)
+ mManager = new CarDriverDistractionManager(Car.createCar(mSpyContext), mService);
}
@After
diff --git a/service/src/com/android/car/CarMediaService.java b/service/src/com/android/car/CarMediaService.java
index 9447096..3b111d8 100644
--- a/service/src/com/android/car/CarMediaService.java
+++ b/service/src/com/android/car/CarMediaService.java
@@ -26,6 +26,8 @@
import android.car.media.CarMediaManager.MediaSourceMode;
import android.car.media.ICarMedia;
import android.car.media.ICarMediaSourceListener;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -171,21 +173,13 @@
}
};
- private final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
-
- @Override
- public void onSwitchUser(int userId) {
- if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) {
- Log.d(CarLog.TAG_MEDIA, "Switched to user " + userId);
- }
- maybeInitUser(userId);
+ private final UserLifecycleListener mUserLifecycleListener = event -> {
+ if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) {
+ Log.d(CarLog.TAG_MEDIA, "CarMediaService.onEvent(" + event + ")");
}
-
- @Override
- public void onUserLockChanged(int userId, boolean unlocked) {
- // Do Nothing
+ if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
+ maybeInitUser(event.getUserHandle().getIdentifier());
}
-
};
public CarMediaService(Context context, CarUserService userService) {
@@ -207,7 +201,7 @@
mPackageUpdateFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
mPackageUpdateFilter.addDataScheme("package");
mUserService = userService;
- mUserService.addUserCallback(mUserCallback);
+ mUserService.removeUserLifecycleListener(mUserLifecycleListener);
mPlayOnMediaSourceChangedConfig =
mContext.getResources().getInteger(R.integer.config_mediaSourceChangedAutoplay);
@@ -301,7 +295,7 @@
@Override
public void release() {
mMediaSessionUpdater.unregisterCallbacks();
- mUserService.removeUserCallback(mUserCallback);
+ mUserService.removeUserLifecycleListener(mUserLifecycleListener);
}
@Override
diff --git a/service/src/com/android/car/CarOccupantZoneService.java b/service/src/com/android/car/CarOccupantZoneService.java
index dae1667..8050a64 100644
--- a/service/src/com/android/car/CarOccupantZoneService.java
+++ b/service/src/com/android/car/CarOccupantZoneService.java
@@ -29,6 +29,8 @@
import android.car.ICarOccupantZoneCallback;
import android.car.VehicleAreaSeat;
import android.car.media.CarAudioManager;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -157,14 +159,11 @@
private final HashMap<Integer, OccupantConfig> mActiveOccupantConfigs = new HashMap<>();
@VisibleForTesting
- final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
- @Override
- public void onUserLockChanged(@UserIdInt int userId, boolean unlocked) {
- // nothing to do
+ final UserLifecycleListener mUserLifecycleListener = event -> {
+ if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) {
+ Log.d(CarLog.TAG_MEDIA, "onEvent(" + event + ")");
}
-
- @Override
- public void onSwitchUser(@UserIdInt int userId) {
+ if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
handleUserChange();
}
};
@@ -185,21 +184,21 @@
@VisibleForTesting
final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) {
- handleDisplayChange();
- }
+ @Override
+ public void onDisplayAdded(int displayId) {
+ handleDisplayChange();
+ }
- @Override
- public void onDisplayRemoved(int displayId) {
- handleDisplayChange();
- }
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ handleDisplayChange();
+ }
- @Override
- public void onDisplayChanged(int displayId) {
- // nothing to do
- }
- };
+ @Override
+ public void onDisplayChanged(int displayId) {
+ // nothing to do
+ }
+ };
private final RemoteCallbackList<ICarOccupantZoneCallback> mClientCallbacks =
new RemoteCallbackList<>();
@@ -234,7 +233,7 @@
handleUserChangesLocked();
}
CarUserService userService = CarLocalServices.getService(CarUserService.class);
- userService.addUserCallback(mUserCallback);
+ userService.addUserLifecycleListener(mUserLifecycleListener);
userService.addPassengerCallback(mPassengerCallback);
mDisplayManager.registerDisplayListener(mDisplayListener,
new Handler(Looper.getMainLooper()));
@@ -298,7 +297,7 @@
if (getDisplayForOccupant(ozi.zoneId,
CarOccupantZoneManager.DISPLAY_TYPE_MAIN) != Display.INVALID_DISPLAY
&& ozi.occupantType != CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
- return true;
+ return true;
}
}
return false;
@@ -311,7 +310,7 @@
public void release() {
mDisplayManager.unregisterDisplayListener(mDisplayListener);
CarUserService userService = CarLocalServices.getService(CarUserService.class);
- userService.removeUserCallback(mUserCallback);
+ userService.removeUserLifecycleListener(mUserLifecycleListener);
userService.removePassengerCallback(mPassengerCallback);
synchronized (mLock) {
mOccupantsConfig.clear();
@@ -483,7 +482,7 @@
continue;
}
DisplayConfig config =
- mDisplayConfigs.get(Byte.toUnsignedInt(portAddress));
+ mDisplayConfigs.get(Byte.toUnsignedInt(portAddress));
return config;
}
return null;
diff --git a/service/src/com/android/car/PerUserCarServiceHelper.java b/service/src/com/android/car/PerUserCarServiceHelper.java
index a4148aa..cbe701e 100644
--- a/service/src/com/android/car/PerUserCarServiceHelper.java
+++ b/service/src/com/android/car/PerUserCarServiceHelper.java
@@ -17,6 +17,8 @@
package com.android.car;
import android.car.IPerUserCarService;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -55,7 +57,7 @@
mContext = context;
mServiceCallbacks = new ArrayList<>();
mUserService = userService;
- mUserService.addUserCallback(mUserCallback);
+ mUserService.addUserLifecycleListener(mUserLifecycleListener);
}
@Override
@@ -69,20 +71,17 @@
public void release() {
synchronized (mServiceBindLock) {
unbindFromPerUserCarService();
- mUserService.removeUserCallback(mUserCallback);
+ mUserService.removeUserLifecycleListener(mUserLifecycleListener);
}
}
- private final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
-
- @Override
- public void onUserLockChanged(int userId, boolean unlocked) {
- // Do Nothing
+ private final UserLifecycleListener mUserLifecycleListener = event -> {
+ if (DBG) {
+ Log.d(TAG, "onEvent(" + event + ")");
}
-
- @Override
- public void onSwitchUser(int userId) {
+ if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
List<ServiceCallback> callbacks;
+ int userId = event.getUserHandle().getIdentifier();
if (DBG) {
Log.d(TAG, "User Switch Happened. New User" + userId);
}
diff --git a/service/src/com/android/car/am/FixedActivityService.java b/service/src/com/android/car/am/FixedActivityService.java
index abb1eca..29e06fc 100644
--- a/service/src/com/android/car/am/FixedActivityService.java
+++ b/service/src/com/android/car/am/FixedActivityService.java
@@ -31,6 +31,8 @@
import android.app.Presentation;
import android.app.TaskStackListener;
import android.car.hardware.power.CarPowerManager;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -130,16 +132,13 @@
private final UserManager mUm;
- private final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
- @Override
- public void onUserLockChanged(@UserIdInt int userId, boolean unlocked) {
- // Nothing to do
+ private final UserLifecycleListener mUserLifecycleListener = event -> {
+ if (Log.isLoggable(TAG_AM, Log.DEBUG)) {
+ Log.d(TAG_AM, "onEvent(" + event + ")");
}
-
- @Override
- public void onSwitchUser(@UserIdInt int userId) {
- synchronized (mLock) {
- mRunningActivities.clear();
+ if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
+ synchronized (FixedActivityService.this.mLock) {
+ FixedActivityService.this.mRunningActivities.clear();
}
}
};
@@ -309,7 +308,7 @@
mCarPowerManager = carPowerManager;
}
CarUserService userService = CarLocalServices.getService(CarUserService.class);
- userService.addUserCallback(mUserCallback);
+ userService.addUserLifecycleListener(mUserLifecycleListener);
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
@@ -345,7 +344,7 @@
}
mHandlerThread.getThreadHandler().removeCallbacks(mActivityCheckRunnable);
CarUserService userService = CarLocalServices.getService(CarUserService.class);
- userService.removeUserCallback(mUserCallback);
+ userService.removeUserLifecycleListener(mUserLifecycleListener);
try {
mAm.unregisterTaskStackListener(mTaskStackListener);
mAm.unregisterProcessObserver(mProcessObserver);
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
index 8d7fac6..595ada5 100644
--- a/service/src/com/android/car/hal/UserHalService.java
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -134,6 +134,10 @@
mHandler.sendMessage(obtainMessage(
UserHalService::handleOnInitialUserInfoResponse, this, value));
break;
+ case SWITCH_USER:
+ mHandler.sendMessage(obtainMessage(
+ UserHalService::handleOnSwicthUserResponse, this, value));
+ break;
default:
Slog.w(TAG, "received unsupported event from HAL: " + value);
}
@@ -188,7 +192,7 @@
* {@link android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType}).
* @param timeoutMs how long to wait (in ms) for the property change event.
* @param usersInfo current state of Android users.
- * @param callback callback to handle the response.
+ * @param callback to handle the response.
*
* @throws IllegalStateException if the HAL does not support user management (callers should
* call {@link #isSupported()} first to avoid this exception).
@@ -209,17 +213,9 @@
checkSupportedLocked();
if (hasPendingRequestLocked(InitialUserInfoResponse.class, callback)) return;
requestId = mNextRequestId++;
- // TODO(b/150413515): use helper method to convert request to prop value
propRequest.value.int32Values.add(requestId);
propRequest.value.int32Values.add(requestType);
- propRequest.value.int32Values.add(usersInfo.currentUser.userId);
- propRequest.value.int32Values.add(usersInfo.currentUser.flags);
- propRequest.value.int32Values.add(usersInfo.numberUsers);
- for (int i = 0; i < usersInfo.numberUsers; i++) {
- UserInfo userInfo = usersInfo.existingUsers.get(i);
- propRequest.value.int32Values.add(userInfo.userId);
- propRequest.value.int32Values.add(userInfo.flags);
- }
+ addUsersInfo(propRequest, usersInfo);
setTimestamp(propRequest);
addPendingRequestLocked(requestId, InitialUserInfoResponse.class, callback);
}
@@ -238,18 +234,67 @@
}
/**
- * TODO(b/150409110): javadoc it :-)
+ * Calls HAL to asynchronously switch user.
+ *
+ * @param targetInfo target user for user switching
+ * @param timeoutMs how long to wait (in ms) for the property change event.
+ * @param usersInfo current state of Android users.
+ * @param callback to handle the response.
+ *
+ * @throws IllegalStateException if the HAL does not support user management (callers should
+ * call {@link #isSupported()} first to avoid this exception).
*/
public void switchUser(@NonNull UserInfo targetInfo, int timeoutMs,
@NonNull UsersInfo usersInfo, @NonNull HalCallback<SwitchUserResponse> callback) {
if (DBG) Log.d(TAG, "switchUser(" + targetInfo + ")");
+ Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
+ Objects.requireNonNull(usersInfo);
+ // TODO(b/150413515): use helper method to convert request to prop value and check usersInfo
+ // is valid
+ Objects.requireNonNull(callback);
- // TODO(b/150409110): implement
- SwitchUserResponse response = new SwitchUserResponse();
- response.messageType = SwitchUserMessageType.VEHICLE_RESPONSE;
- response.status = SwitchUserStatus.SUCCESS;
- response.requestId = mNextRequestId++;
- callback.onResponse(HalCallback.STATUS_OK, response);
+ VehiclePropValue propRequest = new VehiclePropValue();
+ propRequest.prop = SWITCH_USER;
+ int requestId;
+ synchronized (mLock) {
+ checkSupportedLocked();
+ if (hasPendingRequestLocked(SwitchUserResponse.class, callback)) return;
+ requestId = mNextRequestId++;
+ // TODO(b/150413515): use helper method to convert request to prop value
+ propRequest.value.int32Values.add(requestId);
+ propRequest.value.int32Values.add(SwitchUserMessageType.ANDROID_SWITCH);
+ propRequest.value.int32Values.add(targetInfo.userId);
+ propRequest.value.int32Values.add(targetInfo.flags);
+ addUsersInfo(propRequest, usersInfo);
+ setTimestamp(propRequest);
+ addPendingRequestLocked(requestId, SwitchUserResponse.class, callback);
+ }
+
+ mHandler.sendMessageDelayed(
+ obtainMessage(UserHalService::handleCheckIfRequestTimedOut, this, requestId)
+ .setWhat(requestId),
+ timeoutMs);
+
+ try {
+ if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
+ mHal.set(propRequest);
+ } catch (ServiceSpecificException e) {
+ handleRemovePendingRequest(requestId);
+ Log.w(TAG, "Failed to set ANDROID SWITCH", e);
+ callback.onResponse(HalCallback.STATUS_HAL_SET_TIMEOUT, null);
+ }
+ }
+
+ private static void addUsersInfo(VehiclePropValue propRequest, @NonNull UsersInfo usersInfo) {
+ // TODO(b/150419600) it should be moved to UserHalHelper and tested
+ propRequest.value.int32Values.add(usersInfo.currentUser.userId);
+ propRequest.value.int32Values.add(usersInfo.currentUser.flags);
+ propRequest.value.int32Values.add(usersInfo.numberUsers);
+ for (int i = 0; i < usersInfo.numberUsers; i++) {
+ UserInfo userInfo = usersInfo.existingUsers.get(i);
+ propRequest.value.int32Values.add(userInfo.userId);
+ propRequest.value.int32Values.add(userInfo.flags);
+ }
}
@GuardedBy("mLock")
@@ -345,6 +390,36 @@
callback.onResponse(HalCallback.STATUS_OK, response);
}
+ private void handleOnSwicthUserResponse(VehiclePropValue value) {
+ int requestId = value.value.int32Values.get(0);
+ HalCallback<SwitchUserResponse> callback =
+ handleGetPendingCallback(requestId, SwitchUserResponse.class);
+ if (callback == null) {
+ Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
+ return;
+ }
+ handleRemovePendingRequest(requestId);
+ SwitchUserResponse response = new SwitchUserResponse();
+ response.requestId = requestId;
+ response.messageType = value.value.int32Values.get(1);
+ if (response.messageType != SwitchUserMessageType.VEHICLE_RESPONSE) {
+ Log.e(TAG, "invalid message type (" + response.messageType + ") from HAL: " + value);
+ callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
+ return;
+ }
+ response.status = value.value.int32Values.get(2);
+ if (response.status == SwitchUserStatus.SUCCESS
+ || response.status == SwitchUserStatus.FAILURE) {
+ if (DBG) {
+ Log.d(TAG, "replying to request " + requestId + " with " + response);
+ }
+ callback.onResponse(HalCallback.STATUS_OK, response);
+ } else {
+ Log.e(TAG, "invalid status (" + response.status + ") from HAL: " + value);
+ callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
+ }
+ }
+
private <T> HalCallback<T> handleGetPendingCallback(int requestId, Class<T> clazz) {
Pair<Class<?>, HalCallback<?>> pair = getPendingCallback(requestId);
if (pair == null) return null;
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index bc2d253..d08d7fb 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -29,6 +29,8 @@
import android.car.content.pm.ICarPackageManager;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.ICarUxRestrictionsChangeListener;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -419,7 +421,7 @@
mLock.notifyAll();
}
mContext.unregisterReceiver(mPackageParsingEventReceiver);
- mUserService.removeUserCallback(mUserCallback);
+ mUserService.removeUserLifecycleListener(mUserLifecycleListener);
mSystemActivityMonitoringService.registerActivityLaunchListener(null);
for (int i = 0; i < mUxRestrictionsListeners.size(); i++) {
UxRestrictionsListener listener = mUxRestrictionsListeners.valueAt(i);
@@ -427,24 +429,19 @@
}
}
- private final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
-
- @Override
- public void onUserLockChanged(int userId, boolean unlocked) {
- // Do Nothing
+ private final UserLifecycleListener mUserLifecycleListener = event -> {
+ if (Log.isLoggable(CarLog.TAG_PACKAGE, Log.DEBUG)) {
+ Log.d(CarLog.TAG_PACKAGE, "CarPackageManagerService.onEvent(" + event + ")");
}
-
- @Override
- public void onSwitchUser(int userId) {
- mHandler.requestParsingInstalledPkgs(0);
+ if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
+ CarPackageManagerService.this.mHandler.requestParsingInstalledPkgs(0);
}
-
};
// run from HandlerThread
private void doHandleInit() {
startAppBlockingPolicies();
- mUserService.addUserCallback(mUserCallback);
+ mUserService.addUserLifecycleListener(mUserLifecycleListener);
IntentFilter pkgParseIntent = new IntentFilter();
for (String action : mPackageManagerActions) {
pkgParseIntent.addAction(action);
diff --git a/service/src/com/android/car/pm/VendorServiceController.java b/service/src/com/android/car/pm/VendorServiceController.java
index a14a2e6..be1b6a8 100644
--- a/service/src/com/android/car/pm/VendorServiceController.java
+++ b/service/src/com/android/car/pm/VendorServiceController.java
@@ -19,6 +19,9 @@
import static android.content.Context.BIND_AUTO_CREATE;
import android.app.ActivityManager;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleEvent;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -52,7 +55,7 @@
* possible pass {@link #mHandler} when subscribe for callbacks otherwise redirect code to the
* handler.
*/
-class VendorServiceController implements CarUserService.UserCallback {
+class VendorServiceController implements UserLifecycleListener {
private static final boolean DBG = true;
private static final int MSG_SWITCH_USER = 1;
@@ -102,14 +105,14 @@
}
mCarUserService = CarLocalServices.getService(CarUserService.class);
- mCarUserService.addUserCallback(this);
+ mCarUserService.addUserLifecycleListener(this);
startOrBindServicesIfNeeded();
}
void release() {
if (mCarUserService != null) {
- mCarUserService.removeUserCallback(this);
+ mCarUserService.removeUserLifecycleListener(this);
}
for (ConnectionKey key : mConnections.keySet()) {
@@ -119,6 +122,29 @@
mConnections.clear();
}
+ @Override
+ public void onEvent(UserLifecycleEvent event) {
+ if (Log.isLoggable(CarLog.TAG_PACKAGE, Log.DEBUG)) {
+ Log.d(CarLog.TAG_PACKAGE, "onEvent(" + event + ")");
+ }
+ // TODO(b/152069895): Use USER_LIFECYCLE_EVENT_TYPE_UNLOCKED and not care about the
+ // deprecated unlock=false scenario.
+ if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING == event.getEventType()) {
+ Message msg = mHandler.obtainMessage(
+ MSG_USER_LOCK_CHANGED,
+ event.getUserHandle().getIdentifier(),
+ /* unlocked= */ 1);
+ mHandler.executeOrSendMessage(msg);
+ } else if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
+ mHandler.removeMessages(MSG_SWITCH_USER);
+ Message msg = mHandler.obtainMessage(
+ MSG_SWITCH_USER,
+ event.getUserHandle().getIdentifier(),
+ /* unlocked= */ 0);
+ mHandler.executeOrSendMessage(msg);
+ }
+ }
+
private void doSwitchUser(int userId) {
// Stop all services which which do not run under foreground or system user.
final int fgUser = ActivityManager.getCurrentUser();
@@ -184,19 +210,6 @@
}
}
- @Override
- public void onUserLockChanged(int userId, boolean unlocked) {
- Message msg = mHandler.obtainMessage(MSG_USER_LOCK_CHANGED, userId, unlocked ? 1 : 0);
- mHandler.executeOrSendMessage(msg);
- }
-
- @Override
- public void onSwitchUser(int userId) {
- mHandler.removeMessages(MSG_SWITCH_USER);
- Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, userId, 0);
- mHandler.executeOrSendMessage(msg);
- }
-
private void startOrBindService(VendorServiceInfo service, UserHandle user) {
ConnectionKey key = ConnectionKey.of(service, user);
VendorServiceConnection connection = getOrCreateConnection(key);
diff --git a/service/src/com/android/car/user/CarUserNoticeService.java b/service/src/com/android/car/user/CarUserNoticeService.java
index 94caeaf..698f4ce 100644
--- a/service/src/com/android/car/user/CarUserNoticeService.java
+++ b/service/src/com/android/car/user/CarUserNoticeService.java
@@ -27,6 +27,8 @@
import android.car.CarNotConnectedException;
import android.car.hardware.power.CarPowerManager;
import android.car.settings.CarSettings;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.car.user.IUserNotice;
import android.car.user.IUserNoticeUI;
import android.content.BroadcastReceiver;
@@ -108,19 +110,16 @@
@UserIdInt
private int mIgnoreUserId = UserHandle.USER_NULL;
- private final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
- @Override
- public void onUserLockChanged(@UserIdInt int userId, boolean unlocked) {
- // Nothing to do
+ private final UserLifecycleListener mUserLifecycleListener = event -> {
+ if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+ Log.d(TAG_USER, "onEvent(" + event + ")");
}
-
- @Override
- public void onSwitchUser(@UserIdInt int userId) {
- mMainHandler.post(() -> {
+ if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
+ CarUserNoticeService.this.mMainHandler.post(() -> {
stopUi(/* clearUiShown= */ true);
synchronized (mLock) {
- // This should be the only place to change user
- mUserId = userId;
+ // This should be the only place to change user
+ mUserId = event.getUserHandle().getIdentifier();
}
startNoticeUiIfNecessary();
});
@@ -375,7 +374,7 @@
throw new RuntimeException("CarNotConnectedException from CarPowerManager", e);
}
CarUserService userService = CarLocalServices.getService(CarUserService.class);
- userService.addUserCallback(mUserCallback);
+ userService.addUserLifecycleListener(mUserLifecycleListener);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
@@ -390,7 +389,7 @@
}
mContext.unregisterReceiver(mDisplayBroadcastReceiver);
CarUserService userService = CarLocalServices.getService(CarUserService.class);
- userService.removeUserCallback(mUserCallback);
+ userService.removeUserLifecycleListener(mUserLifecycleListener);
CarPowerManager carPowerManager;
synchronized (mLock) {
carPowerManager = mCarPowerManager;
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 6606fa3..9424a90 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -120,9 +120,6 @@
@GuardedBy("mLockUser")
private final ArrayList<Integer> mBackgroundUsersRestartedHere = new ArrayList<>();
- // TODO(b/144120654): merge then
- private final CopyOnWriteArrayList<UserCallback> mUserCallbacks = new CopyOnWriteArrayList<>();
-
private final UserHalService mHal;
/**
@@ -139,20 +136,6 @@
private final int mHalTimeoutMs = CarProperties.user_hal_timeout().orElse(5_000);
- /**
- * Interface for callbacks related to user activities.
- *
- * @deprecated {@link UserCallback} will be fully replaced by
- * {@link UserLifecycleListener} as part of b/145689885
- */
- @Deprecated
- public interface UserCallback {
- /** Gets called when user lock status has been changed. */
- void onUserLockChanged(@UserIdInt int userId, boolean unlocked);
- /** Called when new foreground user started to boot. */
- void onSwitchUser(@UserIdInt int userId);
- }
-
private final CopyOnWriteArrayList<PassengerCallback> mPassengerCallbacks =
new CopyOnWriteArrayList<>();
@@ -674,30 +657,6 @@
}
/**
- * Adds a new callback to listen to user activity events.
- *
- * @deprecated users should rely on {@link UserLifecycleListener} and invoke
- * {@link #addUserLifecycleListener} instead
- */
- @Deprecated
- public void addUserCallback(@NonNull UserCallback callback) {
- Objects.requireNonNull(callback, "callback cannot be null");
- mUserCallbacks.add(callback);
- }
-
- /**
- * Removes previously added user callback.
- *
- * @deprecated users should rely on {@link UserLifecycleListener} and invoke
- * {@link CarUserService#remove]UserLifecycleListener} instead
- */
- @Deprecated
- public void removeUserCallback(@NonNull UserCallback callback) {
- Objects.requireNonNull(callback, "callback cannot be null");
- mUserCallbacks.remove(callback);
- }
-
- /**
* Adds a new {@link UserLifecycleListener} to listen to user activity events.
*/
public void addUserLifecycleListener(@NonNull UserLifecycleListener listener) {
@@ -737,19 +696,19 @@
*
* @param userId User id whoes lock status is changed.
* @param unlocked Unlocked (={@code true}) or locked (={@code false}).
+ *
+ * @deprecated TODO(b/151895715): method to be folded into onUserLifecycleEvent
*/
+ @Deprecated
public void setUserLockStatus(@UserIdInt int userId, boolean unlocked) {
TimingsTraceLog t = new TimingsTraceLog(TAG_USER,
Trace.TRACE_TAG_SYSTEM_SERVER);
- t.traceBegin("onUserLockChanged-" + userId
- + (unlocked ? "-unlocked" : "-locked"));
- for (UserCallback callback : mUserCallbacks) {
- t.traceBegin("onUserLockChanged-"
- + callback.getClass().getSimpleName());
- callback.onUserLockChanged(userId, unlocked);
- t.traceEnd();
- }
- t.traceEnd();
+ // TODO(b/152043575): we should overload the UserLifecycleEvent constructor with a
+ // /* hidden */ method that takes int userId directly.
+ notifyUserLifecycleListeners(t, new UserLifecycleEvent(
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
+ /* from= */ null,
+ /* to= */ UserHandle.of(userId)));
if (!unlocked) { // nothing else to do when it is locked back.
return;
@@ -882,7 +841,9 @@
* Called when new foreground user started to boot.
*
* @param userId User id of new user.
+ * @deprecated TODO(b/151895715): method to be folded into onUserLifecycleEvent
*/
+ @Deprecated
public void onSwitchUser(@UserIdInt int userId) {
Log.i(TAG_USER, "onSwitchUser() callback for user " + userId);
TimingsTraceLog t = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
@@ -936,23 +897,19 @@
}, "SwitchUser-" + userId + "-Listeners").start();
}
- notifyUserLifecycleListeners(t, userId);
- notifyCallbacks(t, userId);
- }
-
- private void notifyUserLifecycleListeners(TimingsTraceLog t,
- @UserIdInt int userId) {
- if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
- Log.d(TAG_USER, "Notifying " + mUserLifecycleListeners.size()
- + " user lifecycle listeners");
- }
// TODO(b/145689885): passing null for `from` parameter until it gets properly replaced
// the expected Binder call.
- UserLifecycleEvent event = new UserLifecycleEvent(
+ // TODO(b/152043575): we should overload the UserLifecycleEvent constructor with a
+ // /* hidden */ method that takes int userId directly.
+ notifyUserLifecycleListeners(t, new UserLifecycleEvent(
/* eventType= */ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
- /* from= */ null, /* to= */ new UserHandle(userId));
+ /* from= */ null, /* to= */ new UserHandle(userId)));
+ }
+
+ private void notifyUserLifecycleListeners(TimingsTraceLog t, UserLifecycleEvent event) {
+ t.traceBegin("notifyInternalUserLifecycleListeners");
for (UserLifecycleListener listener : mUserLifecycleListeners) {
- t.traceBegin("onEvent-" + listener.getClass().getName());
+ t.traceBegin("onEvent-" + listener.getClass().getSimpleName());
try {
listener.onEvent(event);
} catch (RuntimeException e) {
@@ -961,18 +918,7 @@
}
t.traceEnd();
}
- }
-
- private void notifyCallbacks(TimingsTraceLog t, @UserIdInt int userId) {
- if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
- Log.d(TAG_USER, "Notifying " + mUserCallbacks.size() + " callbacks");
- }
- for (UserCallback callback : mUserCallbacks) {
- t.traceBegin("onSwitchUser-" + callback.getClass().getName());
- callback.onSwitchUser(userId);
- t.traceEnd();
- }
- t.traceEnd(); // onSwitchUser
+ t.traceEnd();
}
/**
@@ -1245,4 +1191,4 @@
List<OccupantZoneInfo> zoneInfos = getOccupantZones(occupantType);
return (zoneInfos.size() > 0) ? zoneInfos.get(0).zoneId : OccupantZoneInfo.INVALID_ZONE_ID;
}
-}
+}
\ No newline at end of file
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 21ed269..b511e04 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -26,6 +26,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.automotive.watchdog.ICarWatchdogClient;
+import android.automotive.watchdog.PowerCycle;
+import android.automotive.watchdog.StateType;
+import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
+import android.car.hardware.power.ICarPowerStateListener;
import android.car.watchdog.ICarWatchdogService;
import android.car.watchdoglib.CarWatchdogDaemonHelper;
import android.content.Context;
@@ -39,6 +43,8 @@
import androidx.annotation.VisibleForTesting;
+import com.android.car.CarLocalServices;
+import com.android.car.CarPowerManagementService;
import com.android.car.CarServiceBase;
import com.android.internal.annotations.GuardedBy;
@@ -116,6 +122,7 @@
}
mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener);
mCarWatchdogDaemonHelper.connect();
+ subscribePowerCycleChange();
if (DEBUG) {
Log.d(TAG, "CarWatchdogService is initialized");
}
@@ -357,6 +364,54 @@
}
}
+ private void subscribePowerCycleChange() {
+ CarPowerManagementService powerService =
+ CarLocalServices.getService(CarPowerManagementService.class);
+ if (powerService == null) {
+ Log.w(TAG, "Cannot get CarPowerManagementService");
+ return;
+ }
+ powerService.registerListener(new ICarPowerStateListener.Stub() {
+ @Override
+ public void onStateChanged(int state) {
+ int powerCycle;
+ switch (state) {
+ // SHUTDOWN_PREPARE covers suspend and shutdown.
+ case CarPowerStateListener.SHUTDOWN_PREPARE:
+ powerCycle = PowerCycle.POWER_CYCLE_SUSPEND;
+ break;
+ // ON covers resume.
+ case CarPowerStateListener.ON:
+ powerCycle = PowerCycle.POWER_CYCLE_RESUME;
+ // There might be outdated & incorrect info. We should reset them before
+ // starting to do health check.
+ prepareHealthCheck();
+ break;
+ default:
+ return;
+ }
+ try {
+ mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
+ powerCycle, /* arg2= */ -1);
+ } catch (IllegalArgumentException | RemoteException e) {
+ Log.w(TAG, "Notifying system state change failed: " + e);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Notified car watchdog daemon a power cycle(" + powerCycle + ")");
+ }
+ }
+ });
+ }
+
+ private void prepareHealthCheck() {
+ synchronized (mLock) {
+ for (int timeout : ALL_TIMEOUTS) {
+ SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout);
+ pingedClients.clear();
+ }
+ }
+ }
+
@NonNull
private int[] toIntArray(@NonNull ArrayList<Integer> list) {
int size = list.size();
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/system_feature_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/system_feature_fragment.xml
new file mode 100644
index 0000000..45d1dbb
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/system_feature_fragment.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ListView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/sys_features_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+</ListView>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index 719e59c..4b5b568 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -64,6 +64,7 @@
import com.google.android.car.kitchensink.sensor.SensorsTestFragment;
import com.google.android.car.kitchensink.storagelifetime.StorageLifetimeFragment;
import com.google.android.car.kitchensink.storagevolumes.StorageVolumesFragment;
+import com.google.android.car.kitchensink.systemfeatures.SystemFeaturesFragment;
import com.google.android.car.kitchensink.touch.TouchTestFragment;
import com.google.android.car.kitchensink.users.UsersFragment;
import com.google.android.car.kitchensink.vehiclectrl.VehicleCtrlFragment;
@@ -72,6 +73,7 @@
import com.google.android.car.kitchensink.weblinks.WebLinksTestFragment;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
public class KitchenSinkActivity extends FragmentActivity {
@@ -179,20 +181,20 @@
// new FragmentMenuEntry("input test", InputTestFragment.class),
new FragmentMenuEntry("notification", NotificationFragment.class),
new FragmentMenuEntry("orientation test", OrientationTestFragment.class),
+ new FragmentMenuEntry("package info", PackageInfoFragment.class),
new FragmentMenuEntry("power test", PowerTestFragment.class),
new FragmentMenuEntry("projection", ProjectionFragment.class),
new FragmentMenuEntry("property test", PropertyTestFragment.class),
new FragmentMenuEntry("sensors", SensorsTestFragment.class),
new FragmentMenuEntry("storage lifetime", StorageLifetimeFragment.class),
new FragmentMenuEntry("storage volumes", StorageVolumesFragment.class),
+ new FragmentMenuEntry("system features", SystemFeaturesFragment.class),
new FragmentMenuEntry("touch test", TouchTestFragment.class),
new FragmentMenuEntry("users", UsersFragment.class),
- new FragmentMenuEntry("volume test", VolumeTestFragment.class),
new FragmentMenuEntry("vehicle ctrl", VehicleCtrlFragment.class),
new FragmentMenuEntry("vehicle hal", VehicleHalFragment.class),
- new FragmentMenuEntry("web links", WebLinksTestFragment.class),
- new FragmentMenuEntry("package info", PackageInfoFragment.class)
- );
+ new FragmentMenuEntry("volume test", VolumeTestFragment.class),
+ new FragmentMenuEntry("web links", WebLinksTestFragment.class));
private Car mCarApi;
private CarHvacManager mHvacManager;
@@ -203,6 +205,10 @@
private CarProjectionManager mCarProjectionManager;
private Object mPropertyManagerReady = new Object();
+ public KitchenSinkActivity() {
+ mMenuEntries.sort(Comparator.comparing(MenuEntry::getText));
+ }
+
public CarHvacManager getHvacManager() {
return mHvacManager;
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/systemfeatures/SystemFeaturesFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/systemfeatures/SystemFeaturesFragment.java
new file mode 100644
index 0000000..f3d51b2
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/systemfeatures/SystemFeaturesFragment.java
@@ -0,0 +1,92 @@
+/*
+ * 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.google.android.car.kitchensink.systemfeatures;
+
+import android.annotation.Nullable;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * Shows system features as available by PackageManager
+ */
+public class SystemFeaturesFragment extends Fragment {
+ private static final String TAG = "CAR.SYSFEATURES.KS";
+
+ private ListView mSysFeaturesList;
+ private PackageManager mPackageManager;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mPackageManager = Objects.requireNonNull(
+ getContext().getPackageManager());
+
+ super.onCreate(savedInstanceState);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(
+ @NonNull LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ mSysFeaturesList = (ListView) inflater.inflate(R.layout.system_feature_fragment,
+ container, false);
+ return mSysFeaturesList;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refresh();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ private void refresh() {
+ final FeatureInfo[] features = mPackageManager.getSystemAvailableFeatures();
+ if (features != null) {
+ final String[] descriptions = Arrays.stream(features)
+ .filter(fi -> fi != null && fi.name != null)
+ .sorted(Comparator.<FeatureInfo, String>comparing(fi -> fi.name)
+ .thenComparing(fi -> fi.version))
+ .map(fi -> String.format("%s (v=%d)", fi.name, fi.version))
+ .toArray(String[]::new);
+ mSysFeaturesList.setAdapter(new ArrayAdapter<>(getContext(),
+ android.R.layout.simple_list_item_1, descriptions));
+ } else {
+ Log.e(TAG, "no features available on this device!");
+ }
+ }
+}
diff --git a/tests/carservice_test/src/com/android/car/CarPropertyManagerTest.java b/tests/carservice_test/src/com/android/car/CarPropertyManagerTest.java
index 80c0df9..7044531 100644
--- a/tests/carservice_test/src/com/android/car/CarPropertyManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarPropertyManagerTest.java
@@ -16,6 +16,8 @@
package com.android.car;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.testng.Assert.assertThrows;
import android.car.Car;
@@ -56,6 +58,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Test for {@link android.car.hardware.property.CarPropertyManager}
@@ -116,6 +120,8 @@
| VehicleAreaSeat.ROW_2_RIGHT;
private static final float INIT_TEMP_VALUE = 16f;
private static final float CHANGED_TEMP_VALUE = 20f;
+ private static final int CALLBACK_SHORT_TIMEOUT_MS = 100; // ms
+
private CarPropertyManager mManager;
@Rule public TestName mTestName = new TestName();
@@ -125,7 +131,7 @@
super.setUp();
setUpTargetSdk();
mManager = (CarPropertyManager) getCar().getCarManager(Car.PROPERTY_SERVICE);
- Assert.assertNotNull(mManager);
+ assertThat(mManager).isNotNull();
}
private void setUpTargetSdk() {
@@ -142,12 +148,12 @@
for (CarPropertyConfig cfg : configs) {
switch (cfg.getPropertyId()) {
case CUSTOM_SEAT_MIXED_PROP_ID_1:
- Assert.assertArrayEquals(CONFIG_ARRAY_1.toArray(),
- cfg.getConfigArray().toArray());
+ assertThat(cfg.getConfigArray()).containsExactlyElementsIn(CONFIG_ARRAY_1)
+ .inOrder();
break;
case CUSTOM_GLOBAL_MIXED_PROP_ID_2:
- Assert.assertArrayEquals(CONFIG_ARRAY_2.toArray(),
- cfg.getConfigArray().toArray());
+ assertThat(cfg.getConfigArray()).containsExactlyElementsIn(CONFIG_ARRAY_2)
+ .inOrder();
break;
case VehiclePropertyIds.HVAC_TEMPERATURE_SET:
case PROP_CAUSE_STATUS_CODE_ACCESS_DENIED:
@@ -170,68 +176,51 @@
0, EXPECTED_VALUE_1);
CarPropertyValue<Object[]> result = mManager.getProperty(
CUSTOM_SEAT_MIXED_PROP_ID_1, 0);
- Assert.assertArrayEquals(EXPECTED_VALUE_1, result.getValue());
-
+ assertThat(result.getValue()).isEqualTo(EXPECTED_VALUE_1);
mManager.setProperty(Object[].class, CUSTOM_GLOBAL_MIXED_PROP_ID_2,
0, EXPECTED_VALUE_2);
result = mManager.getProperty(
CUSTOM_GLOBAL_MIXED_PROP_ID_2, 0);
- Assert.assertArrayEquals(EXPECTED_VALUE_2, result.getValue());
+ assertThat(result.getValue()).isEqualTo(EXPECTED_VALUE_2);
}
@Test
public void testGetPropertyConfig() {
CarPropertyConfig config = mManager.getCarPropertyConfig(CUSTOM_SEAT_MIXED_PROP_ID_1);
- Assert.assertEquals(CUSTOM_SEAT_MIXED_PROP_ID_1, config.getPropertyId());
+ assertThat(config.getPropertyId()).isEqualTo(CUSTOM_SEAT_MIXED_PROP_ID_1);
// return null if can not find the propertyConfig for the property.
- Assert.assertNull(mManager.getCarPropertyConfig(FAKE_PROPERTY_ID));
+ assertThat(mManager.getCarPropertyConfig(FAKE_PROPERTY_ID)).isNull();
}
@Test
public void testGetAreaId() {
int result = mManager.getAreaId(CUSTOM_SEAT_MIXED_PROP_ID_1, VehicleAreaSeat.ROW_1_LEFT);
- Assert.assertEquals(DRIVER_SIDE_AREA_ID, result);
-
+ assertThat(result).isEqualTo(DRIVER_SIDE_AREA_ID);
//test for the GLOBAL property
int globalAreaId =
mManager.getAreaId(CUSTOM_GLOBAL_MIXED_PROP_ID_2, VehicleAreaSeat.ROW_1_LEFT);
- Assert.assertEquals(VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, globalAreaId);
-
+ assertThat(globalAreaId).isEqualTo(VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
//test exception
- try {
- int areaId = mManager.getAreaId(CUSTOM_SEAT_MIXED_PROP_ID_1,
- VehicleAreaSeat.ROW_3_CENTER);
- Assert.fail("Unexpected areaId: " + areaId);
- } catch (IllegalArgumentException e) {
- Log.v(TAG, e.getMessage());
- }
-
- try {
- // test exception
- int areaIdForFakeProp = mManager.getAreaId(FAKE_PROPERTY_ID,
- VehicleAreaSeat.ROW_1_LEFT);
- Assert.fail("Unexpected areaId for fake property: " + areaIdForFakeProp);
- } catch (IllegalArgumentException e) {
- Log.v(TAG, e.getMessage());
- }
+ assertThrows(IllegalArgumentException.class, () -> mManager.getAreaId(
+ CUSTOM_SEAT_MIXED_PROP_ID_1, VehicleAreaSeat.ROW_3_CENTER));
+ assertThrows(IllegalArgumentException.class, () -> mManager.getAreaId(FAKE_PROPERTY_ID,
+ VehicleAreaSeat.ROW_1_LEFT));
}
@Test
- public void testNotReceiveOnErrorEvent() {
- TestCallback callback = new TestCallback();
+ public void testNotReceiveOnErrorEvent() throws Exception {
+ TestErrorCallback callback = new TestErrorCallback();
mManager.registerCallback(callback, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
CarPropertyManager.SENSOR_RATE_ONCHANGE);
injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
// app never change the value of HVAC_TEMPERATURE_SET, it won't get an error code.
- SystemClock.sleep(SHORT_WAIT_TIMEOUT_MS);
- Assert.assertFalse(callback.mReceivedErrorEventWithErrorCode);
- Assert.assertFalse(callback.mReceivedErrorEventWithOutErrorCode);
+ callback.assertOnErrorEventNotCalled();
}
@Test
- public void testReceiveOnErrorEvent() {
- TestCallback callback = new TestCallback();
+ public void testReceiveOnErrorEvent() throws Exception {
+ TestErrorCallback callback = new TestErrorCallback();
mManager.registerCallback(callback, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
CarPropertyManager.SENSOR_RATE_ONCHANGE);
mManager.setFloatProperty(
@@ -239,17 +228,17 @@
CHANGED_TEMP_VALUE);
injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
- SystemClock.sleep(SHORT_WAIT_TIMEOUT_MS);
- Assert.assertTrue(callback.mReceivedErrorEventWithErrorCode);
- Assert.assertEquals(CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN,
- callback.mErrorCode);
- Assert.assertFalse(callback.mReceivedErrorEventWithOutErrorCode);
+ callback.assertOnErrorEventCalled();
+ assertThat(callback.mReceivedErrorEventWithErrorCode).isTrue();
+ assertThat(callback.mErrorCode).isEqualTo(
+ CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
+ assertThat(callback.mReceivedErrorEventWithOutErrorCode).isFalse();
}
@Test
- public void testNotReceiveOnErrorEventAfterUnregister() {
- TestCallback callback1 = new TestCallback();
- TestCallback callback2 = new TestCallback();
+ public void testNotReceiveOnErrorEventAfterUnregister() throws Exception {
+ TestErrorCallback callback1 = new TestErrorCallback();
+ TestErrorCallback callback2 = new TestErrorCallback();
mManager.registerCallback(callback1, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
CarPropertyManager.SENSOR_RATE_ONCHANGE);
mManager.registerCallback(callback2, VehiclePropertyIds.HVAC_TEMPERATURE_SET,
@@ -260,9 +249,9 @@
mManager.unregisterCallback(callback1, VehiclePropertyIds.HVAC_TEMPERATURE_SET);
injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID,
CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN);
- SystemClock.sleep(SHORT_WAIT_TIMEOUT_MS);
- Assert.assertFalse(callback1.mReceivedErrorEventWithErrorCode);
- Assert.assertFalse(callback1.mReceivedErrorEventWithOutErrorCode);
+ // callback1 is unregistered
+ callback1.assertOnErrorEventNotCalled();
+ callback2.assertOnErrorEventCalled();
}
@Test
public void testSetterExceptionsInQ() {
@@ -352,13 +341,12 @@
}
@Test
- public void testOnChangeEventWithSameAreaId() {
+ public void testOnChangeEventWithSameAreaId() throws Exception {
// init
mManager.setProperty(Integer.class,
CUSTOM_SEAT_INT_PROP_1, DRIVER_SIDE_AREA_ID, 1);
- TestSequenceCallback callback = new TestSequenceCallback();
+ TestSequenceCallback callback = new TestSequenceCallback(1);
mManager.registerCallback(callback, CUSTOM_SEAT_INT_PROP_1, 0);
-
VehiclePropValue firstFakeValueDriveSide = new VehiclePropValue();
firstFakeValueDriveSide.prop = CUSTOM_SEAT_INT_PROP_1;
firstFakeValueDriveSide.areaId = DRIVER_SIDE_AREA_ID;
@@ -369,27 +357,24 @@
secFakeValueDriveSide.areaId = DRIVER_SIDE_AREA_ID;
secFakeValueDriveSide.value.int32Values.add(3); // 0 in HAL indicate false;
secFakeValueDriveSide.timestamp = SystemClock.elapsedRealtimeNanos();
- SystemClock.sleep(100);
- callback.reset(); // clean up the old events
-
// inject the new event first
getMockedVehicleHal().injectEvent(secFakeValueDriveSide);
// inject the old event
getMockedVehicleHal().injectEvent(firstFakeValueDriveSide);
- SystemClock.sleep(100); // waiting for events
+ callback.assertOnChangeEventCalled();
// Client should only get the new event
- Assert.assertEquals(3,
- (int) callback.getLastCarPropertyValue(CUSTOM_SEAT_INT_PROP_1).getValue());
- Assert.assertEquals(1, callback.getEventCounter());
+ assertThat((int) callback.getLastCarPropertyValue(CUSTOM_SEAT_INT_PROP_1).getValue())
+ .isEqualTo(3);
+ assertThat(callback.getEventCounter()).isEqualTo(1);
}
@Test
- public void testOnChangeEventWithDifferentAreaId() {
+ public void testOnChangeEventWithDifferentAreaId() throws Exception {
// init
mManager.setProperty(Integer.class,
CUSTOM_SEAT_INT_PROP_2, DRIVER_SIDE_AREA_ID, 1);
- TestSequenceCallback callback = new TestSequenceCallback();
+ TestSequenceCallback callback = new TestSequenceCallback(2);
mManager.registerCallback(callback, CUSTOM_SEAT_INT_PROP_2, 0);
VehiclePropValue fakeValueDriveSide = new VehiclePropValue();
@@ -403,17 +388,16 @@
fakeValuePsgSide.areaId = PASSENGER_SIDE_AREA_ID;
fakeValuePsgSide.value.int32Values.add(5);
fakeValuePsgSide.timestamp = SystemClock.elapsedRealtimeNanos();
- SystemClock.sleep(100);
- callback.reset();
+
// inject passenger event before driver event
getMockedVehicleHal().injectEvent(fakeValuePsgSide);
getMockedVehicleHal().injectEvent(fakeValueDriveSide);
- SystemClock.sleep(100);
+ callback.assertOnChangeEventCalled();
// both events should be received by listener
- Assert.assertEquals(4,
- (int) callback.getLastCarPropertyValue(CUSTOM_SEAT_INT_PROP_2).getValue());
- Assert.assertEquals(2, callback.getEventCounter());
+ assertThat((int) callback.getLastCarPropertyValue(CUSTOM_SEAT_INT_PROP_2).getValue())
+ .isEqualTo(4);
+ assertThat(callback.getEventCounter()).isEqualTo(2);
}
@Override
@@ -503,13 +487,13 @@
}
}
- private static class TestCallback implements CarPropertyManager.CarPropertyEventCallback {
+ private static class TestErrorCallback implements CarPropertyManager.CarPropertyEventCallback {
private static final String CALLBACK_TAG = "ErrorEventTest";
private boolean mReceivedErrorEventWithErrorCode = false;
private boolean mReceivedErrorEventWithOutErrorCode = false;
private int mErrorCode;
-
+ private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
@Override
public void onChangeEvent(CarPropertyValue value) {
Log.d(CALLBACK_TAG, "onChangeEvent: " + value);
@@ -519,6 +503,7 @@
public void onErrorEvent(int propId, int zone) {
mReceivedErrorEventWithOutErrorCode = true;
Log.d(CALLBACK_TAG, "onErrorEvent, propId: " + propId + " zone: " + zone);
+ mCountDownLatch.countDown();
}
@Override
@@ -527,6 +512,21 @@
mErrorCode = errorCode;
Log.d(CALLBACK_TAG, "onErrorEvent, propId: " + propId + " areaId: " + areaId
+ "errorCode: " + errorCode);
+ mCountDownLatch.countDown();
+ }
+
+ public void assertOnErrorEventCalled() throws InterruptedException {
+ if (!mCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException("Callback is not called in "
+ + CALLBACK_SHORT_TIMEOUT_MS + " ms.");
+ }
+ }
+
+ public void assertOnErrorEventNotCalled() throws InterruptedException {
+ if (mCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException("Callback is called in " + CALLBACK_SHORT_TIMEOUT_MS
+ + " ms.");
+ }
}
}
@@ -534,12 +534,20 @@
private ConcurrentHashMap<Integer, CarPropertyValue> mRecorder = new ConcurrentHashMap<>();
private int mCounter = 0;
-
+ private final CountDownLatch mCountDownLatch;
@Override
public void onChangeEvent(CarPropertyValue value) {
Log.e(TAG, "onChanged get a event " + value);
mRecorder.put(value.getPropertyId(), value);
- mCounter++;
+ // Skip initial events
+ if (value.getTimestamp() != 0) {
+ mCounter++;
+ mCountDownLatch.countDown();
+ }
+ }
+
+ TestSequenceCallback(int expectedTimes) {
+ mCountDownLatch = new CountDownLatch(expectedTimes);
}
@Override
@@ -555,9 +563,11 @@
return mCounter;
}
- public void reset() {
- mRecorder.clear();
- mCounter = 0;
+ public void assertOnChangeEventCalled() throws InterruptedException {
+ if (!mCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException("Callback is not called in "
+ + CALLBACK_SHORT_TIMEOUT_MS + " ms.");
+ }
}
}
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index dbb4ca0..f845972 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -21,10 +21,10 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
-import android.annotation.NonNull;
import android.car.Car;
import android.car.test.CarTestManager;
import android.car.test.CarTestManagerBinderWrapper;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
@@ -57,7 +57,6 @@
import com.android.car.systeminterface.WakeLockInterface;
import com.android.car.test.utils.TemporaryDirectory;
import com.android.car.user.CarUserService;
-import com.android.car.user.CarUserService.UserCallback;
import com.android.car.vehiclehal.test.MockedVehicleHal;
import com.android.car.vehiclehal.test.MockedVehicleHal.DefaultPropertyHandler;
import com.android.car.vehiclehal.test.MockedVehicleHal.StaticPropertyHandler;
@@ -95,7 +94,7 @@
private MockResources mResources;
private MockedCarTestContext mMockedCarTestContext;
- private final List<CarUserService.UserCallback> mUserCallbacks = new ArrayList<>();
+ private final List<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
private final CarUserService mCarUserService = mock(CarUserService.class);
private final MockIOInterface mMockIOInterface = new MockIOInterface();
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@@ -163,21 +162,6 @@
return cn.flattenToString();
}
- /**
- * Emulates a call to {@link CarUserService#onSwitchUser(int)} that dispatches
- * {@link UserCallback#onSwitchUser(int)} to the callbacks whose {@code toString()} method
- * contains the given {@code filter}.
- */
- protected void switchUser(int userId, @NonNull String filter) {
- Log.d(TAG, "switchUser(" + userId + ", " + filter + "): callbacks=" + mUserCallbacks);
- for (UserCallback callback : mUserCallbacks) {
- if (callback.toString().contains(filter)) {
- Log.i(TAG, "Notifying " + callback);
- callback.onSwitchUser(userId);
- }
- }
- }
-
@Before
@UiThreadTest
public void setUp() throws Exception {
@@ -194,18 +178,18 @@
configureResourceOverrides((MockResources) mMockedCarTestContext.getResources());
doAnswer((invocation) -> {
- CarUserService.UserCallback callback = invocation.getArgument(0);
- Log.d(TAG, "Adding callback: " + callback);
- mUserCallbacks.add(callback);
+ UserLifecycleListener listener = invocation.getArgument(0);
+ Log.d(TAG, "Adding UserLifecycleListener: " + listener);
+ mUserLifecycleListeners.add(listener);
return null;
- }).when(mCarUserService).addUserCallback(any());
+ }).when(mCarUserService).addUserLifecycleListener(any());
doAnswer((invocation) -> {
- CarUserService.UserCallback callback = invocation.getArgument(0);
- Log.d(TAG, "Removing callback: " + callback);
- mUserCallbacks.remove(callback);
+ UserLifecycleListener listener = invocation.getArgument(0);
+ Log.d(TAG, "Removing UserLifecycleListener: " + listener);
+ mUserLifecycleListeners.remove(listener);
return null;
- }).when(mCarUserService).removeUserCallback(any());
+ }).when(mCarUserService).removeUserLifecycleListener(any());
// ICarImpl will register new CarLocalServices services.
// This prevents one test failure in tearDown from triggering assertion failure for single
diff --git a/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java b/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
index 258c9c9..49ed38c 100644
--- a/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
+++ b/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
@@ -75,6 +75,8 @@
setDrivingStateParked();
}
+ // Suppress test to avoid blocking team while b/150491747 is evaluated
+ @Suppress
@Test
public void testBlockingActivity_doActivity_isNotBlocked() throws Exception {
startActivity(toComponentName(getTestContext(), DoActivity.class));
@@ -95,6 +97,8 @@
UI_TIMEOUT_MS)).isNotNull();
}
+ // Suppress test to avoid blocking team while b/150491747 is evaluated
+ @Suppress
@Test
public void testBlockingActivity_nonDoFinishesOnCreate_noBlockingActivity()
throws Exception {
@@ -103,6 +107,8 @@
assertBlockingActivityNotFound();
}
+ // Suppress test to avoid blocking team while b/150491747 is evaluated
+ @Suppress
@Test
public void testBlockingActivity_nonDoLaunchesDoOnCreate_noBlockingActivity()
throws Exception {
diff --git a/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
index ed468b3..3517e13 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
@@ -32,6 +32,8 @@
import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.car.VehicleAreaSeat;
import android.car.media.CarAudioManager;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleEvent;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
@@ -212,7 +214,7 @@
doReturn(VehicleAreaSeat.SEAT_ROW_1_LEFT).when(mService).getDriverSeat();
doReturn(ActivityManager.getCurrentUser()).when(mService).getCurrentUser();
- Car car = new Car(mContext, /* service= */null, /* handler= */ null);
+ Car car = new Car(mContext, /* service= */ null, /* handler= */ null);
mManager = new CarOccupantZoneManager(car, mService);
}
@@ -454,7 +456,10 @@
final int newUserId = 100;
doReturn(newUserId).when(mService).getCurrentUser();
- mService.mUserCallback.onSwitchUser(newUserId);
+ mService.mUserLifecycleListener.onEvent(new UserLifecycleEvent(
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ /* from= */ null,
+ /* to= */ UserHandle.of(newUserId)));
// key : zone id
HashMap<Integer, OccupantConfig> configs = mService.getActiveOccupantConfigs();
@@ -670,7 +675,10 @@
final int newUserId = 100;
doReturn(newUserId).when(mService).getCurrentUser();
- mService.mUserCallback.onSwitchUser(newUserId);
+ mService.mUserLifecycleListener.onEvent(new UserLifecycleEvent(
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ /* from= */ null,
+ /* to= */ UserHandle.of(newUserId)));
assertThat(newUserId).isEqualTo(mManager.getUserForOccupant(mZoneDriverLHD));
//TODO update this after secondary user handling
@@ -689,7 +697,11 @@
mManager.registerOccupantZoneConfigChangeListener(mChangeListener);
resetConfigChangeEventWait();
- mService.mUserCallback.onSwitchUser(0); // user id does not matter.
+ mService.mUserLifecycleListener.onEvent(new UserLifecycleEvent(
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ /* from= */ null,
+ /* to= */ UserHandle.of(0))); // user id does not matter.
+
assertThat(waitForConfigChangeEventAndAssertFlag(eventWaitTimeMs,
CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER)).isTrue();
@@ -700,7 +712,10 @@
resetConfigChangeEventWait();
mManager.unregisterOccupantZoneConfigChangeListener(mChangeListener);
- mService.mUserCallback.onSwitchUser(0);
+ mService.mUserLifecycleListener.onEvent(new UserLifecycleEvent(
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ /* from= */ null,
+ /* to= */ UserHandle.of(0)));
assertThat(waitForConfigChangeEventAndAssertFlag(eventWaitTimeMs, 0)).isFalse();
}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
index 159efa1..622c66b 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
@@ -35,6 +35,9 @@
import android.car.userlib.UserHalHelper;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserInfo;
import android.hardware.automotive.vehicle.V2_0.UsersInfo;
@@ -69,22 +72,21 @@
private static final String TAG = UserHalServiceTest.class.getSimpleName();
/**
- * Timeout passed to {@link UserHalService#getInitialUserInfo(int, int, UsersInfo, HalCallback)}
- * calls.
+ * Timeout passed to {@link UserHalService} methods
*/
- private static final int INITIAL_USER_TIMEOUT_MS = 20;
+ private static final int TIMEOUT_MS = 20;
/**
* Timeout for {@link GenericHalCallback#assertCalled()} for tests where the HAL is supposed to
* return something - it's a short time so it doesn't impact the test duration.
*/
- private static final int INITIAL_USER_CALLBACK_TIMEOUT_SUCCESS = INITIAL_USER_TIMEOUT_MS + 50;
+ private static final int CALLBACK_TIMEOUT_SUCCESS = TIMEOUT_MS + 50;
/**
* Timeout for {@link GenericHalCallback#assertCalled()} for tests where the HAL is not supposed
* to return anything - it's a slightly longer to make sure the test doesn't fail prematurely.
*/
- private static final int INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT = INITIAL_USER_TIMEOUT_MS + 500;
+ private static final int CALLBACK_TIMEOUT_TIMEOUT = TIMEOUT_MS + 500;
// Used when crafting a reqquest property - the real value will be set by the mock.
private static final int REQUEST_ID_PLACE_HOLDER = 42;
@@ -167,7 +169,7 @@
@Test
public void testGetUserInfo_noUsersInfo() {
assertThrows(NullPointerException.class,
- () -> mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, null,
+ () -> mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS, null,
(i, r) -> {
}));
}
@@ -175,7 +177,7 @@
@Test
public void testGetUserInfo_noCallback() {
assertThrows(NullPointerException.class,
- () -> mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS,
+ () -> mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS,
mUsersInfo, null));
}
@@ -184,8 +186,8 @@
replySetPropertyWithTimeoutException(INITIAL_USER_INFO);
GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
- INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+ CALLBACK_TIMEOUT_TIMEOUT);
+ mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS, mUsersInfo,
callback);
callback.assertCalled();
@@ -193,15 +195,15 @@
assertThat(callback.response).isNull();
// Make sure the pending request was removed
- SystemClock.sleep(INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
+ SystemClock.sleep(CALLBACK_TIMEOUT_TIMEOUT);
callback.assertNotCalledAgain();
}
@Test
public void testGetUserInfo_halDidNotReply() throws Exception {
GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
- INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+ CALLBACK_TIMEOUT_TIMEOUT);
+ mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS, mUsersInfo,
callback);
callback.assertCalled();
@@ -212,12 +214,12 @@
@Test
public void testGetUserInfo_secondCallFailWhilePending() throws Exception {
GenericHalCallback<InitialUserInfoResponse> callback1 = new GenericHalCallback<>(
- INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
+ CALLBACK_TIMEOUT_TIMEOUT);
GenericHalCallback<InitialUserInfoResponse> callback2 = new GenericHalCallback<>(
- INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+ CALLBACK_TIMEOUT_TIMEOUT);
+ mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS, mUsersInfo,
callback1);
- mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+ mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS, mUsersInfo,
callback2);
callback1.assertCalled();
@@ -240,8 +242,8 @@
/* rightRequestId= */ false);
GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
- INITIAL_USER_CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+ CALLBACK_TIMEOUT_TIMEOUT);
+ mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS, mUsersInfo,
callback);
callback.assertCalled();
@@ -261,8 +263,8 @@
INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
- INITIAL_USER_CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+ CALLBACK_TIMEOUT_SUCCESS);
+ mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS, mUsersInfo,
callback);
callback.assertCalled();
@@ -287,8 +289,8 @@
INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
- INITIAL_USER_CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+ CALLBACK_TIMEOUT_SUCCESS);
+ mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS, mUsersInfo,
callback);
callback.assertCalled();
@@ -320,8 +322,8 @@
INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
- INITIAL_USER_CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+ CALLBACK_TIMEOUT_SUCCESS);
+ mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS, mUsersInfo,
callback);
callback.assertCalled();
@@ -355,8 +357,8 @@
INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
GenericHalCallback<InitialUserInfoResponse> callback = new GenericHalCallback<>(
- INITIAL_USER_CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.getInitialUserInfo(COLD_BOOT, INITIAL_USER_TIMEOUT_MS, mUsersInfo,
+ CALLBACK_TIMEOUT_SUCCESS);
+ mUserHalService.getInitialUserInfo(COLD_BOOT, TIMEOUT_MS, mUsersInfo,
callback);
callback.assertCalled();
@@ -381,6 +383,204 @@
testGetUserInfo_successDefault();
}
+ @Test
+ public void testSwitchUser_invalidTimeout() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.switchUser(mUser10, 0, mUsersInfo, (i, r) -> {
+ }));
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.switchUser(mUser10, -1, mUsersInfo, (i, r) -> {
+ }));
+ }
+
+ @Test
+ public void testSwitchUser_noUsersInfo() {
+ assertThrows(NullPointerException.class,
+ () -> mUserHalService.switchUser(mUser10, TIMEOUT_MS, null,
+ (i, r) -> {
+ }));
+ }
+
+ @Test
+ public void testSwitchUser_noCallback() {
+ assertThrows(NullPointerException.class,
+ () -> mUserHalService.switchUser(mUser10, TIMEOUT_MS,
+ mUsersInfo, null));
+ }
+
+ @Test
+ public void testSwitchUser_halSetTimedOut() throws Exception {
+ replySetPropertyWithTimeoutException(SWITCH_USER);
+
+ GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
+ CALLBACK_TIMEOUT_TIMEOUT);
+ mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+
+ callback.assertCalled();
+ assertCallbackStatus(callback, HalCallback.STATUS_HAL_SET_TIMEOUT);
+ assertThat(callback.response).isNull();
+
+ // Make sure the pending request was removed
+ SystemClock.sleep(CALLBACK_TIMEOUT_TIMEOUT);
+ callback.assertNotCalledAgain();
+ }
+
+ @Test
+ public void testSwitchUser_halDidNotReply() throws Exception {
+ GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
+ CALLBACK_TIMEOUT_TIMEOUT);
+ mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+
+ callback.assertCalled();
+ assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
+ assertThat(callback.response).isNull();
+ }
+
+ @Test
+ public void testSwitchUser_halReplyWithWrongRequestId() throws Exception {
+ // TODO(b/150419600): use helper method to convert prop value to proper req
+ VehiclePropValue propResponse = new VehiclePropValue();
+ propResponse.prop = SWITCH_USER;
+ propResponse.value.int32Values.add(REQUEST_ID_PLACE_HOLDER);
+
+ replySetPropertyWithOnChangeEvent(SWITCH_USER, propResponse,
+ /* rightRequestId= */ false);
+
+ GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
+ CALLBACK_TIMEOUT_TIMEOUT);
+ mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+
+ callback.assertCalled();
+ assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
+ assertThat(callback.response).isNull();
+ }
+
+ @Test
+ public void testSwitchUser_halReturnedInvalidMessageType() throws Exception {
+ // TODO(b/150419600): use helper method to convert prop value to proper req
+ VehiclePropValue propResponse = new VehiclePropValue();
+ propResponse.prop = SWITCH_USER;
+ propResponse.value.int32Values.add(REQUEST_ID_PLACE_HOLDER);
+ propResponse.value.int32Values.add(SwitchUserMessageType.VEHICLE_REQUEST);
+ propResponse.value.int32Values.add(SwitchUserStatus.SUCCESS);
+
+ AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
+ SWITCH_USER, propResponse, /* rightRequestId= */ true);
+
+ GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
+ CALLBACK_TIMEOUT_SUCCESS);
+ mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+
+ callback.assertCalled();
+
+ // Make sure the arguments were properly converted
+ assertSwitchUserSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
+
+ // Assert response
+ assertCallbackStatus(callback, HalCallback.STATUS_WRONG_HAL_RESPONSE);
+ assertThat(callback.response).isNull();
+ }
+
+ @Test
+ public void testUserSwitch_success() throws Exception {
+ // TODO(b/150419600): use helper method to convert prop value to proper req
+ VehiclePropValue propResponse = new VehiclePropValue();
+ propResponse.prop = SWITCH_USER;
+ propResponse.value.int32Values.add(REQUEST_ID_PLACE_HOLDER);
+ propResponse.value.int32Values.add(SwitchUserMessageType.VEHICLE_RESPONSE);
+ propResponse.value.int32Values.add(SwitchUserStatus.SUCCESS);
+
+ AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
+ SWITCH_USER, propResponse, /* rightRequestId= */ true);
+
+ GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
+ CALLBACK_TIMEOUT_SUCCESS);
+ mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+
+ callback.assertCalled();
+
+ // Make sure the arguments were properly converted
+ assertSwitchUserSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
+
+ // Assert response
+ assertCallbackStatus(callback, HalCallback.STATUS_OK);
+ SwitchUserResponse actualResponse = callback.response;
+ assertThat(actualResponse.status).isEqualTo(SwitchUserStatus.SUCCESS);
+ assertThat(actualResponse.messageType).isEqualTo(SwitchUserMessageType.VEHICLE_RESPONSE);
+ }
+
+ @Test
+ public void testUserSwitch_failure() throws Exception {
+ // TODO(b/150419600): use helper method to convert prop value to proper req
+ VehiclePropValue propResponse = new VehiclePropValue();
+ propResponse.prop = SWITCH_USER;
+ propResponse.value.int32Values.add(REQUEST_ID_PLACE_HOLDER);
+ propResponse.value.int32Values.add(SwitchUserMessageType.VEHICLE_RESPONSE);
+ propResponse.value.int32Values.add(SwitchUserStatus.FAILURE);
+
+ AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
+ SWITCH_USER, propResponse, /* rightRequestId= */ true);
+
+ GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
+ CALLBACK_TIMEOUT_SUCCESS);
+ mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+
+ callback.assertCalled();
+
+ // Make sure the arguments were properly converted
+ assertSwitchUserSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
+
+ // Assert response
+ assertCallbackStatus(callback, HalCallback.STATUS_OK);
+ SwitchUserResponse actualResponse = callback.response;
+ assertThat(actualResponse.status).isEqualTo(SwitchUserStatus.FAILURE);
+ assertThat(actualResponse.messageType).isEqualTo(SwitchUserMessageType.VEHICLE_RESPONSE);
+ }
+
+ @Test
+ public void testSwitchUser_secondCallFailWhilePending() throws Exception {
+ GenericHalCallback<SwitchUserResponse> callback1 = new GenericHalCallback<>(
+ CALLBACK_TIMEOUT_TIMEOUT);
+ GenericHalCallback<SwitchUserResponse> callback2 = new GenericHalCallback<>(
+ CALLBACK_TIMEOUT_TIMEOUT);
+ mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback1);
+ mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback2);
+
+ callback1.assertCalled();
+ assertCallbackStatus(callback1, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
+ assertThat(callback1.response).isNull();
+
+ callback2.assertCalled();
+ assertCallbackStatus(callback2, HalCallback.STATUS_CONCURRENT_OPERATION);
+ assertThat(callback1.response).isNull();
+ }
+
+ @Test
+ public void testSwitchUser_halReturnedInvalidStatus() throws Exception {
+ // TODO(b/150419600): use helper method to convert prop value to proper req
+ VehiclePropValue propResponse = new VehiclePropValue();
+ propResponse.prop = SWITCH_USER;
+ propResponse.value.int32Values.add(REQUEST_ID_PLACE_HOLDER);
+ propResponse.value.int32Values.add(SwitchUserMessageType.VEHICLE_RESPONSE);
+ propResponse.value.int32Values.add(/*status =*/ 110); // an invalid status
+
+ AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
+ SWITCH_USER, propResponse, /* rightRequestId= */ true);
+
+ GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
+ CALLBACK_TIMEOUT_SUCCESS);
+ mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+
+ callback.assertCalled();
+
+ // Make sure the arguments were properly converted
+ assertSwitchUserSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
+
+ // Assert response
+ assertCallbackStatus(callback, HalCallback.STATUS_WRONG_HAL_RESPONSE);
+ assertThat(callback.response).isNull();
+ }
+
/**
* Asserts the given {@link UsersInfo} is properly represented in the {@link VehiclePropValue}.
*
@@ -455,7 +655,17 @@
assertUsersInfo(req, mUsersInfo, 2);
}
- private void assertCallbackStatus(GenericHalCallback<InitialUserInfoResponse> callback,
+ private void assertSwitchUserSetRequest(VehiclePropValue req, int messageType,
+ UserInfo targetUserInfo) {
+ assertThat(req.value.int32Values.get(1)).isEqualTo(messageType);
+ assertWithMessage("targetuser.id mismatch").that(req.value.int32Values.get(2))
+ .isEqualTo(targetUserInfo.userId);
+ assertWithMessage("targetuser.flags mismatch").that(req.value.int32Values.get(3))
+ .isEqualTo(targetUserInfo.flags);
+ assertUsersInfo(req, mUsersInfo, 4);
+ }
+
+ private void assertCallbackStatus(GenericHalCallback callback,
int expectedStatus) {
int actualStatus = callback.status;
if (actualStatus == expectedStatus) return;
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java
index 93d9500..d9b9b14 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java
@@ -35,6 +35,9 @@
import android.car.hardware.power.CarPowerManager;
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
import android.car.settings.CarSettings;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleEvent;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -91,7 +94,7 @@
private ArgumentCaptor<BroadcastReceiver> mDisplayBroadcastReceiver;
@Captor
- private ArgumentCaptor<CarUserService.UserCallback> mUserCallback;
+ private ArgumentCaptor<UserLifecycleListener> mUserLifecycleListenerArgumentCaptor;
@Captor
private ArgumentCaptor<CarPowerStateListener> mPowerStateListener;
@@ -132,7 +135,8 @@
doReturn(1).when(mMockPackageManager).getPackageUidAsUser(any(), anyInt());
mCarUserNoticeService = new CarUserNoticeService(mMockContext, mHandler);
mCarUserNoticeService.init();
- verify(mMockCarUserService).addUserCallback(mUserCallback.capture());
+ verify(mMockCarUserService).addUserLifecycleListener(
+ mUserLifecycleListenerArgumentCaptor.capture());
verify(mMockContext).registerReceiver(mDisplayBroadcastReceiver.capture(),
any(IntentFilter.class));
verify(mCarPowerManager).setListener(mPowerStateListener.capture());
@@ -230,9 +234,12 @@
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
}
- private void switchUser(int usrId) throws Exception {
- // Switch User callback
- mUserCallback.getValue().onSwitchUser(usrId);
+ private void switchUser(int userId) throws Exception {
+ // Notify listeners about user switch.
+ mUserLifecycleListenerArgumentCaptor.getValue().onEvent(new UserLifecycleEvent(
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ /* from= */ null,
+ /* to= */ UserHandle.of(userId)));
}
private CountDownLatch mockBindService() {
diff --git a/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl b/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl
index ae11b4a..8368a81 100644
--- a/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl
+++ b/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl
@@ -119,11 +119,12 @@
* The caller should have system UID.
*
* @param type One of the change types defined in the StateType enum.
- * @param args State change information for the specified type.
+ * @param arg1 First state change information for the specified type.
+ * @param arg2 Second state change information for the specified type.
*
- * When type is POWER_CYCLE, args should contain the current power cycle of the device.
- * When type is USER_STATE, args should contain the user ID and current user state.
- * When type is BOOT_PHASE, args should contain the current boot phase.
+ * When type is POWER_CYCLE, arg1 should contain the current power cycle of the device.
+ * When type is USER_STATE, arg1 and arg2 should contain the user ID and the current user state.
+ * When type is BOOT_PHASE, arg1 should contain the current boot phase.
*/
- void notifySystemStateChange(in StateType type, in @utf8InCpp List<String> args);
+ void notifySystemStateChange(in StateType type, in int arg1, in int arg2);
}
diff --git a/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java b/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
index 1580f84..45bd8ff 100644
--- a/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
+++ b/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
@@ -33,7 +33,6 @@
import com.android.internal.annotations.GuardedBy;
-import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -155,9 +154,9 @@
* @param client Car watchdog client to be registered.
* @param timeout Time within which the client should respond.
* @throws IllegalArgumentException If the client is already registered.
+ * @throws RemoteException
*/
- public void registerClient(ICarWatchdogClient client, int timeout)
- throws IllegalArgumentException, RemoteException {
+ public void registerClient(ICarWatchdogClient client, int timeout) throws RemoteException {
invokeDaemonMethod((daemon) -> daemon.registerClient(client, timeout));
}
@@ -166,9 +165,9 @@
*
* @param client Car watchdog client to be unregistered.
* @throws IllegalArgumentException If the client is not registered.
+ * @throws RemoteException
*/
- public void unregisterClient(ICarWatchdogClient client)
- throws IllegalArgumentException, RemoteException {
+ public void unregisterClient(ICarWatchdogClient client) throws RemoteException {
invokeDaemonMethod((daemon) -> daemon.unregisterClient(client));
}
@@ -177,9 +176,9 @@
*
* @param mediator Car watchdog client to be registered.
* @throws IllegalArgumentException If the mediator is already registered.
+ * @throws RemoteException
*/
- public void registerMediator(ICarWatchdogClient mediator)
- throws IllegalArgumentException, RemoteException {
+ public void registerMediator(ICarWatchdogClient mediator) throws RemoteException {
invokeDaemonMethod((daemon) -> daemon.registerMediator(mediator));
}
@@ -188,9 +187,9 @@
*
* @param mediator Car watchdog client to be unregistered.
* @throws IllegalArgumentException If the mediator is not registered.
+ * @throws RemoteException
*/
- public void unregisterMediator(ICarWatchdogClient mediator)
- throws IllegalArgumentException, RemoteException {
+ public void unregisterMediator(ICarWatchdogClient mediator) throws RemoteException {
invokeDaemonMethod((daemon) -> daemon.unregisterMediator(mediator));
}
@@ -199,9 +198,9 @@
*
* @param monitor Car watchdog monitor to be registered.
* @throws IllegalArgumentException If there is another monitor registered.
+ * @throws RemoteException
*/
- public void registerMonitor(ICarWatchdogMonitor monitor)
- throws IllegalArgumentException, RemoteException {
+ public void registerMonitor(ICarWatchdogMonitor monitor) throws RemoteException {
invokeDaemonMethod((daemon) -> daemon.registerMonitor(monitor));
}
@@ -210,9 +209,9 @@
*
* @param monitor Car watchdog monitor to be unregistered.
* @throws IllegalArgumentException If the monitor is not registered.
+ * @throws RemoteException
*/
- public void unregisterMonitor(ICarWatchdogMonitor monitor)
- throws IllegalArgumentException, RemoteException {
+ public void unregisterMonitor(ICarWatchdogMonitor monitor) throws RemoteException {
invokeDaemonMethod((daemon) -> daemon.unregisterMonitor(monitor));
}
@@ -223,9 +222,9 @@
* @param sessionId Session ID that car watchdog daemon has given.
* @throws IllegalArgumentException If the client is not registered,
* or session ID is not correct.
+ * @throws RemoteException
*/
- public void tellClientAlive(ICarWatchdogClient client, int sessionId)
- throws IllegalArgumentException, RemoteException {
+ public void tellClientAlive(ICarWatchdogClient client, int sessionId) throws RemoteException {
invokeDaemonMethod((daemon) -> daemon.tellClientAlive(client, sessionId));
}
@@ -237,9 +236,10 @@
* @param sessionId Session ID that car watchdog daemon has given.
* @throws IllegalArgumentException If the client is not registered,
* or session ID is not correct.
+ * @throws RemoteException
*/
public void tellMediatorAlive(ICarWatchdogClient mediator, int[] clientsNotResponding,
- int sessionId) throws IllegalArgumentException, RemoteException {
+ int sessionId) throws RemoteException {
invokeDaemonMethod(
(daemon) -> daemon.tellMediatorAlive(mediator, clientsNotResponding, sessionId));
}
@@ -250,20 +250,25 @@
* @param monitor Car watchdog monitor that dumped process information.
* @param pid ID of process that has been dumped.
* @throws IllegalArgumentException If the monitor is not registered.
+ * @throws RemoteException
*/
- public void tellDumpFinished(ICarWatchdogMonitor monitor, int pid)
- throws IllegalArgumentException, RemoteException {
+ public void tellDumpFinished(ICarWatchdogMonitor monitor, int pid) throws RemoteException {
invokeDaemonMethod((daemon) -> daemon.tellDumpFinished(monitor, pid));
}
/**
* Tells car watchdog daemon that system state has been changed for the specified StateType.
*
- * @param StateType Either PowerCycle, UserState, or BootPhase
- * @param args Args explaining the state change for the specified state type.
+ * @param type Either PowerCycle, UserState, or BootPhase
+ * @param arg1 First state change information for the specified state type.
+ * @param arg2 Second state change information for the specified state type.
+ * @throws IllegalArgumentException If the args don't match the state type. Refer to the aidl
+ * interface for more information on the args.
+ * @throws RemoteException
*/
- public void notifySystemStateChange(int type, List<String> args) throws RemoteException {
- invokeDaemonMethod((daemon) -> daemon.notifySystemStateChange(type, args));
+ public void notifySystemStateChange(int type, int arg1, int arg2)
+ throws IllegalArgumentException, RemoteException {
+ invokeDaemonMethod((daemon) -> daemon.notifySystemStateChange(type, arg1, arg2));
}
private void invokeDaemonMethod(Invokable r) throws IllegalArgumentException, RemoteException {
diff --git a/watchdog/server/src/WatchdogBinderMediator.cpp b/watchdog/server/src/WatchdogBinderMediator.cpp
index aedd27c..68c944a 100644
--- a/watchdog/server/src/WatchdogBinderMediator.cpp
+++ b/watchdog/server/src/WatchdogBinderMediator.cpp
@@ -140,28 +140,14 @@
return mWatchdogProcessService->unregisterMonitor(monitor);
}
-Status WatchdogBinderMediator::notifySystemStateChange(StateType type,
- const std::vector<std::string>& args) {
+Status WatchdogBinderMediator::notifySystemStateChange(StateType type, int32_t arg1, int32_t arg2) {
Status status = checkSystemPermission();
if (!status.isOk()) {
return status;
}
switch (type) {
case StateType::POWER_CYCLE: {
- if (args.size() != 1) {
- return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
- StringPrintf("Expected exactly one argument for %s "
- "change, got %zu",
- toString(StateType::POWER_CYCLE).c_str(),
- args.size()));
- }
- uint32_t powerCycleArg = 0;
- if (!ParseUint(args[0], &powerCycleArg)) {
- return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
- StringPrintf("Failed to parse power cycle argument %s",
- args[0].c_str()));
- }
- auto powerCycle = static_cast<PowerCycle>(powerCycleArg);
+ PowerCycle powerCycle = static_cast<PowerCycle>(static_cast<uint32_t>(arg1));
if (powerCycle >= PowerCycle::NUM_POWER_CYLES) {
return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
StringPrintf("Invalid power cycle %d", powerCycle));
@@ -169,26 +155,8 @@
return mWatchdogProcessService->notifyPowerCycleChange(powerCycle);
}
case StateType::USER_STATE: {
- if (args.size() != 2) {
- return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
- StringPrintf("Expected exactly two arguments for %s "
- "change, got %zu",
- toString(StateType::USER_STATE).c_str(),
- args.size()));
- }
- userid_t userId = 0;
- if (!ParseUint(args[0], &userId)) {
- return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
- StringPrintf("Failed to parse user ID argument %s",
- args[0].c_str()));
- }
- uint32_t userStateArg = 0;
- if (!ParseUint(args[1], &userStateArg)) {
- return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
- StringPrintf("Failed to parse user state argument %s",
- args[0].c_str()));
- }
- auto userState = static_cast<UserState>(userStateArg);
+ userid_t userId = static_cast<userid_t>(arg1);
+ UserState userState = static_cast<UserState>(static_cast<uint32_t>(arg2));
if (userState >= UserState::NUM_USER_STATES) {
return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
StringPrintf("Invalid user state %d", userState));
@@ -196,20 +164,8 @@
return mWatchdogProcessService->notifyUserStateChange(userId, userState);
}
case StateType::BOOT_PHASE: {
- if (args.size() != 1) {
- return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
- StringPrintf("Expacted exactly one argument for %s "
- "change, got %zu",
- toString(StateType::BOOT_PHASE).c_str(),
- args.size()));
- }
- uint32_t phase = 0;
- if (!ParseUint(args[0], &phase)) {
- return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
- StringPrintf("Failed to parse boot phase argument %s",
- args[0].c_str()));
- }
- if (static_cast<BootPhase>(phase) >= BootPhase::BOOT_COMPLETED) {
+ BootPhase phase = static_cast<BootPhase>(static_cast<uint32_t>(arg1));
+ if (phase >= BootPhase::BOOT_COMPLETED) {
/*auto ret = mIoPerfCollection->onBootFinished();
if (!ret.ok()) {
return fromExceptionCode(ret.error().code(), ret.error().message());
diff --git a/watchdog/server/src/WatchdogBinderMediator.h b/watchdog/server/src/WatchdogBinderMediator.h
index 1041c8e..a3b19a2 100644
--- a/watchdog/server/src/WatchdogBinderMediator.h
+++ b/watchdog/server/src/WatchdogBinderMediator.h
@@ -69,8 +69,7 @@
int32_t pid) override {
return mWatchdogProcessService->tellDumpFinished(monitor, pid);
}
- binder::Status notifySystemStateChange(StateType type,
- const std::vector<std::string>& args) override;
+ binder::Status notifySystemStateChange(StateType type, int32_t arg1, int32_t arg2) override;
protected:
android::base::Result<void> init(android::sp<WatchdogProcessService> watchdogProcessService,
diff --git a/watchdog/server/src/WatchdogProcessService.cpp b/watchdog/server/src/WatchdogProcessService.cpp
index bbb08c5..51d1f07 100644
--- a/watchdog/server/src/WatchdogProcessService.cpp
+++ b/watchdog/server/src/WatchdogProcessService.cpp
@@ -21,6 +21,7 @@
#include <android-base/chrono_utils.h>
#include <android-base/file.h>
+#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <binder/IPCThreadState.h>
@@ -30,6 +31,7 @@
using std::literals::chrono_literals::operator""s;
using android::base::Error;
+using android::base::GetProperty;
using android::base::Result;
using android::base::StringAppendF;
using android::base::StringPrintf;
@@ -67,14 +69,22 @@
return buffer;
}
+bool isSystemShuttingDown() {
+ std::string sysPowerCtl;
+ std::istringstream tokenStream(GetProperty("sys.powerctl", ""));
+ std::getline(tokenStream, sysPowerCtl, ',');
+ return sysPowerCtl == "reboot" || sysPowerCtl == "shutdown";
+}
+
} // namespace
WatchdogProcessService::WatchdogProcessService(const sp<Looper>& handlerLooper) :
mHandlerLooper(handlerLooper), mLastSessionId(0) {
mMessageHandler = new MessageHandlerImpl(this);
+ mWatchdogEnabled = true;
for (const auto& timeout : kTimeouts) {
mClients.insert(std::make_pair(timeout, std::vector<ClientInfo>()));
- mPingedClients.insert(std::make_pair(timeout, PingedClientSet()));
+ mPingedClients.insert(std::make_pair(timeout, PingedClientMap()));
}
}
@@ -187,8 +197,35 @@
}
Status WatchdogProcessService::notifyPowerCycleChange(PowerCycle cycle) {
- // TODO(b/148884065): implement this method.
- (void)cycle;
+ std::string buffer;
+ Mutex::Autolock lock(mMutex);
+ bool oldStatus = mWatchdogEnabled;
+ switch (cycle) {
+ case PowerCycle::POWER_CYCLE_SHUTDOWN:
+ mWatchdogEnabled = false;
+ buffer = "SHUTDOWN power cycle";
+ break;
+ case PowerCycle::POWER_CYCLE_SUSPEND:
+ mWatchdogEnabled = false;
+ buffer = "SUSPEND power cycle";
+ break;
+ case PowerCycle::POWER_CYCLE_RESUME:
+ mWatchdogEnabled = true;
+ for (const auto& timeout : kTimeouts) {
+ startHealthCheckingLocked(timeout);
+ }
+ buffer = "RESUME power cycle";
+ break;
+ default:
+ ALOGW("Unsupported power cycle: %d", cycle);
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
+ "The monitor is not registered or an invalid monitor "
+ "is given");
+ }
+ ALOGI("Received %s", buffer.c_str());
+ if (oldStatus != mWatchdogEnabled) {
+ ALOGI("Car watchdog is %s", mWatchdogEnabled ? "enabled" : "disabled");
+ }
return Status::ok();
}
@@ -201,55 +238,65 @@
Result<void> WatchdogProcessService::dump(int fd, const Vector<String16>& /*args*/) {
Mutex::Autolock lock(mMutex);
+ const char* indent = " ";
+ const char* doubleIndent = " ";
std::string buffer;
WriteStringToFd("CAR WATCHDOG PROCESS SERVICE\n", fd);
- WriteStringToFd(" Registered clients\n", fd);
+ WriteStringToFd(StringPrintf("%sWatchdog enabled: %s\n", indent,
+ mWatchdogEnabled ? "true" : "false"),
+ fd);
+ WriteStringToFd(StringPrintf("%sRegistered clients\n", indent), fd);
int count = 1;
for (const auto& timeout : kTimeouts) {
std::vector<ClientInfo>& clients = mClients[timeout];
for (auto it = clients.begin(); it != clients.end(); it++, count++) {
- WriteStringToFd(StringPrintf(" Client #%d: %s\n", count, it->toString().c_str()),
+ WriteStringToFd(StringPrintf("%sClient #%d: %s\n", doubleIndent, count,
+ it->toString().c_str()),
fd);
}
}
- WriteStringToFd(StringPrintf("\n Monitor registered: %s\n",
+ WriteStringToFd(StringPrintf("\n%sMonitor registered: %s\n", indent,
mMonitor == nullptr ? "false" : "true"),
fd);
+ WriteStringToFd(StringPrintf("%sisSystemShuttingDown: %s\n", indent,
+ isSystemShuttingDown() ? "true" : "false"),
+ fd);
return {};
}
void WatchdogProcessService::doHealthCheck(int what) {
mHandlerLooper->removeMessages(mMessageHandler, what);
+ if (!isWatchdogEnabled()) {
+ return;
+ }
const TimeoutLength timeout = static_cast<TimeoutLength>(what);
- std::vector<ClientInfo> clientsToCheck;
- PingedClientSet& pingedClients = mPingedClients[timeout];
-
dumpAndKillClientsIfNotResponding(timeout);
/* Generates a temporary/local vector containing clients.
* Using a local copy may send unnecessary ping messages to clients after they are unregistered.
* Clients should be able to handle them.
*/
+ std::vector<ClientInfo> clientsToCheck;
+ PingedClientMap& pingedClients = mPingedClients[timeout];
{
Mutex::Autolock lock(mMutex);
- clientsToCheck = mClients[timeout];
pingedClients.clear();
+ clientsToCheck = mClients[timeout];
+ for (auto& clientInfo : clientsToCheck) {
+ int sessionId = getNewSessionId();
+ clientInfo.sessionId = sessionId;
+ pingedClients.insert(std::make_pair(sessionId, clientInfo));
+ }
}
for (const auto& clientInfo : clientsToCheck) {
- int32_t sessionId = getNewSessionId();
- PingedClient targetClient(clientInfo.client, sessionId);
- {
- Mutex::Autolock lock(mMutex);
- pingedClients.insert(targetClient);
- }
- Status status = clientInfo.client->checkIfAlive(sessionId, timeout);
+ Status status = clientInfo.client->checkIfAlive(clientInfo.sessionId, timeout);
if (!status.isOk()) {
ALOGW("Sending a ping message to client(pid: %d) failed: %s", clientInfo.pid,
status.exceptionMessage().c_str());
{
Mutex::Autolock lock(mMutex);
- pingedClients.erase(targetClient);
+ pingedClients.erase(clientInfo.sessionId);
}
}
}
@@ -318,7 +365,7 @@
// If the client array becomes non-empty, start health checking.
if (clients.size() == 1) {
- startHealthChecking(timeout);
+ startHealthCheckingLocked(timeout);
}
if (DEBUG) {
ALOGD("Car watchdog %s(pid: %d, timeout: %d) is registered", clientName, callingPid,
@@ -350,11 +397,11 @@
Status WatchdogProcessService::tellClientAliveLocked(const sp<ICarWatchdogClient>& client,
int32_t sessionId) {
+ const sp<IBinder> binder = BnCarWatchdog::asBinder(client);
for (const auto& timeout : kTimeouts) {
- PingedClientSet& clients = mPingedClients[timeout];
- PingedClient respondingClient(client, sessionId);
- PingedClientSet::const_iterator it = clients.find(respondingClient);
- if (it == clients.cend()) {
+ PingedClientMap& clients = mPingedClients[timeout];
+ PingedClientMap::const_iterator it = clients.find(sessionId);
+ if (it == clients.cend() || binder != BnCarWatchdog::asBinder(it->second.client)) {
continue;
}
clients.erase(it);
@@ -382,7 +429,9 @@
return false;
}
-Result<void> WatchdogProcessService::startHealthChecking(TimeoutLength timeout) {
+Result<void> WatchdogProcessService::startHealthCheckingLocked(TimeoutLength timeout) {
+ PingedClientMap& clients = mPingedClients[timeout];
+ clients.clear();
int what = static_cast<int>(timeout);
auto durationNs = timeoutToDurationNs(timeout);
mHandlerLooper->sendMessageDelayed(durationNs.count(), mMessageHandler, Message(what));
@@ -393,10 +442,10 @@
std::vector<int32_t> processIds;
{
Mutex::Autolock lock(mMutex);
- PingedClientSet& clients = mPingedClients[timeout];
- for (PingedClientSet::const_iterator it = clients.cbegin(); it != clients.cend(); it++) {
+ PingedClientMap& clients = mPingedClients[timeout];
+ for (PingedClientMap::const_iterator it = clients.cbegin(); it != clients.cend(); it++) {
pid_t pid = -1;
- sp<IBinder> binder = BnCarWatchdog::asBinder((*it).client);
+ sp<IBinder> binder = BnCarWatchdog::asBinder(it->second.client);
std::vector<TimeoutLength> timeouts = {timeout};
// Unhealthy clients are eventually removed from the list through binderDied when they
// are killed.
@@ -419,11 +468,11 @@
if (size == 0) {
return {};
}
+ std::string pidString = pidArrayToString(processesNotResponding);
sp<ICarWatchdogMonitor> monitor;
{
Mutex::Autolock lock(mMutex);
if (mMonitor == nullptr) {
- std::string pidString = pidArrayToString(processesNotResponding);
std::string errorMsg =
StringPrintf("Cannot dump and kill processes(pid = %s): Monitor is not set",
pidString.c_str());
@@ -432,9 +481,13 @@
}
monitor = mMonitor;
}
+ if (isSystemShuttingDown()) {
+ ALOGI("Skip dumping and killing processes(%s): The system is shutting down",
+ pidString.c_str());
+ return {};
+ }
monitor->onClientsNotResponding(processesNotResponding);
if (DEBUG) {
- std::string pidString = pidArrayToString(processesNotResponding);
ALOGD("Dumping and killing processes is requested: %s", pidString.c_str());
}
return {};
@@ -448,6 +501,11 @@
return mLastSessionId;
}
+bool WatchdogProcessService::isWatchdogEnabled() {
+ Mutex::Autolock lock(mMutex);
+ return mWatchdogEnabled;
+}
+
std::string WatchdogProcessService::ClientInfo::toString() {
std::string buffer;
StringAppendF(&buffer, "pid = %d, type = %s", pid, type == Regular ? "Regular" : "Mediator");
diff --git a/watchdog/server/src/WatchdogProcessService.h b/watchdog/server/src/WatchdogProcessService.h
index c76936e..d5e389a 100644
--- a/watchdog/server/src/WatchdogProcessService.h
+++ b/watchdog/server/src/WatchdogProcessService.h
@@ -77,26 +77,11 @@
android::sp<ICarWatchdogClient> client;
pid_t pid;
+ int sessionId;
ClientType type;
};
- struct PingedClient {
- PingedClient(const android::sp<ICarWatchdogClient>& client, int32_t sessionId) :
- client(client), sessionId(sessionId) {}
-
- bool operator==(const PingedClient& other) const { return sessionId == other.sessionId; }
-
- android::sp<ICarWatchdogClient> client;
- int32_t sessionId;
- };
-
- struct PingedClientHash {
- std::size_t operator()(const PingedClient& pingedClient) const {
- return pingedClient.sessionId;
- }
- };
-
- typedef std::unordered_set<PingedClient, PingedClientHash> PingedClientSet;
+ typedef std::unordered_map<int, ClientInfo> PingedClientMap;
class MessageHandlerImpl : public MessageHandler {
public:
@@ -116,10 +101,11 @@
bool isRegisteredLocked(const android::sp<ICarWatchdogClient>& client);
binder::Status tellClientAliveLocked(const android::sp<ICarWatchdogClient>& client,
int32_t sessionId);
- base::Result<void> startHealthChecking(TimeoutLength timeout);
+ base::Result<void> startHealthCheckingLocked(TimeoutLength timeout);
base::Result<void> dumpAndKillClientsIfNotResponding(TimeoutLength timeout);
base::Result<void> dumpAndKillAllProcesses(const std::vector<int32_t>& processesNotResponding);
int32_t getNewSessionId();
+ bool isWatchdogEnabled();
using Processor =
std::function<void(std::vector<ClientInfo>&, std::vector<ClientInfo>::const_iterator)>;
@@ -127,12 +113,13 @@
const android::sp<IBinder> binder, const Processor& processor);
private:
- Mutex mMutex;
sp<Looper> mHandlerLooper;
android::sp<MessageHandlerImpl> mMessageHandler;
+ Mutex mMutex;
std::unordered_map<TimeoutLength, std::vector<ClientInfo>> mClients GUARDED_BY(mMutex);
- std::unordered_map<TimeoutLength, PingedClientSet> mPingedClients GUARDED_BY(mMutex);
+ std::unordered_map<TimeoutLength, PingedClientMap> mPingedClients GUARDED_BY(mMutex);
android::sp<ICarWatchdogMonitor> mMonitor GUARDED_BY(mMutex);
+ bool mWatchdogEnabled GUARDED_BY(mMutex);
// mLastSessionId is accessed only within main thread. No need for mutual-exclusion.
int32_t mLastSessionId;
};
diff --git a/watchdog/server/tests/WatchdogBinderMediatorTest.cpp b/watchdog/server/tests/WatchdogBinderMediatorTest.cpp
index b2155c9..cb533a2 100644
--- a/watchdog/server/tests/WatchdogBinderMediatorTest.cpp
+++ b/watchdog/server/tests/WatchdogBinderMediatorTest.cpp
@@ -286,102 +286,61 @@
TEST_F(WatchdogBinderMediatorTest, TestErrorOnNotifyStateChangeWithNonSystemCallingUid) {
StateType type = StateType::POWER_CYCLE;
- std::vector<std::string> args = {
- std::to_string(static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND))};
EXPECT_CALL(*mMockWatchdogProcessService, notifyPowerCycleChange(_)).Times(0);
- Status status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+ Status status =
+ mWatchdogBinderMediator
+ ->notifySystemStateChange(type,
+ static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND),
+ -1);
ASSERT_FALSE(status.isOk()) << status;
}
TEST_F(WatchdogBinderMediatorTest, TestNotifyPowerCycleChange) {
setSystemCallingUid();
StateType type = StateType::POWER_CYCLE;
- std::vector<std::string> args = {
- std::to_string(static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND))};
EXPECT_CALL(*mMockWatchdogProcessService,
notifyPowerCycleChange(PowerCycle::POWER_CYCLE_SUSPEND))
.WillOnce(Return(Status::ok()));
- Status status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+ Status status =
+ mWatchdogBinderMediator
+ ->notifySystemStateChange(type,
+ static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND),
+ -1);
ASSERT_TRUE(status.isOk()) << status;
}
TEST_F(WatchdogBinderMediatorTest, TestErrorOnNotifyPowerCycleChangeWithInvalidArgs) {
EXPECT_CALL(*mMockWatchdogProcessService, notifyPowerCycleChange(_)).Times(0);
StateType type = StateType::POWER_CYCLE;
- std::vector<std::string> args = {std::to_string(
- static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND)),
- std::to_string(
- static_cast<int32_t>(PowerCycle::POWER_CYCLE_RESUME))};
- Status status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+
+ Status status = mWatchdogBinderMediator->notifySystemStateChange(type, -1, -1);
ASSERT_FALSE(status.isOk()) << status;
- args.clear();
- args.push_back("-1");
- status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
- ASSERT_FALSE(status.isOk()) << status;
-
- args.clear();
- args.push_back("3000");
- status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
- ASSERT_FALSE(status.isOk()) << status;
-
- args.clear();
- args.push_back("invalid_power_cycle");
- status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+ status = mWatchdogBinderMediator->notifySystemStateChange(type, 3000, -1);
ASSERT_FALSE(status.isOk()) << status;
}
TEST_F(WatchdogBinderMediatorTest, TestNotifyUserStateChange) {
setSystemCallingUid();
StateType type = StateType::USER_STATE;
- std::vector<std::string> args = {"234567",
- std::to_string(
- static_cast<int32_t>(UserState::USER_STATE_STOPPED))};
EXPECT_CALL(*mMockWatchdogProcessService,
notifyUserStateChange(234567, UserState::USER_STATE_STOPPED))
.WillOnce(Return(Status::ok()));
- Status status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+ Status status =
+ mWatchdogBinderMediator
+ ->notifySystemStateChange(type, 234567,
+ static_cast<int32_t>(UserState::USER_STATE_STOPPED));
ASSERT_TRUE(status.isOk()) << status;
}
TEST_F(WatchdogBinderMediatorTest, TestErrorOnNotifyUserStateChangeWithInvalidArgs) {
EXPECT_CALL(*mMockWatchdogProcessService, notifyUserStateChange(_, _)).Times(0);
- StateType type = StateType::POWER_CYCLE;
- std::vector<std::string> args = {"234567",
- std::to_string(
- static_cast<int32_t>(UserState::USER_STATE_STOPPED)),
- "extra_arg"};
- Status status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+ StateType type = StateType::USER_STATE;
+
+ Status status = mWatchdogBinderMediator->notifySystemStateChange(type, 234567, -1);
ASSERT_FALSE(status.isOk()) << status;
- args.clear();
- args.push_back("-1");
- args.push_back(std::to_string(static_cast<int32_t>(UserState::USER_STATE_STOPPED)));
- status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
- ASSERT_FALSE(status.isOk()) << status;
-
- args.clear();
- args.push_back("invalid_user_id");
- args.push_back(std::to_string(static_cast<int32_t>(UserState::USER_STATE_STOPPED)));
- status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
- ASSERT_FALSE(status.isOk()) << status;
-
- args.clear();
- args.push_back("234567");
- args.push_back("-1");
- status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
- ASSERT_FALSE(status.isOk()) << status;
-
- args.clear();
- args.push_back("234567");
- args.push_back("3000");
- status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
- ASSERT_FALSE(status.isOk()) << status;
-
- args.clear();
- args.push_back("234567");
- args.push_back("invalid_user_state");
- status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+ status = mWatchdogBinderMediator->notifySystemStateChange(type, 234567, 3000);
ASSERT_FALSE(status.isOk()) << status;
}
diff --git a/watchdog/testclient/src/WatchdogClient.cpp b/watchdog/testclient/src/WatchdogClient.cpp
index abc10ef..41a2b26 100644
--- a/watchdog/testclient/src/WatchdogClient.cpp
+++ b/watchdog/testclient/src/WatchdogClient.cpp
@@ -49,6 +49,9 @@
}
ndk::ScopedAStatus WatchdogClient::checkIfAlive(int32_t sessionId, TimeoutLength timeout) {
+ if (mVerbose) {
+ ALOGI("Pinged by car watchdog daemon: session id = %d", sessionId);
+ }
Mutex::Autolock lock(mMutex);
mHandlerLooper->removeMessages(mMessageHandler, WHAT_CHECK_ALIVE);
mSession = HealthCheckSession(sessionId, timeout);
@@ -74,6 +77,7 @@
mIsClientActive = true;
}
mForcedKill = param.forcedKill;
+ mVerbose = param.verbose;
registerClient(param.timeout);
if (param.inactiveAfterInSec >= 0) {
@@ -115,11 +119,17 @@
ALOGE("Failed to call binder interface: %d", status.getStatus());
return;
}
+ if (mVerbose) {
+ ALOGI("Sent response to car watchdog daemon: session id = %d", sessionId);
+ }
}
void WatchdogClient::becomeInactive() {
Mutex::Autolock lock(mMutex);
mIsClientActive = false;
+ if (mVerbose) {
+ ALOGI("Became inactive");
+ }
}
void WatchdogClient::terminateProcess() {
diff --git a/watchdog/testclient/src/WatchdogClient.h b/watchdog/testclient/src/WatchdogClient.h
index 2073598..95f11ca 100644
--- a/watchdog/testclient/src/WatchdogClient.h
+++ b/watchdog/testclient/src/WatchdogClient.h
@@ -33,6 +33,7 @@
int inactiveAfterInSec;
int terminateAfterInSec;
bool forcedKill;
+ bool verbose;
};
struct HealthCheckSession {
@@ -75,6 +76,7 @@
::android::sp<::android::Looper> mHandlerLooper;
::android::sp<MessageHandlerImpl> mMessageHandler;
bool mForcedKill;
+ bool mVerbose;
::android::Mutex mMutex;
std::shared_ptr<ICarWatchdog> mWatchdogServer GUARDED_BY(mMutex);
std::shared_ptr<ICarWatchdogClient> mTestClient GUARDED_BY(mMutex);
diff --git a/watchdog/testclient/src/main.cpp b/watchdog/testclient/src/main.cpp
index 5b24173..440069d 100644
--- a/watchdog/testclient/src/main.cpp
+++ b/watchdog/testclient/src/main.cpp
@@ -34,7 +34,7 @@
Result<CommandParam> checkArgument(int argc, char** argv) {
CommandParam param;
- if (argc != 4 && argc != 5) {
+ if (argc < 4) {
return Error() << "Invalid syntax";
}
if (strcmp(argv[1], "critical") && strcmp(argv[1], "moderate") && strcmp(argv[1], "normal")) {
@@ -49,22 +49,27 @@
if (!ParseInt(strValue, ¶m.terminateAfterInSec)) {
return Error() << "Invalid terminate after time";
}
- if (argc == 5) {
- if (strcmp(argv[4], "--forcedkill")) {
+ param.forcedKill = false;
+ param.verbose = false;
+ for (int i = 4; i < argc; i++) {
+ if (!strcmp(argv[i], "--forcedkill")) {
+ param.forcedKill = true;
+ } else if (!strcmp(argv[i], "--verbose")) {
+ param.verbose = true;
+ } else {
return Error() << "Invalid option";
}
- param.forcedKill = true;
- } else {
- param.forcedKill = false;
}
return param;
}
/**
* Usage: carwatchdog_testclient [timeout] [inactive_after] [terminate_after] [--forcedkill]
+ * [--verbose]
* timeout: critical|moderate|normal
* inactive_after: number in seconds. -1 for never being inactive.
* terminate_after: number in seconds. -1 for running forever.
* --forcedkill: terminate without unregistering from car watchdog daemon.
+ * --verbose: output verbose logs.
*/
int main(int argc, char** argv) {
sp<Looper> looper(Looper::prepare(/*opts=*/0));
@@ -81,7 +86,8 @@
ALOGE("timeout: critical|moderate|normal");
ALOGE("inactive_after: number in seconds (-1 for never being inactive)");
ALOGE("terminate_after: number in seconds (-1 for running forever)");
- ALOGE("--forcedkill: terminate without unregistering from car watchdog daemone");
+ ALOGE("--forcedkill: terminate without unregistering from car watchdog daemon");
+ ALOGE("--verbose: output verbose logs");
return 1;
}
if (!service->initialize(*param)) {