Merge "Update to the latest core lib .so" into rvc-dev
diff --git a/FrameworkPackageStubs/AndroidManifest.xml b/FrameworkPackageStubs/AndroidManifest.xml
index 912944c..6c25bcd 100644
--- a/FrameworkPackageStubs/AndroidManifest.xml
+++ b/FrameworkPackageStubs/AndroidManifest.xml
@@ -95,6 +95,10 @@
<action android:name="android.settings.USER_DICTIONARY_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
+ <intent-filter android:priority="-1">
+ <action android:name="android.settings.PICTURE_IN_PICTURE_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
</activity>
<!-- CDD Core Application Intents Stubs -->
diff --git a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
index 0c634ae..f404172 100644
--- a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
+++ b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
@@ -82,6 +82,8 @@
150116 car_user_svc_create_user_resp (status|1),(result|1),(error_message|3)
150117 car_user_svc_create_user_user_created (user_id|1),(safe_name|3),(user_type|3),(flags|1)
150118 car_user_svc_create_user_user_removed (user_id|1),(reason|3)
+150119 car_user_svc_remove_user_req (user_id|1)
+150120 car_user_svc_remove_user_resp (user_id|1),(result|1)
150140 car_user_hal_initial_user_info_req (request_id|1),(request_type|1),(timeout|1)
150141 car_user_hal_initial_user_info_resp (request_id|1),(status|1),(action|1),(user_id|1),(flags|1),(safe_name|3),(user_locales|3)
@@ -96,15 +98,18 @@
150150 car_user_hal_oem_switch_user_req (request_id|1),(target_user_id|1)
150151 car_user_hal_create_user_req (request_id|1),(safe_name|3),(flags|1),(timeout|1)
150152 car_user_hal_create_user_resp (request_id|1),(status|1),(result|1),(error_message|3)
+150153 car_user_hal_remove_user_req (target_user_id|1),(current_user_id|1)
150171 car_user_mgr_add_listener (uid|1)
150172 car_user_mgr_remove_listener (uid|1)
150173 car_user_mgr_disconnected (uid|1)
-150174 car_user_mgr_switch_user_request (uid|1),(user_id|1)
-150175 car_user_mgr_switch_user_response (uid|1),(status|1),(error_message|3)
+150174 car_user_mgr_switch_user_req (uid|1),(user_id|1)
+150175 car_user_mgr_switch_user_resp (uid|1),(status|1),(error_message|3)
150176 car_user_mgr_get_user_auth_req (types|4)
150177 car_user_mgr_get_user_auth_resp (values|4)
150178 car_user_mgr_set_user_auth_req (types_and_values_pairs|4)
150179 car_user_mgr_set_user_auth_resp (values|4)
150180 car_user_mgr_create_user_req (uid|1),(safe_name|3),(user_type|3),(flags|1)
150181 car_user_mgr_create_user_resp (uid|1),(status|1),(error_message|3)
+150182 car_user_mgr_remove_user_req (uid|1),(user_id|1)
+150183 car_user_mgr_remove_user_resp (uid|1),(status|1)
\ No newline at end of file
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 4a966d4..c404acd 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -955,6 +955,17 @@
}
+package android.car.settings {
+
+ public class CarSettings {
+ }
+
+ public static final class CarSettings.Secure {
+ field public static final String KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL = "android.car.KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL";
+ }
+
+}
+
package android.car.storagemonitoring {
public final class CarStorageMonitoringManager {
diff --git a/car-lib/src/android/car/CarAppFocusManager.java b/car-lib/src/android/car/CarAppFocusManager.java
index 3d4d90d..cc0d10b 100644
--- a/car-lib/src/android/car/CarAppFocusManager.java
+++ b/car-lib/src/android/car/CarAppFocusManager.java
@@ -45,8 +45,11 @@
/**
* Application focus has changed. Note that {@link CarAppFocusManager} instance
* causing the change will not get this notification.
- * @param appType
- * @param active
+ *
+ * <p>Note that this call can happen for app focus grant, release, and ownership change.
+ *
+ * @param appType appType where the focus change has happened.
+ * @param active {@code true} if there is an active owner for the focus.
*/
void onAppFocusChanged(@AppFocusType int appType, boolean active);
}
diff --git a/car-lib/src/android/car/ICarUserService.aidl b/car-lib/src/android/car/ICarUserService.aidl
index df5e6ab..c5a4c98 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -18,6 +18,7 @@
import android.content.pm.UserInfo;
import android.car.user.UserCreationResult;
+import android.car.user.UserRemovalResult;
import android.car.user.UserIdentificationAssociationResponse;
import android.car.user.UserSwitchResult;
import com.android.internal.infra.AndroidFuture;
@@ -32,6 +33,7 @@
void setUserSwitchUiCallback(in IResultReceiver callback);
void createUser(@nullable String name, String userType, int flags, int timeoutMs,
in AndroidFuture<UserCreationResult> receiver);
+ UserRemovalResult removeUser(int userId);
List<UserInfo> getAllDrivers();
List<UserInfo> getPassengers(int driverId);
boolean startPassenger(int passengerId, int zoneId);
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index 5272548..cdf0cca 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -412,13 +412,13 @@
startActivityAsUser(intent, mActivityOptions.toBundle(), UserHandle.CURRENT);
Log.i(TAG, String.format("Activity launched: %s (options: %s, displayId: %d)",
mActivityOptions, intent, mActivityOptions.getLaunchDisplayId()));
- } catch (ActivityNotFoundException ex) {
+ } catch (ActivityNotFoundException e) {
Log.w(TAG, "Unable to find activity for intent: " + intent);
return false;
- } catch (Exception ex) {
+ } catch (RuntimeException e) {
// Catch all other possible exception to prevent service disruption by misbehaving
// applications.
- Log.e(TAG, "Error trying to launch intent: " + intent + ". Ignored", ex);
+ Log.e(TAG, "Error trying to launch intent: " + intent + ". Ignored", e);
return false;
}
return true;
diff --git a/car-lib/src/android/car/content/pm/CarPackageManager.java b/car-lib/src/android/car/content/pm/CarPackageManager.java
index 98c5822..6a8c7fb 100644
--- a/car-lib/src/android/car/content/pm/CarPackageManager.java
+++ b/car-lib/src/android/car/content/pm/CarPackageManager.java
@@ -123,6 +123,8 @@
/**
* Restarts the requested task. If task with {@code taskId} does not exist, do nothing.
*
+ * <p>This requires {@code android.permission.REAL_GET_TASKS} permission.
+ *
* @hide
*/
public void restartTask(int taskId) {
diff --git a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
index 54bda19..971f1fc 100644
--- a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
+++ b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
@@ -25,8 +25,6 @@
import android.car.hardware.property.CarPropertyManager;
import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
import android.car.hardware.property.ICarProperty;
-import android.car.hardware.property.PropertyAccessDeniedSecurityException;
-import android.car.hardware.property.PropertyNotAvailableException;
import android.os.IBinder;
import android.util.ArraySet;
import android.util.Log;
@@ -351,12 +349,9 @@
mCarPropertyMgr.unregisterCallback(mListenerToBase, c.getPropertyId());
}
- } catch (PropertyAccessDeniedSecurityException
- | PropertyNotAvailableException
- | IllegalArgumentException e) {
+ } catch (RuntimeException e) {
Log.e(TAG, "getPropertyList exception ", e);
}
-
if (mCallbacks.isEmpty()) {
mCarPropertyMgr.unregisterCallback(mListenerToBase);
mListenerToBase = null;
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index 31eb173..9f57617 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -32,8 +32,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
-import android.view.Display;
-import android.view.DisplayAddress;
import java.util.ArrayList;
import java.util.Collections;
@@ -538,43 +536,6 @@
}
/**
- * Get the zone id for the display
- *
- * @param display display to query
- * @return zone id for display or
- * CarAudioManager.PRIMARY_AUDIO_ZONE if no match is found.
- * @hide
- */
- @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
- public int getZoneIdForDisplay(Display display) {
- DisplayAddress address = display.getAddress();
- if (address instanceof DisplayAddress.Physical) {
- DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;
- if (physicalAddress != null) {
- return getZoneIdForDisplayPortId(physicalAddress.getPort());
- }
- }
- return PRIMARY_AUDIO_ZONE;
- }
-
- /**
- * Get the zone id for the display port id passed in
- *
- * @param displayPortId display port id to query
- * @return zone id for display port id or
- * CarAudioManager.PRIMARY_AUDIO_ZONE if no match is found.
- * @hide
- */
- @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
- public int getZoneIdForDisplayPortId(byte displayPortId) {
- try {
- return mService.getZoneIdForDisplayPortId(displayPortId);
- } catch (RemoteException e) {
- return handleRemoteExceptionFromCarService(e, 0);
- }
- }
-
- /**
* Gets the output device for a given {@link AudioAttributes} usage in zoneId.
*
* <p><b>Note:</b> To be used for routing to a specific device. Most applications should
diff --git a/car-lib/src/android/car/media/ICarAudio.aidl b/car-lib/src/android/car/media/ICarAudio.aidl
index 7491a19..a8997ce 100644
--- a/car-lib/src/android/car/media/ICarAudio.aidl
+++ b/car-lib/src/android/car/media/ICarAudio.aidl
@@ -48,8 +48,6 @@
boolean setZoneIdForUid(int zoneId, int uid);
boolean clearZoneIdForUid(int uid);
- int getZoneIdForDisplayPortId(byte displayPortId);
-
String getOutputDeviceAddressForUsage(int zoneId, int usage);
List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId);
diff --git a/car-lib/src/android/car/settings/CarSettings.java b/car-lib/src/android/car/settings/CarSettings.java
index 932eec0..3f5a6c3 100644
--- a/car-lib/src/android/car/settings/CarSettings.java
+++ b/car-lib/src/android/car/settings/CarSettings.java
@@ -23,6 +23,7 @@
*
* @hide
*/
+@SystemApi
public class CarSettings {
private CarSettings() {
@@ -73,6 +74,22 @@
"android.car.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE";
/**
+ * User id of the last foreground user
+ *
+ * @hide
+ */
+ public static final String LAST_ACTIVE_USER_ID =
+ "android.car.LAST_ACTIVE_USER_ID";
+
+ /**
+ * User id of the last persistent (i.e, not counting ephemeral guests) foreground user
+ *
+ * @hide
+ */
+ public static final String LAST_ACTIVE_PERSISTENT_USER_ID =
+ "android.car.LAST_ACTIVE_PERSISTENT_USER_ID";
+
+ /**
* Defines global runtime overrides to system bar policy.
*
* See {@link com.android.systemui.wm.BarControlPolicy} for value format.
diff --git a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
index ac59bac..6f993e3 100644
--- a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
+++ b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
@@ -47,9 +47,17 @@
private final SingleMessageHandler<IoStats> mMessageHandler;
private final Set<IoStatsListener> mListeners = new HashSet<>();
+ /**
+ * Implementers will be notified on every new I/O activity calculated stats.
+ */
public interface IoStatsListener {
+
+ /**
+ * Invoked when a new periodic snapshot delta of I/O activities is calculated.
+ */
void onSnapshot(IoStats snapshot);
}
+
private static final class ListenerToService extends IIoStatsListener.Stub {
private final WeakReference<CarStorageMonitoringManager> mManager;
@@ -109,7 +117,7 @@
* It will return either PRE_EOL_INFO_UNKNOWN if the value can't be determined,
* or one of PRE_EOL_INFO_{NORMAL|WARNING|URGENT} depending on the device state.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public int getPreEolIndicatorStatus() {
try {
return mService.getPreEolIndicatorStatus();
@@ -127,7 +135,7 @@
*
* If either or both indicators are not available, they will be reported as UNKNOWN.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public WearEstimate getWearEstimate() {
try {
return mService.getWearEstimate();
@@ -147,7 +155,7 @@
*
* If no indicators are available, an empty list will be returned.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public List<WearEstimateChange> getWearEstimateHistory() {
try {
return mService.getWearEstimateHistory();
@@ -166,7 +174,7 @@
*
* If the information is not available, an empty list will be returned.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public List<IoStatsEntry> getBootIoStats() {
try {
return mService.getBootIoStats();
@@ -196,7 +204,7 @@
*
* <p>If the information is not available, SHUTDOWN_COST_INFO_MISSING will be returned.</p>s
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public long getShutdownDiskWriteAmount() {
try {
return mService.getShutdownDiskWriteAmount();
@@ -213,7 +221,7 @@
*
* If the information is not available, an empty list will be returned.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public List<IoStatsEntry> getAggregateIoStats() {
try {
return mService.getAggregateIoStats();
@@ -233,7 +241,7 @@
*
* If the information is not available, an empty list will be returned.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public List<IoStats> getIoStatsDeltas() {
try {
return mService.getIoStatsDeltas();
@@ -250,7 +258,7 @@
*
* The timing of availability of the deltas is configurable by the OEM.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public void registerListener(IoStatsListener listener) {
try {
if (mListeners.isEmpty()) {
@@ -268,7 +276,7 @@
/**
* This method removes a registered listener of I/O stats deltas.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public void unregisterListener(IoStatsListener listener) {
try {
if (!mListeners.remove(listener)) {
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index 1525889..fcdc89a 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -209,7 +209,7 @@
@Override
protected void onCompleted(UserSwitchResult result, Throwable err) {
if (result != null) {
- EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_RESPONSE, uid,
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_RESP, uid,
result.getStatus(), result.getErrorMessage());
} else {
Log.w(TAG, "switchUser(" + targetUserId + ") failed: " + err);
@@ -217,7 +217,7 @@
super.onCompleted(result, err);
};
};
- EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_REQUEST, uid, targetUserId);
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_REQ, uid, targetUserId);
mService.switchUser(targetUserId, HAL_TIMEOUT_MS, future);
return future;
} catch (RemoteException e) {
@@ -264,6 +264,28 @@
}
}
+ /**
+ * Removes a user.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public UserRemovalResult removeUser(@UserIdInt int userId) {
+ int uid = myUid();
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_REMOVE_USER_REQ, uid, userId);
+ int status = UserRemovalResult.STATUS_HAL_INTERNAL_FAILURE;
+ try {
+ UserRemovalResult result = mService.removeUser(userId);
+ status = result.getStatus();
+ return result;
+ } catch (RemoteException e) {
+ return handleRemoteExceptionFromCarService(e,
+ new UserRemovalResult(UserRemovalResult.STATUS_HAL_INTERNAL_FAILURE));
+ } finally {
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_REMOVE_USER_RESP, uid, status);
+ }
+ }
+
/**
* Adds a listener for {@link UserLifecycleEvent user lifecycle events}.
*
diff --git a/car-lib/src/android/car/user/CommonResults.java b/car-lib/src/android/car/user/CommonResults.java
new file mode 100644
index 0000000..9238a0b
--- /dev/null
+++ b/car-lib/src/android/car/user/CommonResults.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.car.user;
+
+/**
+ * Defines status for common result objects.
+ */
+final class CommonResults {
+
+ /**
+ * Operation was is successful for both HAL and Android.
+ */
+ public static final int STATUS_SUCCESSFUL = 1;
+
+ /**
+ * Operation failed on Android.
+ */
+ public static final int STATUS_ANDROID_FAILURE = 2;
+
+ /**
+ * Operation failed on HAL.
+ */
+ public static final int STATUS_HAL_FAILURE = 3;
+
+ /**
+ * Operation failed due to an error communication with HAL (like timeout).
+ */
+ public static final int STATUS_HAL_INTERNAL_FAILURE = 4;
+
+ /**
+ * Operation failed due to invalid request.
+ */
+ public static final int STATUS_INVALID_REQUEST = 5;
+
+ /**
+ * Reference for common status - anything higher than this can be used for custom status
+ */
+ static final int LAST_COMMON_STATUS = 100;
+
+ private CommonResults() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/car-lib/src/android/car/user/UserCreationResult.java b/car-lib/src/android/car/user/UserCreationResult.java
index d0f7aa1..fa29cf4 100644
--- a/car-lib/src/android/car/user/UserCreationResult.java
+++ b/car-lib/src/android/car/user/UserCreationResult.java
@@ -42,7 +42,7 @@
*
* @hide
*/
- public static final int STATUS_SUCCESSFUL = 1;
+ public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
/**
* {@link Status} called when user creation failed on Android - HAL is not even called in this
@@ -50,7 +50,7 @@
*
* @hide
*/
- public static final int STATUS_ANDROID_FAILURE = 2;
+ public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
/**
* {@link Status} called when user was created on Android but HAL returned a failure - the
@@ -58,7 +58,7 @@
*
* @hide
*/
- public static final int STATUS_HAL_FAILURE = 3;
+ public static final int STATUS_HAL_FAILURE = CommonResults.STATUS_HAL_FAILURE;
/**
* {@link Status} called when user creation is failed for HAL for some internal error - the
@@ -66,7 +66,7 @@
*
* @hide
*/
- public static final int STATUS_HAL_INTERNAL_FAILURE = 4;
+ public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
/**
* Gets the user switch result status.
@@ -77,7 +77,7 @@
* or
* {@link UserCreationResult#STATUS_HAL_INTERNAL_FAILURE}
*/
- private final int mStatus;
+ private final @Status int mStatus;
/**
* Gets the created user.
@@ -98,6 +98,8 @@
return mStatus == STATUS_SUCCESSFUL;
}
+ // TODO(b/158195639): if you change any status constant, you need to manually assign its values
+ // (rather than using CommonResults) before running codegen to regenerate the class
// Code below generated by codegen v1.0.15.
diff --git a/car-lib/src/android/car/user/UserRemovalResult.aidl b/car-lib/src/android/car/user/UserRemovalResult.aidl
new file mode 100644
index 0000000..00a0eba
--- /dev/null
+++ b/car-lib/src/android/car/user/UserRemovalResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.car.user;
+
+parcelable UserRemovalResult;
diff --git a/car-lib/src/android/car/user/UserRemovalResult.java b/car-lib/src/android/car/user/UserRemovalResult.java
new file mode 100644
index 0000000..dbfd8a6
--- /dev/null
+++ b/car-lib/src/android/car/user/UserRemovalResult.java
@@ -0,0 +1,249 @@
+/*
+ * 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 android.car.user;
+
+import android.annotation.IntDef;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * User remove result.
+ *
+ * @hide
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstructor = true,
+ genHiddenConstDefs = true)
+public final class UserRemovalResult implements Parcelable {
+
+ /**
+ * When user remove is successful.
+ *
+ * @hide
+ */
+ public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
+
+ /**
+ * When user remove fails for android. Hal user is not removed.
+ *
+ * @hide
+ */
+ public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
+
+ /**
+ * When remove user fails for unknown error.
+ *
+ * @hide
+ */
+ public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
+
+ /**
+ * When user to remove is same as current user.
+ *
+ * @hide
+ */
+ public static final int STATUS_TARGET_USER_IS_CURRENT_USER =
+ CommonResults.LAST_COMMON_STATUS + 1;
+
+ /**
+ * When user to remove doesn't exits.
+ *
+ * @hide
+ */
+ public static final int STATUS_USER_DOES_NOT_EXIST = CommonResults.LAST_COMMON_STATUS + 2;
+
+ /**
+ * When user to remove is last admin user.
+ *
+ * @hide
+ */
+ public static final int STATUS_TARGET_USER_IS_LAST_ADMIN_USER =
+ CommonResults.LAST_COMMON_STATUS + 3;
+
+ /**
+ * Gets the user switch result status.
+ *
+ * @return either {@link UserRemovalResult#STATUS_SUCCESSFUL},
+ * {@link UserRemovalResult#STATUS_ANDROID_FAILURE},
+ * {@link UserRemovalResult#STATUS_HAL_INTERNAL_FAILURE},
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_CURRENT_USER},
+ * {@link UserRemovalResult#STATUS_USER_DOES_NOT_EXIST}, or
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_LAST_ADMIN_USER}.
+ */
+ private final @Status int mStatus;
+
+ public boolean isSuccess() {
+ return mStatus == STATUS_SUCCESSFUL;
+ }
+
+ // TODO(b/158195639): if you change any status constant, you need to manually assign its values
+ // (rather than using CommonResults) before running codegen to regenerate the class
+
+
+ // Code below generated by codegen v1.0.15.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/user/UserRemovalResult.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @IntDef(prefix = "STATUS_", value = {
+ STATUS_SUCCESSFUL,
+ STATUS_ANDROID_FAILURE,
+ STATUS_HAL_INTERNAL_FAILURE,
+ STATUS_TARGET_USER_IS_CURRENT_USER,
+ STATUS_USER_DOES_NOT_EXIST,
+ STATUS_TARGET_USER_IS_LAST_ADMIN_USER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Status {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String statusToString(@Status int value) {
+ switch (value) {
+ case STATUS_SUCCESSFUL:
+ return "STATUS_SUCCESSFUL";
+ case STATUS_ANDROID_FAILURE:
+ return "STATUS_ANDROID_FAILURE";
+ case STATUS_HAL_INTERNAL_FAILURE:
+ return "STATUS_HAL_INTERNAL_FAILURE";
+ case STATUS_TARGET_USER_IS_CURRENT_USER:
+ return "STATUS_TARGET_USER_IS_CURRENT_USER";
+ case STATUS_USER_DOES_NOT_EXIST:
+ return "STATUS_USER_DOES_NOT_EXIST";
+ case STATUS_TARGET_USER_IS_LAST_ADMIN_USER:
+ return "STATUS_TARGET_USER_IS_LAST_ADMIN_USER";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * Creates a new UserRemovalResult.
+ *
+ * @param status
+ * Gets the user switch result status.
+ *
+ * @return either {@link UserRemovalResult#STATUS_SUCCESSFUL},
+ * {@link UserRemovalResult#STATUS_ANDROID_FAILURE},
+ * {@link UserRemovalResult#STATUS_HAL_INTERNAL_FAILURE},
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_CURRENT_USER},
+ * {@link UserRemovalResult#STATUS_USER_DOES_NOT_EXIST}, or
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_LAST_ADMIN_USER}.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public UserRemovalResult(
+ int status) {
+ this.mStatus = status;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Gets the user switch result status.
+ *
+ * @return either {@link UserRemovalResult#STATUS_SUCCESSFUL},
+ * {@link UserRemovalResult#STATUS_ANDROID_FAILURE},
+ * {@link UserRemovalResult#STATUS_HAL_INTERNAL_FAILURE},
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_CURRENT_USER},
+ * {@link UserRemovalResult#STATUS_USER_DOES_NOT_EXIST}, or
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_LAST_ADMIN_USER}.
+ */
+ @DataClass.Generated.Member
+ public int getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "UserRemovalResult { " +
+ "status = " + mStatus +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mStatus);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ UserRemovalResult(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int status = in.readInt();
+
+ this.mStatus = status;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<UserRemovalResult> CREATOR
+ = new Parcelable.Creator<UserRemovalResult>() {
+ @Override
+ public UserRemovalResult[] newArray(int size) {
+ return new UserRemovalResult[size];
+ }
+
+ @Override
+ public UserRemovalResult createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new UserRemovalResult(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1591259644931L,
+ codegenVersion = "1.0.15",
+ sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserRemovalResult.java",
+ inputSignatures = "public static final int STATUS_SUCCESSFUL\npublic static final int STATUS_ANDROID_FAILURE\npublic static final int STATUS_HAL_INTERNAL_FAILURE\npublic static final int STATUS_TARGET_USER_IS_CURRENT_USER\npublic static final int STATUS_USER_DOES_NOT_EXIST\npublic static final int STATUS_TARGET_USER_IS_LAST_ADMIN_USER\nprivate final int mStatus\npublic boolean isSuccess()\nclass UserRemovalResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/car-lib/src/android/car/user/UserSwitchResult.java b/car-lib/src/android/car/user/UserSwitchResult.java
index 8c4c535..55e2dbc 100644
--- a/car-lib/src/android/car/user/UserSwitchResult.java
+++ b/car-lib/src/android/car/user/UserSwitchResult.java
@@ -38,63 +38,50 @@
/**
* When user switch is successful for both HAL and Android.
- *
- * @hide
*/
- public static final int STATUS_SUCCESSFUL = 1;
+ public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
/**
* When user switch is only successful for Hal but not for Android. Hal user switch rollover
* message have been sent.
- *
- * @hide
*/
- public static final int STATUS_ANDROID_FAILURE = 2;
+ public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
/**
* When user switch fails for HAL. User switch for Android is not called.
- *
- * @hide
*/
- public static final int STATUS_HAL_FAILURE = 3;
+ public static final int STATUS_HAL_FAILURE = CommonResults.STATUS_HAL_FAILURE;
/**
* When user switch fails for HAL for some internal error. User switch for Android is not
* called.
- *
- * @hide
*/
- public static final int STATUS_HAL_INTERNAL_FAILURE = 4;
-
- /**
- * When target user is same as current user.
- *
- * @hide
- */
- public static final int STATUS_ALREADY_REQUESTED_USER = 5;
-
- /**
- * When another user switch request for the same target user is in process.
- *
- * @hide
- */
- public static final int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO = 6;
-
- /**
- * When another user switch request for a new different target user is received. Previous
- * request is abandoned.
- *
- * @hide
- */
- public static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST = 7;
+ public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
/**
* When given parameters or environment states are invalid for switching user. HAL or Android
* user switch is not requested.
- *
- * @hide
*/
- public static final int STATUS_INVALID_REQUEST = 8;
+ public static final int STATUS_INVALID_REQUEST = CommonResults.STATUS_INVALID_REQUEST;
+
+ /**
+ * When target user is same as current user.
+ */
+ public static final int STATUS_ALREADY_REQUESTED_USER =
+ CommonResults.LAST_COMMON_STATUS + 1;
+
+ /**
+ * When another user switch request for the same target user is in process.
+ */
+ public static final int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO =
+ CommonResults.LAST_COMMON_STATUS + 2;
+
+ /**
+ * When another user switch request for a new different target user is received. Previous
+ * request is abandoned.
+ */
+ public static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST =
+ CommonResults.LAST_COMMON_STATUS + 3;
/**
* Gets the user switch result status.
@@ -108,7 +95,7 @@
* {@link UserSwitchResult#STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST}, or
* {@link UserSwitchResult#STATUS_INVALID_REQUEST}.
*/
- private final int mStatus;
+ private final @Status int mStatus;
/**
* Gets the error message, if any.
@@ -123,6 +110,8 @@
return mStatus == STATUS_SUCCESSFUL || mStatus == STATUS_ALREADY_REQUESTED_USER;
}
+ // TODO(b/158195639): if you change any status constant, you need to manually assign its values
+ // (rather than using CommonResults) before running codegen to regenerate the class
// Code below generated by codegen v1.0.15.
diff --git a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java b/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
index 65610ae..c89a337 100644
--- a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
+++ b/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
@@ -86,11 +86,11 @@
*/
public abstract class AbstractExtendedMockitoTestCase {
- private static final boolean TRACE = false;
-
- private static final boolean VERBOSE = false;
private static final String TAG = AbstractExtendedMockitoTestCase.class.getSimpleName();
+ private static final boolean TRACE = false;
+ private static final boolean VERBOSE = false;
+
private final List<Class<?>> mStaticSpiedClasses = new ArrayList<>();
// Tracks (S)Log.wtf() calls made during code execution, then used on verifyWtfNeverLogged()
@@ -159,7 +159,7 @@
}
}
ArrayList<SyncRunnable> syncs = new ArrayList<>(handlerThreads.size());
- Log.i(TAG, "will wait for HandlerThreads:" + handlerThreads.size());
+ Log.i(TAG, "will wait for " + handlerThreads.size() + " HandlerThreads");
for (int i = 0; i < handlerThreads.size(); i++) {
Handler handler = new Handler(handlerThreads.get(i).getLooper());
SyncRunnable sr = new SyncRunnable(() -> { });
@@ -203,6 +203,13 @@
}
/**
+ * Asserts that the giving settings was not set.
+ */
+ protected void assertSettingsNotSet(String key) {
+ mSettings.assertDoesNotContainsKey(key);
+ }
+
+ /**
* Subclasses can use this method to initialize the Mockito session that's started before every
* test on {@link #startSession()}.
*
@@ -522,6 +529,13 @@
public int getInt(String key) {
return get(key, null, Integer.class);
}
+
+ public void assertDoesNotContainsKey(String key) {
+ if (mSettingsMapping.containsKey(key)) {
+ throw new AssertionError("Should not have key " + key + ", but has: "
+ + mSettingsMapping.get(key));
+ }
+ }
}
/**
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java b/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
index 8b2f425..a1be7dd 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
@@ -71,18 +71,16 @@
private final PackageManager mPackageManager;
private final UsbDeviceHandlerResolverCallback mDeviceCallback;
private final Context mContext;
- private final HandlerThread mHandlerThread;
- private final UsbDeviceResolverHandler mHandler;
private final AoapServiceManager mAoapServiceManager;
+ private HandlerThread mHandlerThread;
+ private UsbDeviceResolverHandler mHandler;
public UsbDeviceHandlerResolver(UsbManager manager, Context context,
UsbDeviceHandlerResolverCallback deviceListener) {
mUsbManager = manager;
mContext = context;
mDeviceCallback = deviceListener;
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
- mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
+ createHandlerThread();
mPackageManager = context.getPackageManager();
mAoapServiceManager = new AoapServiceManager(mContext.getApplicationContext());
}
@@ -104,9 +102,22 @@
}
/**
+ * Listener for failed {@code startAosp} command.
+ *
+ * <p>If {@code startAosp} fails, the device could be left in a inconsistent state, that's why
+ * we go back to USB enumeration, instead of just repeating the command.
+ */
+ public interface StartAoapFailureListener {
+
+ /** Called if startAoap fails. */
+ void onFailure();
+ }
+
+ /**
* Dispatches device to component.
*/
- public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap) {
+ public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap,
+ StartAoapFailureListener failureListener) {
if (LOCAL_LOGD) {
Log.d(TAG, "dispatch: " + device + " component: " + component + " inAoap: " + inAoap);
}
@@ -128,10 +139,20 @@
packageMatches(activityInfo, intent.getAction(), device, true);
if (filter != null) {
+ if (!mHandlerThread.isAlive()) {
+ // Start a new thread. Used only when startAoap fails, and we need to
+ // re-enumerate device in order to try again.
+ createHandlerThread();
+ }
mHandlerThread.getThreadHandler().post(() -> {
if (mAoapServiceManager.canSwitchDeviceToAoap(device,
ComponentName.unflattenFromString(filter.mAoapService))) {
- requestAoapSwitch(device, filter);
+ try {
+ requestAoapSwitch(device, filter);
+ } catch (IOException e) {
+ Log.w(TAG, "Start AOAP command failed:" + e);
+ failureListener.onFailure();
+ }
} else {
Log.i(TAG, "Ignore AOAP switch for device " + device
+ " handled by " + filter.mAoapService);
@@ -151,6 +172,12 @@
return true;
}
+ private void createHandlerThread() {
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
+ }
+
private static Intent createDeviceAttachedIntent(UsbDevice device) {
Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
@@ -192,7 +219,7 @@
return settings;
}
- private void requestAoapSwitch(UsbDevice device, UsbDeviceFilter filter) {
+ private void requestAoapSwitch(UsbDevice device, UsbDeviceFilter filter) throws IOException {
UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, device);
if (connection == null) {
Log.e(TAG, "Failed to connect to usb device.");
@@ -209,11 +236,9 @@
filter.mAoapVersion,
filter.mAoapUri,
hashedSerial);
- } catch (IOException e) {
- Log.w(TAG, "Failed to switch device into AOAP mode", e);
+ } finally {
+ connection.close();
}
-
- connection.close();
}
private String getHashed(String serial) {
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
index 3fcb67b..0da71ac 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
@@ -30,6 +30,7 @@
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
/**
@@ -300,6 +301,12 @@
private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
+ // Used to get the device that we are trying to connect to, if mActiveDevice is removed and
+ // startAoap fails afterwards. Used during USB enumeration when retrying to startAoap when
+ // there are multiple devices attached.
+ private int mLastDeviceId = 0;
+ private int mStartAoapRetries = 1;
+
private UsbHostControllerHandler(Looper looper) {
super(looper);
}
@@ -308,6 +315,24 @@
sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
}
+ private void onFailure() {
+ if (mStartAoapRetries == 0) {
+ Log.w(TAG, "Reached maximum retry count for startAoap. Giving up Aoa handshake.");
+ return;
+ }
+ mStartAoapRetries--;
+
+ Log.d(TAG, "Restarting USB enumeration.");
+ Iterator<UsbDevice> deviceIterator = mUsbManager.getDeviceList().values().iterator();
+ while (deviceIterator.hasNext()) {
+ UsbDevice device = deviceIterator.next();
+ if (mLastDeviceId == device.getDeviceId()) {
+ processDevice(device);
+ return;
+ }
+ }
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -318,8 +343,10 @@
UsbHostControllerHandlerDispatchData data =
(UsbHostControllerHandlerDispatchData) msg.obj;
UsbDevice device = data.getUsbDevice();
+ mLastDeviceId = device.getDeviceId();
UsbDeviceSettings settings = data.getUsbDeviceSettings();
- if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.getAoap())) {
+ if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.getAoap(),
+ this::onFailure)) {
if (data.mRetries > 0) {
--data.mRetries;
Message nextMessage = Message.obtain(msg);
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 4c310a8..9194bc1 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -44,6 +44,11 @@
<!-- Service responsible for displaying information on the car instrument cluster. -->
<string name="instrumentClusterRendererService" translatable="false">android.car.cluster/.ClusterRenderingService</string>
+ <!-- Service responsible for handling the rotary controller input. This service will start
+ on boot or on user switch. Set this string to empty if you don't want to start this
+ service. -->
+ <string name="rotaryService" translatable="false">com.android.car.rotary/com.android.car.rotary.RotaryService</string>
+
<!-- Whether to enable Activity blocking for safety. When Activity blocking is enabled,
only whitelisted safe Activities will be allowed while car is not parked. -->
<bool name="enableActivityBlockingForSafety">true</bool>
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
index d97bec8..cc1a530 100644
--- a/service/src/com/android/car/AppFocusService.java
+++ b/service/src/com/android/car/AppFocusService.java
@@ -161,7 +161,7 @@
ownerInfo.binderInterface, appType);
if (DBG) {
Log.i(CarLog.TAG_APP_FOCUS, "losing app type "
- + appType + "," + ownerInfo.toString());
+ + appType + "," + ownerInfo);
}
}
updateFocusOwner(appType, info);
@@ -169,20 +169,20 @@
info.addOwnedAppType(appType);
mDispatchHandler.requestAppFocusOwnershipGrantDispatch(
info.binderInterface, appType);
- if (mActiveAppTypes.add(appType)) {
- if (DBG) {
- Log.i(CarLog.TAG_APP_FOCUS, "adding active app type " + appType + ","
- + info.toString());
- }
- for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
- mAllChangeClients.getInterfaces()) {
- ClientInfo clientInfo = (ClientInfo) client;
- // dispatch events only when there is change after filter and the listener
- // is not coming from the current caller.
- if (clientInfo.getAppTypes().contains(appType)) {
- mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
- appType, true);
- }
+ mActiveAppTypes.add(appType);
+ if (DBG) {
+ Log.i(CarLog.TAG_APP_FOCUS, "updating active app type " + appType + ","
+ + info);
+ }
+ // Always dispatch.
+ for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
+ mAllChangeClients.getInterfaces()) {
+ ClientInfo clientInfo = (ClientInfo) client;
+ // dispatch events only when there is change after filter and the listener
+ // is not coming from the current caller.
+ if (clientInfo.getAppTypes().contains(appType)) {
+ mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
+ appType, true);
}
}
}
@@ -212,8 +212,7 @@
mActiveAppTypes.remove(appType);
info.removeOwnedAppType(appType);
if (DBG) {
- Log.i(CarLog.TAG_APP_FOCUS, "abandoning focus " + appType
- + "," + info.toString());
+ Log.i(CarLog.TAG_APP_FOCUS, "abandoning focus " + appType + "," + info);
}
for (FocusOwnershipCallback ownershipCallback : mFocusOwnershipCallbacks) {
ownershipCallback.onFocusAbandoned(appType, info.mUid, info.mPid);
@@ -270,7 +269,7 @@
for (BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> client :
mAllOwnershipClients.getInterfaces()) {
OwnershipClientInfo clientInfo = (OwnershipClientInfo) client;
- writer.println(clientInfo.toString());
+ writer.println(clientInfo);
}
}
}
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 65c616f..222e760 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -19,6 +19,7 @@
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_PUSH_TO_TALK;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -32,6 +33,8 @@
import android.car.input.ICarInputCallback;
import android.car.input.ICarInputListener;
import android.car.input.RotaryEvent;
+import android.car.user.CarUserManager;
+import android.car.userlib.UserHelper;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -58,6 +61,7 @@
import android.view.ViewConfiguration;
import com.android.car.hal.InputHalService;
+import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
@@ -159,6 +163,7 @@
private final Context mContext;
private final InputHalService mInputHalService;
+ private final CarUserService mUserService;
private final TelecomManager mTelecomManager;
private final AssistUtils mAssistUtils;
// The ComponentName of the CarInputListener service. Can be changed via resource overlay,
@@ -175,6 +180,8 @@
// from Settings.Secure for the current user, falling back to the system-wide default
// long-press delay defined in ViewConfiguration. May be overridden for testing.
private final IntSupplier mLongPressDelaySupplier;
+ // ComponentName of the RotaryService.
+ private final String mRotaryServiceComponentName;
private final Object mLock = new Object();
@@ -270,6 +277,13 @@
}
};
+ private final CarUserManager.UserLifecycleListener mUserLifecycleListener = event -> {
+ Log.d(CarLog.TAG_INPUT, "CarInputService.onEvent(" + event + ")");
+ if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
+ updateRotaryServiceSettings(event.getUserId());
+ }
+ };
+
@Nullable
private static ComponentName getDefaultInputComponent(Context context) {
String carInputService = context.getString(R.string.inputService);
@@ -285,8 +299,9 @@
UserHandle.USER_CURRENT);
}
- public CarInputService(Context context, InputHalService inputHalService) {
- this(context, inputHalService, new Handler(Looper.getMainLooper()),
+ public CarInputService(Context context, InputHalService inputHalService,
+ CarUserService userService) {
+ this(context, inputHalService, userService, new Handler(Looper.getMainLooper()),
context.getSystemService(TelecomManager.class), new AssistUtils(context),
event ->
context.getSystemService(InputManager.class)
@@ -297,14 +312,15 @@
}
@VisibleForTesting
- CarInputService(Context context, InputHalService inputHalService, Handler handler,
- TelecomManager telecomManager, AssistUtils assistUtils,
+ CarInputService(Context context, InputHalService inputHalService, CarUserService userService,
+ Handler handler, TelecomManager telecomManager, AssistUtils assistUtils,
KeyEventListener mainDisplayHandler, Supplier<String> lastCalledNumberSupplier,
@Nullable ComponentName customInputServiceComponent,
IntSupplier longPressDelaySupplier) {
mContext = context;
mCaptureController = new InputCaptureClientController(context);
mInputHalService = inputHalService;
+ mUserService = userService;
mTelecomManager = telecomManager;
mAssistUtils = assistUtils;
mMainDisplayHandler = mainDisplayHandler;
@@ -317,6 +333,8 @@
handler, longPressDelaySupplier, this::handleVoiceAssistLongPress);
mCallKeyTimer =
new KeyPressTimer(handler, longPressDelaySupplier, this::handleCallLongPress);
+
+ mRotaryServiceComponentName = mContext.getString(R.string.rotaryService);
}
@VisibleForTesting
@@ -367,6 +385,9 @@
mBluetoothAdapter.getProfileProxy(
mContext, mBluetoothProfileServiceListener, BluetoothProfile.HEADSET_CLIENT);
}
+ if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
+ mUserService.addUserLifecycleListener(mUserLifecycleListener);
+ }
}
@Override
@@ -385,6 +406,9 @@
mBluetoothHeadsetClient = null;
}
}
+ if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
+ mUserService.removeUserLifecycleListener(mUserLifecycleListener);
+ }
}
@Override
@@ -705,4 +729,19 @@
intent.setComponent(mCustomInputServiceComponent);
return mContext.bindService(intent, mInputServiceConnection, Context.BIND_AUTO_CREATE);
}
+
+ private void updateRotaryServiceSettings(@UserIdInt int userId) {
+ if (UserHelper.isHeadlessSystemUser(userId)) {
+ return;
+ }
+ ContentResolver contentResolver = mContext.getContentResolver();
+ Settings.Secure.putStringForUser(contentResolver,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ mRotaryServiceComponentName,
+ userId);
+ Settings.Secure.putStringForUser(contentResolver,
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ "1",
+ userId);
+ }
}
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index f9e15cc..aef68cd 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -44,6 +44,7 @@
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -59,6 +60,7 @@
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractionManagerService;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -137,6 +139,8 @@
private final CarUserService mUserService;
private final InitialUserSetter mInitialUserSetter;
+ private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
// TODO: Make this OEM configurable.
private static final int SHUTDOWN_POLLING_INTERVAL_MS = 2000;
private static final int SHUTDOWN_EXTEND_MAX_MS = 5000;
@@ -168,13 +172,16 @@
this(context, context.getResources(), powerHal, systemInterface, UserManager.get(context),
carUserService, new InitialUserSetter(context,
(u) -> carUserService.setInitialUser(u),
- context.getString(R.string.default_guest_name)));
+ context.getString(R.string.default_guest_name)),
+ IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)));
}
@VisibleForTesting
public CarPowerManagementService(Context context, Resources resources, PowerHalService powerHal,
SystemInterface systemInterface, UserManager userManager, CarUserService carUserService,
- InitialUserSetter initialUserSetter) {
+ InitialUserSetter initialUserSetter,
+ IVoiceInteractionManagerService voiceInteractionService) {
mContext = context;
mHal = powerHal;
mSystemInterface = systemInterface;
@@ -194,6 +201,7 @@
}
mUserService = carUserService;
mInitialUserSetter = initialUserSetter;
+ mVoiceInteractionManagerService = voiceInteractionService;
}
@VisibleForTesting
@@ -408,6 +416,7 @@
mSystemInterface.setDisplayState(true);
sendPowerManagerEvent(CarPowerStateListener.ON);
+
mHal.sendOn();
try {
@@ -415,6 +424,8 @@
} catch (Exception e) {
Log.e(CarLog.TAG_POWER, "Could not switch user on resume", e);
}
+
+ setVoiceInteractionDisabled(false);
}
@VisibleForTesting // Ideally it should not be exposed, but it speeds up the unit tests
@@ -552,6 +563,7 @@
}
private void handleShutdownPrepare(CpmsState newState) {
+ setVoiceInteractionDisabled(true);
mSystemInterface.setDisplayState(false);
// Shutdown on finish if the system doesn't support deep sleep or doesn't allow it.
synchronized (mLock) {
@@ -619,6 +631,8 @@
throw new AssertionError("Should not return from PowerManager.reboot()");
}
}
+ setVoiceInteractionDisabled(true);
+
if (mustShutDown) {
// shutdown HU
mSystemInterface.shutdown();
@@ -628,6 +642,14 @@
mShutdownOnNextSuspend = false;
}
+ private void setVoiceInteractionDisabled(boolean disabled) {
+ try {
+ mVoiceInteractionManagerService.setDisabled(disabled);
+ } catch (RemoteException e) {
+ Log.w(TAG, "setVoiceIntefactionDisabled(" + disabled + ") failed", e);
+ }
+ }
+
@GuardedBy("mLock")
private void releaseTimerLocked() {
if (mTimer != null) {
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 86253dc..0a8c420 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -36,6 +36,7 @@
import android.car.user.CarUserManager;
import android.car.user.UserCreationResult;
import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
import android.car.user.UserSwitchResult;
import android.car.userlib.HalCallback;
import android.car.userlib.UserHalHelper;
@@ -46,7 +47,9 @@
import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
@@ -124,6 +127,7 @@
private static final String COMMAND_INJECT_ROTARY = "inject-rotary";
private static final String COMMAND_GET_INITIAL_USER_INFO = "get-initial-user-info";
private static final String COMMAND_SWITCH_USER = "switch-user";
+ private static final String COMMAND_REMOVE_USER = "remove-user";
private static final String COMMAND_CREATE_USER = "create-user";
private static final String COMMAND_GET_INITIAL_USER = "get-initial-user";
private static final String COMMAND_SET_USER_ID_TO_OCCUPANT_ZONE =
@@ -154,6 +158,8 @@
android.Manifest.permission.MANAGE_USERS);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SWITCH_USER,
android.Manifest.permission.MANAGE_USERS);
+ USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_REMOVE_USER,
+ android.Manifest.permission.MANAGE_USERS);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_CREATE_USER,
android.Manifest.permission.MANAGE_USERS);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_GET_USER_AUTH_ASSOCIATION,
@@ -373,6 +379,10 @@
pw.println("\t The --hal-only option only calls HAL, without switching the user,");
pw.println("\t while the --timeout defines how long to wait for the HAL response.");
+ pw.printf("\t%s <USER_ID> [--hal-only]\n", COMMAND_REMOVE_USER);
+ pw.println("\t Removes user with USER_ID using the HAL integration.");
+ pw.println("\t The --hal-only option only calls HAL, without removing the user,");
+
pw.printf("\t%s [--hal-only] [--timeout TIMEOUT_MS] [--type TYPE] [--flags FLAGS] [NAME]\n",
COMMAND_CREATE_USER);
pw.println("\t Creates a new user using the HAL integration.");
@@ -608,6 +618,9 @@
case COMMAND_SWITCH_USER:
switchUser(args, writer);
break;
+ case COMMAND_REMOVE_USER:
+ removeUser(args, writer);
+ break;
case COMMAND_CREATE_USER:
createUser(args, writer);
break;
@@ -953,12 +966,15 @@
if (halOnly) {
CountDownLatch latch = new CountDownLatch(1);
UserHalService userHal = mHal.getUserHal();
- UsersInfo usersInfo = generateUsersInfo();
UserInfo targetUserInfo = new UserInfo();
targetUserInfo.userId = targetUserId;
targetUserInfo.flags = getUserHalFlags(targetUserId);
- userHal.switchUser(targetUserInfo, timeout, usersInfo, (status, resp) -> {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.targetUser = targetUserInfo;
+ request.usersInfo = generateUsersInfo();
+
+ userHal.switchUser(request, timeout, (status, resp) -> {
try {
Log.d(TAG, "SwitchUserResponse: status=" + status + ", resp=" + resp);
writer.printf("Call Status: %s\n",
@@ -978,7 +994,7 @@
// Android error. This is to "rollback" the HAL switch.
if (status == HalCallback.STATUS_OK
&& resp.status == SwitchUserStatus.SUCCESS) {
- userHal.postSwitchResponse(resp.requestId, targetUserInfo, usersInfo);
+ userHal.postSwitchResponse(request);
}
} finally {
latch.countDown();
@@ -1107,6 +1123,52 @@
}
}
+ private void removeUser(String[] args, PrintWriter writer) {
+ if (args.length < 2) {
+ writer.println("Insufficient number of args");
+ return;
+ }
+
+ int userId = Integer.parseInt(args[1]);
+ boolean halOnly = false;
+
+ for (int i = 2; i < args.length; i++) {
+ String arg = args[i];
+ switch (arg) {
+ case "--hal-only":
+ halOnly = true;
+ break;
+ default:
+ writer.println("Invalid option at index " + i + ": " + arg);
+ return;
+ }
+ }
+
+ Log.d(TAG, "handleRemoveUser(): User to remove=" + userId + ", halOnly=" + halOnly);
+
+ if (halOnly) {
+ UserHalService userHal = mHal.getUserHal();
+ UsersInfo usersInfo = generateUsersInfo();
+ UserInfo userInfo = new UserInfo();
+ userInfo.userId = userId;
+ userInfo.flags = getUserHalFlags(userId);
+
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.removedUserInfo = userInfo;
+ request.usersInfo = usersInfo;
+
+ userHal.removeUser(request);
+ writer.printf("User removal sent for HAL only.\n");
+ return;
+ }
+
+ CarUserManager carUserManager = getCarUserManager(mContext);
+ UserRemovalResult result = carUserManager.removeUser(userId);
+ if (result == null) return;
+ writer.printf("UserRemovalResult: status = %s\n",
+ UserRemovalResult.statusToString(result.getStatus()));
+ }
+
private static <T> T waitForFuture(@NonNull PrintWriter writer,
@NonNull AndroidFuture<T> future, int timeoutMs) {
T result = null;
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 53e76fa..4d78dad 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -208,7 +208,7 @@
mCarUserService);
mPerUserCarServiceHelper = new PerUserCarServiceHelper(serviceContext, mCarUserService);
mCarBluetoothService = new CarBluetoothService(serviceContext, mPerUserCarServiceHelper);
- mCarInputService = new CarInputService(serviceContext, mHal.getInputHal());
+ mCarInputService = new CarInputService(serviceContext, mHal.getInputHal(), mCarUserService);
mCarProjectionService = new CarProjectionService(
serviceContext, null /* handler */, mCarInputService, mCarBluetoothService);
mGarageModeService = new GarageModeService(mContext);
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index b0dd41d..3efbd18 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -56,7 +56,6 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
-import android.view.DisplayAddress;
import android.view.KeyEvent;
import com.android.car.CarLocalServices;
@@ -1046,31 +1045,6 @@
return true;
}
- /**
- * Gets the zone id for the display port id.
- * @param displayPortId display port id to match
- * @return zone id for the display port id or
- * CarAudioManager.PRIMARY_AUDIO_ZONE if none are found
- */
- @Override
- public int getZoneIdForDisplayPortId(byte displayPortId) {
- enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
- requireDynamicRouting();
- synchronized (mImplLock) {
- for (int index = 0; index < mCarAudioZones.length; index++) {
- CarAudioZone zone = mCarAudioZones[index];
- List<DisplayAddress.Physical> displayAddresses = zone.getPhysicalDisplayAddresses();
- if (displayAddresses.stream().anyMatch(displayAddress->
- displayAddress.getPort() == displayPortId)) {
- return index;
- }
- }
-
- // Everything else defaults to primary audio zone
- return CarAudioManager.PRIMARY_AUDIO_ZONE;
- }
- }
-
@Override
public void registerVolumeCallback(@NonNull IBinder binder) {
synchronized (mImplLock) {
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
index a87b4b1..f4a4a33 100644
--- a/service/src/com/android/car/audio/CarAudioZone.java
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -19,7 +19,6 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.util.Log;
-import android.view.DisplayAddress;
import com.android.car.CarLog;
import com.android.internal.util.Preconditions;
@@ -45,14 +44,12 @@
private final int mId;
private final String mName;
private final List<CarVolumeGroup> mVolumeGroups;
- private final List<DisplayAddress.Physical> mPhysicalDisplayAddresses;
private List<AudioDeviceAttributes> mInputAudioDevice;
CarAudioZone(int id, String name) {
mId = id;
mName = name;
mVolumeGroups = new ArrayList<>();
- mPhysicalDisplayAddresses = new ArrayList<>();
mInputAudioDevice = new ArrayList<>();
}
@@ -96,23 +93,6 @@
}
/**
- * Associates a new display physical port with this audio zone. This can be used to
- * identify what zone an activity should produce sound in when launching on a particular display
- * @param physicalDisplayAddress port to associate with this zone
- */
- void addPhysicalDisplayAddress(DisplayAddress.Physical physicalDisplayAddress) {
- mPhysicalDisplayAddresses.add(physicalDisplayAddress);
- }
-
- /**
- * Gets list of ports for displays associated with this audio zone
- * @return list of Physical ports for displays associated with this audio zone
- */
- List<DisplayAddress.Physical> getPhysicalDisplayAddresses() {
- return mPhysicalDisplayAddresses;
- }
-
- /**
* @return Snapshot of available {@link CarVolumeGroup}s in array.
*/
CarVolumeGroup[] getVolumeGroups() {
@@ -173,11 +153,6 @@
void dump(String indent, PrintWriter writer) {
String internalIndent = indent + "\t";
writer.printf("%sCarAudioZone(%s:%d) isPrimary? %b\n", indent, mName, mId, isPrimaryZone());
- for (DisplayAddress.Physical physical: mPhysicalDisplayAddresses) {
- long port = (long) physical.getPort();
- writer.printf("%sDisplayAddress.Physical(%d)\n", internalIndent, port);
- }
- writer.println();
for (CarVolumeGroup group : mVolumeGroups) {
group.dump(internalIndent, writer);
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index 3ff25e9..1a4e0ad 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -22,7 +22,6 @@
import android.text.TextUtils;
import android.util.SparseIntArray;
import android.util.Xml;
-import android.view.DisplayAddress;
import com.android.car.audio.CarAudioContext.AudioContext;
import com.android.internal.util.Preconditions;
@@ -55,14 +54,11 @@
private static final String TAG_VOLUME_GROUP = "group";
private static final String TAG_AUDIO_DEVICE = "device";
private static final String TAG_CONTEXT = "context";
- private static final String TAG_DISPLAYS = "displays";
- private static final String TAG_DISPLAY = "display";
private static final String ATTR_VERSION = "version";
private static final String ATTR_IS_PRIMARY = "isPrimary";
private static final String ATTR_ZONE_NAME = "name";
private static final String ATTR_DEVICE_ADDRESS = "address";
private static final String ATTR_CONTEXT_NAME = "context";
- private static final String ATTR_PHYSICAL_PORT = "port";
private static final String ATTR_ZONE_ID = "audioZoneId";
private static final String ATTR_OCCUPANT_ZONE_ID = "occupantZoneId";
private static final String TAG_INPUT_DEVICES = "inputDevices";
@@ -132,7 +128,6 @@
private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
private final Map<String, AudioDeviceInfo> mAddressToInputAudioDeviceInfo;
private final InputStream mInputStream;
- private final Set<Long> mPortIds;
private final SparseIntArray mZoneIdToOccupantZoneIdMapping;
private final Set<Integer> mAudioZoneIds;
private final Set<String> mInputAudioDevices;
@@ -158,7 +153,6 @@
mAddressToInputAudioDeviceInfo =
CarAudioZonesHelper.generateAddressToInputAudioDeviceInfoMap(inputDeviceInfo);
mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
- mPortIds = new HashSet<>();
mZoneIdToOccupantZoneIdMapping = new SparseIntArray();
mAudioZoneIds = new HashSet<>();
mInputAudioDevices = new HashSet<>();
@@ -258,8 +252,6 @@
// Expect one <volumeGroups> in one audio zone
if (TAG_VOLUME_GROUPS.equals(parser.getName())) {
parseVolumeGroups(parser, zone);
- } else if (TAG_DISPLAYS.equals(parser.getName())) {
- parseDisplays(parser, zone);
} else if (TAG_INPUT_DEVICES.equals(parser.getName())) {
parseInputAudioDevices(parser, zone);
} else {
@@ -364,37 +356,6 @@
mInputAudioDevices.add(audioDeviceAddress);
}
- private void parseDisplays(XmlPullParser parser, CarAudioZone zone)
- throws IOException, XmlPullParserException {
- while (parser.next() != XmlPullParser.END_TAG) {
- if (parser.getEventType() != XmlPullParser.START_TAG) continue;
- if (TAG_DISPLAY.equals(parser.getName())) {
- zone.addPhysicalDisplayAddress(parsePhysicalDisplayAddress(parser));
- }
- skip(parser);
- }
- }
-
- private DisplayAddress.Physical parsePhysicalDisplayAddress(XmlPullParser parser) {
- String port = parser.getAttributeValue(NAMESPACE, ATTR_PHYSICAL_PORT);
- long portId;
- try {
- portId = Long.parseLong(port);
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException(String.format("Port %s is not a number", port), e);
- }
- validatePortIsUnique(portId);
- return DisplayAddress.fromPhysicalDisplayId(portId);
- }
-
- private void validatePortIsUnique(Long portId) {
- if (mPortIds.contains(portId)) {
- throw new IllegalArgumentException(
- String.format("Port Id %d is already associated with a zone", portId));
- }
- mPortIds.add(portId);
- }
-
private void validateOccupantZoneIdIsUnique(int occupantZoneId) {
if (mZoneIdToOccupantZoneIdMapping.indexOfValue(occupantZoneId) > -1) {
throw new IllegalArgumentException(ATTR_OCCUPANT_ZONE_ID + " " + occupantZoneId
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
index 33cf970..645f563 100644
--- a/service/src/com/android/car/hal/UserHalService.java
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -17,6 +17,7 @@
import static android.car.VehiclePropertyIds.CREATE_USER;
import static android.car.VehiclePropertyIds.INITIAL_USER_INFO;
+import static android.car.VehiclePropertyIds.REMOVE_USER;
import static android.car.VehiclePropertyIds.SWITCH_USER;
import static android.car.VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION;
@@ -34,7 +35,9 @@
import android.hardware.automotive.vehicle.V2_0.CreateUserResponse;
import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
@@ -165,6 +168,9 @@
mHandler.sendMessage(obtainMessage(
UserHalService::handleOnCreateUserResponse, this, value));
break;
+ case REMOVE_USER:
+ Log.w(TAG, "Received REMOVE_USER HAL event: " + value);
+ break;
case USER_IDENTIFICATION_ASSOCIATION:
mHandler.sendMessage(obtainMessage(
UserHalService::handleOnUserIdentificationAssociation, this, value));
@@ -270,33 +276,30 @@
/**
* Calls HAL to asynchronously switch user.
*
- * @param targetInfo target user for user switching
+ * @param request metadata
* @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 + ")");
-
- Objects.requireNonNull(targetInfo);
+ public void switchUser(@NonNull SwitchUserRequest request, int timeoutMs,
+ @NonNull HalCallback<SwitchUserResponse> callback) {
Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
- Objects.requireNonNull(callback);
- UserHalHelper.checkValid(usersInfo);
+ Objects.requireNonNull(callback, "callback cannot be null");
+ if (DBG) Log.d(TAG, "switchUser(" + request + ")");
VehiclePropValue propRequest;
int requestId;
synchronized (mLock) {
checkSupportedLocked();
if (hasPendingRequestLocked(SwitchUserResponse.class, callback)) return;
requestId = getNextRequestId();
+ request.requestId = requestId;
+ request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+ propRequest = UserHalHelper.toVehiclePropValue(request);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_REQ, requestId,
- targetInfo.userId, timeoutMs);
- propRequest = getPropRequestForSwitchUserLocked(requestId,
- SwitchUserMessageType.ANDROID_SWITCH, targetInfo, usersInfo);
+ request.targetUser.userId, timeoutMs);
addPendingRequestLocked(requestId, SwitchUserResponse.class, callback);
}
@@ -304,6 +307,34 @@
}
/**
+ * Calls HAL to remove user.
+ *
+ * @throws IllegalStateException if the HAL does not support user management (callers should
+ * call {@link #isSupported()} first to avoid this exception).
+ */
+ public void removeUser(@NonNull RemoveUserRequest request) {
+ Objects.requireNonNull(request, "request cannot be null");
+
+ if (DBG) Log.d(TAG, "removeUser(" + request.removedUserInfo.userId + ")");
+ EventLog.writeEvent(EventLogTags.CAR_USER_HAL_REMOVE_USER_REQ,
+ request.removedUserInfo.userId, request.usersInfo.currentUser.userId);
+
+ VehiclePropValue propRequest;
+ synchronized (mLock) {
+ checkSupportedLocked();
+ request.requestId = getNextRequestId();
+ propRequest = UserHalHelper.toVehiclePropValue(request);
+
+ }
+ try {
+ if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
+ mHal.set(propRequest);
+ } catch (ServiceSpecificException e) {
+ Log.w(TAG, "Failed to set REMOVE USER", e);
+ }
+ }
+
+ /**
* Calls HAL to indicate an Android user was created.
*
* @param request info agout the created user.
@@ -337,23 +368,17 @@
/**
* Calls HAL after android user switch.
- *
- * @param requestId for which switch response is sent.
- * @param targetInfo target user info.
- * @param usersInfo current state of Android users.
*/
- public void postSwitchResponse(int requestId, @NonNull UserInfo targetInfo,
- @NonNull UsersInfo usersInfo) {
- EventLog.writeEvent(EventLogTags.CAR_USER_HAL_POST_SWITCH_USER_REQ, requestId,
- targetInfo.userId, usersInfo.currentUser.userId);
- if (DBG) Log.d(TAG, "postSwitchResponse(" + targetInfo + ")");
- UserHalHelper.checkValid(usersInfo);
+ public void postSwitchResponse(@NonNull SwitchUserRequest request) {
+ EventLog.writeEvent(EventLogTags.CAR_USER_HAL_POST_SWITCH_USER_REQ, request.requestId,
+ request.targetUser.userId, request.usersInfo.currentUser.userId);
+ if (DBG) Log.d(TAG, "postSwitchResponse(" + request.targetUser.userId + ")");
VehiclePropValue propRequest;
synchronized (mLock) {
checkSupportedLocked();
- propRequest = getPropRequestForSwitchUserLocked(requestId,
- SwitchUserMessageType.ANDROID_POST_SWITCH, targetInfo, usersInfo);
+ request.messageType = SwitchUserMessageType.ANDROID_POST_SWITCH;
+ propRequest = UserHalHelper.toVehiclePropValue(request);
}
try {
@@ -368,22 +393,18 @@
* Calls HAL to switch user after legacy Android user switch. Legacy Android user switch means
* user switch is not requested by {@link CarUserManager} or OEM, and user switch is directly
* requested by {@link ActivityManager}
- *
- * @param targetInfo target user info.
- * @param usersInfo current state of Android users.
*/
- public void legacyUserSwitch(@NonNull UserInfo targetInfo, @NonNull UsersInfo usersInfo) {
- if (DBG) Log.d(TAG, "userSwitchLegacy(" + targetInfo + ")");
- UserHalHelper.checkValid(usersInfo);
+ public void legacyUserSwitch(@NonNull SwitchUserRequest request) {
+ if (DBG) Log.d(TAG, "userSwitchLegacy(" + request + ")");
VehiclePropValue propRequest;
synchronized (mLock) {
checkSupportedLocked();
int requestId = getNextRequestId();
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_LEGACY_SWITCH_USER_REQ, requestId,
- targetInfo.userId, usersInfo.currentUser.userId);
- propRequest = getPropRequestForSwitchUserLocked(requestId,
- SwitchUserMessageType.LEGACY_ANDROID_SWITCH, targetInfo, usersInfo);
+ request.targetUser.userId, request.usersInfo.currentUser.userId);
+ request.messageType = SwitchUserMessageType.LEGACY_ANDROID_SWITCH;
+ propRequest = UserHalHelper.toVehiclePropValue(request);
}
try {
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 1d1bbb6..9f66e49 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -224,6 +224,11 @@
*/
@Override
public void restartTask(int taskId) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.REAL_GET_TASKS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "requires permission " + android.Manifest.permission.REAL_GET_TASKS);
+ }
mSystemActivityMonitoringService.restartTask(taskId);
}
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index d17afed..bd45abb 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -35,6 +35,7 @@
import android.car.user.CarUserManager.UserLifecycleListener;
import android.car.user.UserCreationResult;
import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
import android.car.user.UserSwitchResult;
import android.car.userlib.CarUserManagerHelper;
import android.car.userlib.CommonConstants.CarUserServiceConstants;
@@ -53,6 +54,8 @@
import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
@@ -301,6 +304,7 @@
writer.printf("EnablePassengerSupport: %s\n", mEnablePassengerSupport);
writer.printf("User HAL timeout: %dms\n", mHalTimeoutMs);
writer.printf("Initial user: %s\n", mInitialUser);
+
writer.println("Relevant overlayable properties");
Resources res = mContext.getResources();
writer.printf("%sowner_name=%s\n", indent,
@@ -312,9 +316,18 @@
mRequestIdForUserSwitchInProcess);
writer.printf("System UI package name=%s\n", getSystemUiPackageName());
+ writer.println("Relevant Global settings");
+ dumpGlobalProperty(writer, indent, CarSettings.Global.LAST_ACTIVE_USER_ID);
+ dumpGlobalProperty(writer, indent, CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+
dumpUserMetrics(writer);
}
+ private void dumpGlobalProperty(PrintWriter writer, String indent, String property) {
+ String value = Settings.Global.getString(mContext.getContentResolver(), property);
+ writer.printf("%s%s=%s\n", indent, property, value);
+ }
+
/**
* Dumps user metrics.
*/
@@ -793,11 +806,9 @@
}
UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
- android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
- new android.hardware.automotive.vehicle.V2_0.UserInfo();
- halTargetUser.userId = targetUser.id;
- halTargetUser.flags = UserHalHelper.convertFlags(targetUser);
- mHal.switchUser(halTargetUser, timeoutMs, usersInfo, (status, resp) -> {
+ SwitchUserRequest request = createUserSwitchRequest(targetUserId, usersInfo);
+
+ mHal.switchUser(request, timeoutMs, (status, resp) -> {
if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
Log.d(TAG, "switch response: status="
+ UserHalHelper.halCallbackStatusToString(status) + ", resp=" + resp);
@@ -870,6 +881,62 @@
});
}
+ @Override
+ public UserRemovalResult removeUser(@UserIdInt int userId) {
+ checkManageUsersPermission("removeUser");
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_REMOVE_USER_REQ, userId);
+ // If the requested user is the current user, return error.
+ if (ActivityManager.getCurrentUser() == userId) {
+ return logAndGetResults(userId,
+ UserRemovalResult.STATUS_TARGET_USER_IS_CURRENT_USER);
+ }
+
+ // If requested user is the only admin user, return error.
+ UserInfo userInfo = mUserManager.getUserInfo(userId);
+ if (userInfo == null) {
+ return logAndGetResults(userId, UserRemovalResult.STATUS_USER_DOES_NOT_EXIST);
+ }
+
+ android.hardware.automotive.vehicle.V2_0.UserInfo halUser =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ halUser.userId = userInfo.id;
+ halUser.flags = UserHalHelper.convertFlags(userInfo);
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+
+ // Do not delete last admin user.
+ if (UserHalHelper.isAdmin(halUser.flags)) {
+ int size = usersInfo.existingUsers.size();
+ int totalAdminUsers = 0;
+ for (int i = 0; i < size; i++) {
+ if (UserHalHelper.isAdmin(usersInfo.existingUsers.get(i).flags)) {
+ totalAdminUsers++;
+ }
+ }
+ if (totalAdminUsers == 1) {
+ return logAndGetResults(userId,
+ UserRemovalResult.STATUS_TARGET_USER_IS_LAST_ADMIN_USER);
+ }
+ }
+
+ // First remove user from android and then remove from HAL because HAL remove user is one
+ // way call.
+ if (!mUserManager.removeUser(userId)) {
+ return logAndGetResults(userId, UserRemovalResult.STATUS_ANDROID_FAILURE);
+ }
+
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.removedUserInfo = halUser;
+ request.usersInfo = usersInfo;
+ mHal.removeUser(request);
+ return logAndGetResults(userId, UserRemovalResult.STATUS_SUCCESSFUL);
+ }
+
+ private UserRemovalResult logAndGetResults(@UserIdInt int userId,
+ @UserRemovalResult.Status int result) {
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_REMOVE_USER_RESP, userId, result);
+ return new UserRemovalResult(result);
+ }
+
private void sendUserSwitchUiCallback(@UserIdInt int targetUserId) {
if (mUserSwitchUiReceiver == null) {
Log.w(TAG_USER, "No User switch UI receiver.");
@@ -1166,17 +1233,26 @@
mRequestIdForUserSwitchInProcess = requestId;
}
}
-
private void postSwitchHalResponse(int requestId, @UserIdInt int targetUserId) {
- UserInfo targetUser = mUserManager.getUserInfo(targetUserId);
UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_POST_SWITCH_USER_REQ, requestId,
+ targetUserId, usersInfo.currentUser.userId);
+ SwitchUserRequest request = createUserSwitchRequest(targetUserId, usersInfo);
+ request.requestId = requestId;
+ mHal.postSwitchResponse(request);
+ }
+
+ private SwitchUserRequest createUserSwitchRequest(@UserIdInt int targetUserId,
+ @NonNull UsersInfo usersInfo) {
+ UserInfo targetUser = mUserManager.getUserInfo(targetUserId);
android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
new android.hardware.automotive.vehicle.V2_0.UserInfo();
halTargetUser.userId = targetUser.id;
halTargetUser.flags = UserHalHelper.convertFlags(targetUser);
- EventLog.writeEvent(EventLogTags.CAR_USER_SVC_POST_SWITCH_USER_REQ, requestId,
- targetUserId, usersInfo.currentUser.userId);
- mHal.postSwitchResponse(requestId, halTargetUser, usersInfo);
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.targetUser = halTargetUser;
+ request.usersInfo = usersInfo;
+ return request;
}
/**
@@ -1536,9 +1612,8 @@
// Switch HAL users if user switch is not requested by CarUserService
notifyHalLegacySwitch(fromUserId, toUserId);
- if (!UserHelper.isHeadlessSystemUser(toUserId)) {
- mCarUserManagerHelper.setLastActiveUser(toUserId);
- }
+ mCarUserManagerHelper.setLastActiveUser(toUserId);
+
if (mLastPassengerId != UserHandle.USER_NULL) {
stopPassengerInternal(mLastPassengerId, false);
}
@@ -1555,13 +1630,9 @@
}
// switch HAL user
- UserInfo targetUser = mUserManager.getUserInfo(toUserId);
- android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
- new android.hardware.automotive.vehicle.V2_0.UserInfo();
- halTargetUser.userId = targetUser.id;
- halTargetUser.flags = UserHalHelper.convertFlags(targetUser);
UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
- mHal.legacyUserSwitch(halTargetUser, usersInfo);
+ SwitchUserRequest request = createUserSwitchRequest(toUserId, usersInfo);
+ mHal.legacyUserSwitch(request);
}
/**
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/CarTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/CarPermisisonTest.java
similarity index 97%
rename from tests/CarSecurityPermissionTest/src/com/android/car/CarTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/CarPermisisonTest.java
index 90378a8..07f4310 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/CarTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/CarPermisisonTest.java
@@ -29,10 +29,10 @@
import org.junit.runner.RunWith;
/**
- * This class contains security permission tests for the {@link CarTest}'s system APIs.
+ * This class contains security permission tests for the {@link Car}'s system APIs.
*/
@RunWith(AndroidJUnit4.class)
-public class CarTest {
+public class CarPermisisonTest {
private Car mCar = null;
@Before
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicPermissionTest.java
similarity index 98%
rename from tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicPermissionTest.java
index 92226a1..975bc74 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicPermissionTest.java
@@ -40,11 +40,11 @@
* This class contains security permission tests for the {@link CarPropertyManager}'s public APIs.
*/
@RunWith(AndroidJUnit4.class)
-public class CarPropertyManagerPublicTest {
+public class CarPropertyManagerPublicPermissionTest {
private Car mCar = null;
private CarPropertyManager mPropertyManager;
private HashSet<Integer> mProps = new HashSet<>();
- private static final String TAG = CarPropertyManagerPublicTest.class.getSimpleName();
+ private static final String TAG = CarPropertyManagerPublicPermissionTest.class.getSimpleName();
private static final Integer DUMMY_AREA_ID = VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
// Dummy values for setter test.
private static final int DUMMY_PROPERTY_VALUE_INTEGER = 1;
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicPermissionTest.java
similarity index 97%
rename from tests/CarSecurityPermissionTest/src/com/android/car/CarPublicTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/CarPublicPermissionTest.java
index af1867b..16aef37 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicPermissionTest.java
@@ -29,10 +29,10 @@
import org.junit.runner.RunWith;
/**
- * This class contains security permission tests for the {@link CarTest}'s public APIs.
+ * This class contains security permission tests for the {@link CarPermisisonTest}'s public APIs.
*/
@RunWith(AndroidJUnit4.class)
-public class CarPublicTest {
+public class CarPublicPermissionTest {
private Car mCar = null;
@Before
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/content/pm/CarPackageManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/content/pm/CarPackageManagerPermissionTest.java
new file mode 100644
index 0000000..f1e23ba
--- /dev/null
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/content/pm/CarPackageManagerPermissionTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.car.content.pm.CarAppBlockingPolicy;
+import android.car.content.pm.CarPackageManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * This class contains security permission tests for {@link CarPackageManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarPackageManagerPermissionTest {
+ private Car mCar;
+ private CarPackageManager mPm;
+
+ @Before
+ public void setUp() throws Exception {
+ mCar = Car.createCar(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ mPm = (CarPackageManager) mCar.getCarManager(Car.PACKAGE_SERVICE);
+ }
+
+ @After
+ public void tearDown() {
+ mCar.disconnect();
+ }
+
+ @Test
+ public void testRestartTask() {
+ assertThrows(SecurityException.class, () -> mPm.restartTask(0));
+ }
+
+ @Test
+ public void testSetAppBlockingPolicy() {
+ String packageName = "com.android";
+ CarAppBlockingPolicy policy = new CarAppBlockingPolicy(null, null);
+ assertThrows(SecurityException.class, () -> mPm.setAppBlockingPolicy(packageName, policy,
+ 0));
+ }
+
+}
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
similarity index 97%
rename from tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
index 354446e..9730d13 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
@@ -35,7 +35,7 @@
* This class contains security permission tests for the {@link CarInputManager}'s system APIs.
*/
@RunWith(AndroidJUnit4.class)
-public class CarInputManagerTest {
+public class CarInputManagerPermisisonTest {
private Car mCar;
private CarInputManager mCarInputManager;
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPermissionTest.java
similarity index 87%
rename from tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPermissionTest.java
index 1bb81ec..1e4391b 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPermissionTest.java
@@ -25,16 +25,12 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeNotNull;
import static org.testng.Assert.expectThrows;
import android.car.Car;
import android.car.media.CarAudioManager;
import android.content.Context;
-import android.hardware.display.DisplayManager;
import android.os.Handler;
-import android.view.Display;
-import android.view.DisplayAddress;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -43,15 +39,13 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Arrays;
import java.util.Objects;
-import java.util.Optional;
/**
* This class contains security permission tests for the {@link CarAudioManager}'s system APIs.
*/
@RunWith(AndroidJUnit4.class)
-public final class CarAudioManagerTest {
+public final class CarAudioManagerPermissionTest {
private static final int GROUP_ID = 0;
private static final int UID = 10;
@@ -220,22 +214,6 @@
}
@Test
- public void getZoneIdForDisplayPermission() {
- Display display = getPhysicalDisplay();
- assumeNotNull(display);
- Exception e = expectThrows(SecurityException.class,
- () -> mCarAudioManager.getZoneIdForDisplay(display));
- assertThat(e.getMessage()).contains(PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
- }
-
- @Test
- public void getZoneIdForDisplayPortIdPermission() {
- Exception e = expectThrows(SecurityException.class,
- () -> mCarAudioManager.getZoneIdForDisplayPortId(Byte.MAX_VALUE));
- assertThat(e.getMessage()).contains(PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
- }
-
- @Test
public void getOutputDeviceForUsagePermission() {
Exception e = expectThrows(SecurityException.class,
() -> mCarAudioManager.getOutputDeviceForUsage(PRIMARY_AUDIO_ZONE, USAGE_MEDIA));
@@ -255,12 +233,4 @@
() -> mCarAudioManager.onCarDisconnected());
assertThat(e.getMessage()).contains(PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
}
-
- private Display getPhysicalDisplay() {
- DisplayManager displayManager = (DisplayManager) mContext.getSystemService(
- Context.DISPLAY_SERVICE);
- Optional<Display> physical = Arrays.stream(displayManager.getDisplays()).filter(
- display -> (display.getAddress() instanceof DisplayAddress.Physical)).findFirst();
- return physical.orElse(null);
- }
}
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicPermissionTest.java
similarity index 96%
rename from tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicPermissionTest.java
index 2877043..b9cafc4 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicPermissionTest.java
@@ -39,7 +39,7 @@
* This class contains security permission tests for the {@link CarAudioManager}'s public APIs.
*/
@RunWith(AndroidJUnit4.class)
-public final class CarAudioManagerPublicTest {
+public final class CarAudioManagerPublicPermissionTest {
private CarAudioManager mCarAudioManager;
@Before
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
index b0edd13..3a5ea1b 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
@@ -82,6 +82,12 @@
}
@Test
+ public void testRemoveUserPermission() throws Exception {
+ Exception e = expectThrows(SecurityException.class, () -> mCarUserManager.removeUser(100));
+ assertThat(e.getMessage()).contains(MANAGE_USERS);
+ }
+
+ @Test
public void testAddListenerPermission() {
UserLifecycleListener listener = (e) -> { };
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
index 4a4681b..8b64620 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
@@ -54,7 +54,7 @@
// Request all application focuses and abandon them to ensure no active context is present
// when test starts.
int[] activeTypes = mManager.getActiveAppTypes();
- FocusOwnershipCallback owner = new FocusOwnershipCallback();
+ FocusOwnershipCallback owner = new FocusOwnershipCallback(/* assertEventThread= */ false);
for (int i = 0; i < activeTypes.length; i++) {
mManager.requestAppFocus(activeTypes[i], owner);
owner.waitForOwnershipGrantAndAssert(DEFAULT_WAIT_TIMEOUT_MS, activeTypes[i]);
@@ -165,9 +165,9 @@
assertThat(manager1.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder();
assertThat(manager2.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder();
assertThat(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
- APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+ APP_FOCUS_TYPE_NAVIGATION, true)).isTrue();
assertThat(change1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
- APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+ APP_FOCUS_TYPE_NAVIGATION, true)).isTrue();
assertThat(manager2.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner2))
.isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
@@ -337,6 +337,15 @@
private final Semaphore mLossEventWait = new Semaphore(0);
private int mLastGrantEvent;
private final Semaphore mGrantEventWait = new Semaphore(0);
+ private final boolean mAssertEventThread;
+
+ private FocusOwnershipCallback(boolean assertEventThread) {
+ mAssertEventThread = assertEventThread;
+ }
+
+ private FocusOwnershipCallback() {
+ this(true);
+ }
boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedAppType)
throws Exception {
@@ -359,7 +368,9 @@
@Override
public void onAppFocusOwnershipLost(int appType) {
Log.i(TAG, "onAppFocusOwnershipLost " + appType);
- assertEventThread();
+ if (mAssertEventThread) {
+ assertEventThread();
+ }
mLastLossEvent = appType;
mLossEventWait.release();
}
diff --git a/tests/carservice_test/res/raw/car_audio_configuration.xml b/tests/carservice_test/res/raw/car_audio_configuration.xml
index b4bb08a..e1d75df 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration.xml
@@ -21,10 +21,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V1.xml b/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
index 4aaaa6c..26b1c08 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
@@ -33,10 +33,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml b/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml
index 03e9a0f..5c39ceb 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml
@@ -33,10 +33,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
index 4b5abc5..8561968 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="1" occupantZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
index 1d15b36..8c1d0e8 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2" occupantZoneId="1">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
index 288e8fb..dba13a2 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
@@ -17,10 +17,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="1"/>
- </displays>
</zone>
</zones>
</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
index 182e606..972da37 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
index acdb784..060934b 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
index b7d7a62..cffad51 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
index 09168a6..8e1c41b 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
index af57c24..f00b169 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
index f6995bc..9adef24 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
@@ -21,9 +21,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="one"/>
- </displays>
</zone>
</zones>
</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
index f822469..4364a1d 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
@@ -33,10 +33,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="0">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml b/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
index 83c054c..a359d36 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
index 6f845e7..2d25716 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
index a37c0e4..fce0a78 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
@@ -28,7 +28,6 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.util.SparseIntArray;
-import android.view.DisplayAddress;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -293,56 +292,6 @@
}
@Test
- public void loadAudioZones_parsesPhysicalDisplayAddresses() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
- mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
- CarAudioZone[] zones = cazh.loadAudioZones();
-
- CarAudioZone primaryZone = zones[0];
- List<DisplayAddress.Physical> primaryPhysicals = primaryZone.getPhysicalDisplayAddresses();
- assertThat(primaryPhysicals).hasSize(2);
- assertThat(primaryPhysicals.get(0).getPort()).isEqualTo(1);
- assertThat(primaryPhysicals.get(1).getPort()).isEqualTo(2);
- }
-
- @Test
- public void loadAudioZones_defaultsDisplayAddressesToEmptyList() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
- mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
- CarAudioZone[] zones = cazh.loadAudioZones();
-
- CarAudioZone rseZone = zones[1];
- List<DisplayAddress.Physical> rsePhysicals = rseZone.getPhysicalDisplayAddresses();
- assertThat(rsePhysicals).isEmpty();
- }
-
- @Test(expected = RuntimeException.class)
- public void loadAudioZones_throwsOnDuplicatePorts() throws Exception {
- try (InputStream duplicatePortStream = mContext.getResources().openRawResource(
- R.raw.car_audio_configuration_duplicate_ports)) {
- CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mCarAudioSettings, duplicatePortStream,
- mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
- cazh.loadAudioZones();
- }
- }
-
- @Test
- public void loadAudioZones_throwsOnNonNumericalPort() {
- InputStream duplicatePortStream = mContext.getResources().openRawResource(
- R.raw.car_audio_configuration_non_numerical_port);
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, duplicatePortStream,
- mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
- IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
- cazh::loadAudioZones);
- assertThat(exception).hasMessageThat().contains("Port one is not a number");
- }
-
- @Test
public void loadAudioZones_passesOnMissingAudioZoneIdForPrimary() throws Exception {
try (InputStream missingAudioZoneIdStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_no_audio_zone_id_for_primary_zone)) {
diff --git a/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java b/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
index 5fe5670..62cf6d4 100644
--- a/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
@@ -16,6 +16,8 @@
package android.car.userlib;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetSystemUser;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserInfo;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUsers;
import static android.car.test.util.UserTestingHelper.newUser;
import static android.os.UserHandle.USER_SYSTEM;
@@ -31,6 +33,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.car.settings.CarSettings;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.content.ContentResolver;
import android.content.Context;
@@ -38,7 +41,6 @@
import android.content.res.Resources;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.sysprop.CarProperties;
import androidx.test.InstrumentationRegistry;
@@ -192,12 +194,31 @@
}
@Test
- public void testGetInitialUser_WithNonExistLastActiveUser_ReturnsSmallestUserId() {
- setLastActiveUser(12);
- mockGetUsers(USER_SYSTEM, 10, 10 + 1);
+ public void testGetInitialUser_WithNonExistLastActiveUser_ReturnsLastPersistentUser() {
+ setLastActiveUser(120);
+ setLastPersistentActiveUser(110);
+ mockGetUsers(USER_SYSTEM, 100, 110);
assertThat(mCarUserManagerHelper.getInitialUser(/* usesOverrideUserIdProperty= */ true))
- .isEqualTo(10);
+ .isEqualTo(110);
+ // should have reset last active user
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+ .isEqualTo(UserHandle.USER_NULL);
+ }
+
+ @Test
+ public void testGetInitialUser_WithNonExistLastActiveAndPersistentUsers_ReturnsSmallestUser() {
+ setLastActiveUser(120);
+ setLastPersistentActiveUser(120);
+ mockGetUsers(USER_SYSTEM, 100, 110);
+
+ assertThat(mCarUserManagerHelper.getInitialUser(/* usesOverrideUserIdProperty= */ true))
+ .isEqualTo(100);
+ // should have reset both settions
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+ .isEqualTo(UserHandle.USER_NULL);
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID))
+ .isEqualTo(UserHandle.USER_NULL);
}
@Test
@@ -289,6 +310,65 @@
assertThat(mCarUserManagerHelper.hasInitialUser()).isFalse();
}
+ @Test
+ public void testSetLastActiveUser_headlessSystem() {
+ mockIsHeadlessSystemUserMode(true);
+ mockUmGetSystemUser(mUserManager);
+
+ mCarUserManagerHelper.setLastActiveUser(UserHandle.USER_SYSTEM);
+
+ assertSettingsNotSet(CarSettings.Global.LAST_ACTIVE_USER_ID);
+ assertSettingsNotSet(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+ }
+
+ @Test
+ public void testSetLastActiveUser_nonHeadlessSystem() {
+ mockIsHeadlessSystemUserMode(false);
+ mockUmGetSystemUser(mUserManager);
+
+ mCarUserManagerHelper.setLastActiveUser(UserHandle.USER_SYSTEM);
+
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+ .isEqualTo(UserHandle.USER_SYSTEM);
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID))
+ .isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void testSetLastActiveUser_nonExistingUser() {
+ // Don't need to mock um.getUser(), it will return null by default
+ mCarUserManagerHelper.setLastActiveUser(42);
+
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID)).isEqualTo(42);
+ assertSettingsNotSet(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+ }
+
+ @Test
+ public void testSetLastActiveUser_ephemeralUser() {
+ int persistentUserId = 42;
+ int ephemeralUserid = 108;
+ mockUmGetUserInfo(mUserManager, persistentUserId, NO_FLAGS);
+ mockUmGetUserInfo(mUserManager, ephemeralUserid, UserInfo.FLAG_EPHEMERAL);
+
+ mCarUserManagerHelper.setLastActiveUser(persistentUserId);
+ mCarUserManagerHelper.setLastActiveUser(ephemeralUserid);
+
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+ .isEqualTo(ephemeralUserid);
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID))
+ .isEqualTo(persistentUserId);
+ }
+
+ @Test
+ public void testSetLastActiveUser_nonEphemeralUser() {
+ mockUmGetUserInfo(mUserManager, 42, NO_FLAGS);
+
+ mCarUserManagerHelper.setLastActiveUser(42);
+
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID)).isEqualTo(42);
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID)).isEqualTo(42);
+ }
+
private void mockGetUsers(@NonNull @UserIdInt int... userIds) {
mockUmGetUsers(mUserManager, userIds);
}
@@ -298,7 +378,11 @@
}
private void setLastActiveUser(@UserIdInt int userId) {
- putSettingsInt(Settings.Global.LAST_ACTIVE_USER_ID, userId);
+ putSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID, userId);
+ }
+
+ private void setLastPersistentActiveUser(@UserIdInt int userId) {
+ putSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID, userId);
}
private void setDefaultBootUserOverride(@UserIdInt int userId) {
diff --git a/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java b/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
index b7816c2..316cd32 100644
--- a/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
@@ -17,6 +17,8 @@
package android.car.userlib;
import static android.car.userlib.UserHalHelper.CREATE_USER_PROPERTY;
+import static android.car.userlib.UserHalHelper.REMOVE_USER_PROPERTY;
+import static android.car.userlib.UserHalHelper.SWITCH_USER_PROPERTY;
import static android.car.userlib.UserHalHelper.USER_IDENTIFICATION_ASSOCIATION_PROPERTY;
import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.ASSOCIATE_CURRENT_USER;
import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.DISASSOCIATE_ALL_USERS;
@@ -49,6 +51,9 @@
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
@@ -861,6 +866,62 @@
}
@Test
+ public void testRemoveUserRequestToVehiclePropValue_null() {
+ assertThrows(NullPointerException.class,
+ () -> UserHalHelper.toVehiclePropValue((RemoveUserRequest) null));
+ }
+
+ @Test
+ public void testRemoveUserRequestToVehiclePropValue_emptyRequest() {
+ RemoveUserRequest request = new RemoveUserRequest();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
+ public void testRemoveUserRequestToVehiclePropValue_missingRequestId() {
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.removedUserInfo.userId = 11;
+ request.usersInfo.existingUsers.add(request.removedUserInfo);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
+ public void testRemoveUserRequestToVehiclePropValue_ok() {
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.requestId = 42;
+
+ android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ user10.userId = 10;
+ user10.flags = UserFlags.ADMIN;
+
+ // existing users
+ request.usersInfo.numberUsers = 1;
+ request.usersInfo.existingUsers.add(user10);
+
+ // current user
+ request.usersInfo.currentUser = user10;
+ // user to remove
+ request.removedUserInfo = user10;
+
+ VehiclePropValue propValue = UserHalHelper.toVehiclePropValue(request);
+
+ assertWithMessage("wrong prop on %s", propValue).that(propValue.prop)
+ .isEqualTo(REMOVE_USER_PROPERTY);
+ assertWithMessage("wrong int32values on %s", propValue).that(propValue.value.int32Values)
+ .containsExactly(42, // request id
+ 10, UserFlags.ADMIN, // user to remove
+ 10, UserFlags.ADMIN, // current user
+ 1, // number of users
+ 10, UserFlags.ADMIN // existing user 1
+ ).inOrder();
+ }
+
+ @Test
public void testCreateUserRequestToVehiclePropValue_null() {
assertThrows(NullPointerException.class,
() -> UserHalHelper.toVehiclePropValue((CreateUserRequest) null));
@@ -984,6 +1045,84 @@
}
@Test
+ public void testSwitchUserRequestToVehiclePropValue_null() {
+ assertThrows(NullPointerException.class,
+ () -> UserHalHelper.toVehiclePropValue((SwitchUserRequest) null));
+ }
+
+ @Test
+ public void testSwitchUserRequestToVehiclePropValue_emptyRequest() {
+ SwitchUserRequest request = new SwitchUserRequest();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
+ public void testSwitchUserRequestToVehiclePropValue_missingMessageType() {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.requestId = 42;
+ android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ user10.userId = 10;
+ request.usersInfo.numberUsers = 1;
+ request.usersInfo.existingUsers.add(user10);
+ request.usersInfo.currentUser = user10;
+ request.targetUser = user10;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
+ public void testSwitchUserRequestToVehiclePropValue_incorrectMessageType() {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.requestId = 42;
+ request.messageType = -1;
+ android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ user10.userId = 10;
+ request.usersInfo.numberUsers = 1;
+ request.usersInfo.existingUsers.add(user10);
+ request.usersInfo.currentUser = user10;
+ request.targetUser = user10;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
+ public void tesSwitchUserRequestToVehiclePropValue_ok() {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.requestId = 42;
+ android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ user10.userId = 10;
+ user10.flags = UserFlags.ADMIN;
+ // existing users
+ request.usersInfo.numberUsers = 1;
+ request.usersInfo.existingUsers.add(user10);
+ // current user
+ request.usersInfo.currentUser = user10;
+ // user to remove
+ request.targetUser = user10;
+ request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+
+ VehiclePropValue propValue = UserHalHelper.toVehiclePropValue(request);
+
+ assertWithMessage("wrong prop on %s", propValue).that(propValue.prop)
+ .isEqualTo(SWITCH_USER_PROPERTY);
+ assertWithMessage("wrong int32values on %s", propValue).that(propValue.value.int32Values)
+ .containsExactly(42, // request id
+ SwitchUserMessageType.ANDROID_SWITCH, // message type
+ 10, UserFlags.ADMIN, // target user
+ 10, UserFlags.ADMIN, // current user
+ 1, // number of users
+ 10, UserFlags.ADMIN // existing user 1
+ ).inOrder();
+ }
+
+ @Test
public void testNewUsersInfo_nullUm() {
assertThrows(IllegalArgumentException.class, () -> UserHalHelper.newUsersInfo(null));
}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
index 9f360e1..cabab88 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
@@ -30,16 +30,25 @@
import static org.mockito.Mockito.ignoreStubs;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.UserIdInt;
+import android.app.IActivityManager;
import android.car.CarProjectionManager;
import android.car.input.CarInputHandlingService.InputFilter;
import android.car.input.ICarInputListener;
+import android.car.testapi.BlockingUserLifecycleListener;
+import android.car.user.CarUserManager;
+import android.car.userlib.CarUserManagerHelper;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -48,17 +57,25 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
import android.service.voice.VoiceInteractionSession;
import android.telecom.TelecomManager;
+import android.test.mock.MockContentResolver;
import android.view.KeyEvent;
import androidx.test.core.app.ApplicationProvider;
import com.android.car.hal.InputHalService;
+import com.android.car.hal.UserHalService;
+import com.android.car.user.CarUserService;
import com.android.internal.app.AssistUtils;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
import com.google.common.collect.Range;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,6 +90,9 @@
@RunWith(MockitoJUnitRunner.class)
public class CarInputServiceTest {
+ // TODO(b/152069895): decrease value once refactored. In fact, it should not even use
+ // runWithScissors(), but only rely on CountdownLatches
+ private static final long DEFAULT_TIMEOUT_MS = 5_000;
@Mock InputHalService mInputHalService;
@Mock TelecomManager mTelecomManager;
@@ -84,12 +104,54 @@
@Spy Context mContext = ApplicationProvider.getApplicationContext();
@Spy Handler mHandler = new Handler(Looper.getMainLooper());
+ private MockContext mMockContext;
+ private CarUserService mCarUserService;
private CarInputService mCarInputService;
+ /**
+ * A mock {@link Context}.
+ * This class uses a mock {@link ContentResolver} and {@link android.content.ContentProvider} to
+ * avoid changing real system settings. Besides, to emulate the case where the OEM changes
+ * {@link R.string.rotaryService} to empty in the resource file (e.g., the OEM doesn't want to
+ * start RotaryService), this class allows to return a given String when retrieving {@link
+ * R.string.rotaryService}.
+ */
+ private static class MockContext extends BroadcastInterceptingContext {
+ private final MockContentResolver mContentResolver;
+ private final FakeSettingsProvider mContentProvider;
+ private final Resources mResources;
+
+ MockContext(Context base, String rotaryService) {
+ super(base);
+ FakeSettingsProvider.clearSettingsProvider();
+ mContentResolver = new MockContentResolver(this);
+ mContentProvider = new FakeSettingsProvider();
+ mContentResolver.addProvider(Settings.AUTHORITY, mContentProvider);
+
+ mResources = spy(base.getResources());
+ doReturn(rotaryService).when(mResources).getString(R.string.rotaryService);
+ }
+
+ void release() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
+
+ @Override
+ public Resources getResources() {
+ return mResources;
+ }
+ }
+
@Before
public void setUp() {
- mCarInputService = new CarInputService(mContext, mInputHalService, mHandler,
- mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
+ mCarUserService = mock(CarUserService.class);
+ mCarInputService = new CarInputService(mContext, mInputHalService, mCarUserService,
+ mHandler, mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
/* customInputServiceComponent= */ null, mLongPressDelaySupplier);
when(mInputHalService.isKeyInputSupported()).thenReturn(true);
@@ -100,6 +162,69 @@
}
@Test
+ public void rotaryServiceSettingsUpdated_whenRotaryServiceIsNotEmpty() throws Exception {
+ final String rotaryService = "com.android.car.rotary/com.android.car.rotary.RotaryService";
+ init(rotaryService);
+ assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
+
+ final int userId = 11;
+
+ // By default RotaryService is not enabled.
+ String enabledServices = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId);
+ assertThat(enabledServices == null ? "" : enabledServices).doesNotContain(rotaryService);
+
+ String enabled = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ userId);
+ assertThat(enabled).isNull();
+
+ // Enable RotaryService by sending user switch event.
+ sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, userId);
+
+ enabledServices = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId);
+ assertThat(enabledServices).contains(rotaryService);
+
+ enabled = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ userId);
+ assertThat(enabled).isEqualTo("1");
+ }
+
+ @Test
+ public void rotaryServiceSettingsNotUpdated_whenRotaryServiceIsEmpty() throws Exception {
+ final String rotaryService = "";
+ init(rotaryService);
+ assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
+
+ final int userId = 11;
+
+ // By default the Accessibility is disabled.
+ String enabled = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ userId);
+ assertThat(enabled).isNull();
+
+ sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, userId);
+
+ // Sending user switch event shouldn't enable the Accessibility because RotaryService is
+ // empty.
+ enabled = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ userId);
+ assertThat(enabled).isNull();
+ }
+
+ @Test
public void ordinaryEvents_onMainDisplay_routedToInputManager() {
KeyEvent event = send(Key.DOWN, KeyEvent.KEYCODE_ENTER, Display.MAIN);
@@ -458,6 +583,54 @@
assertThat(timeCaptor.getValue()).isIn(Range.closed(then + systemDelay, now + systemDelay));
}
+ @After
+ public void tearDown() {
+ if (mMockContext != null) {
+ mMockContext.release();
+ mMockContext = null;
+ }
+ }
+
+ /**
+ * Initializes {@link #mMockContext}, {@link #mCarUserService}, and {@link #mCarInputService}.
+ */
+ private void init(String rotaryService) {
+ mMockContext = new MockContext(mContext, rotaryService);
+
+ UserManager userManager = mock(UserManager.class);
+ UserInfo userInfo = mock(UserInfo.class);
+ doReturn(userInfo).when(userManager).getUserInfo(anyInt());
+ UserHalService userHal = mock(UserHalService.class);
+ CarUserManagerHelper carUserManagerHelper = mock(CarUserManagerHelper.class);
+ IActivityManager iActivityManager = mock(IActivityManager.class);
+ mCarUserService = new CarUserService(mMockContext, userHal, carUserManagerHelper,
+ userManager, iActivityManager, /* maxRunningUsers= */ 2);
+
+ mCarInputService = new CarInputService(mMockContext, mInputHalService, mCarUserService,
+ mHandler, mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
+ /* customInputServiceComponent= */ null, mLongPressDelaySupplier);
+ mCarInputService.init();
+ }
+
+ private void sendUserLifecycleEvent(@CarUserManager.UserLifecycleEventType int eventType,
+ @UserIdInt int userId) throws InterruptedException {
+ // Add a blocking listener to ensure CarUserService event notification is completed
+ // before proceeding with test execution.
+ BlockingUserLifecycleListener blockingListener =
+ BlockingUserLifecycleListener.forAnyEvent().build();
+ mCarUserService.addUserLifecycleListener(blockingListener);
+
+ runOnMainThreadAndWaitForIdle(() -> mCarUserService.onUserLifecycleEvent(eventType,
+ /* timestampMs= */ 0, /* fromUserId= */ UserHandle.USER_NULL, userId));
+ blockingListener.waitForAnyEvent();
+ }
+
+ private static void runOnMainThreadAndWaitForIdle(Runnable r) {
+ Handler.getMain().runWithScissors(r, DEFAULT_TIMEOUT_MS);
+ // Run empty runnable to make sure that all posted handlers are done.
+ Handler.getMain().runWithScissors(() -> { }, DEFAULT_TIMEOUT_MS);
+ }
+
private enum Key {DOWN, UP}
private enum Display {MAIN, INSTRUMENT_CLUSTER}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
index f146dcb..3cbfffc 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
@@ -62,6 +62,7 @@
import com.android.car.systeminterface.WakeLockInterface;
import com.android.car.test.utils.TemporaryDirectory;
import com.android.car.user.CarUserService;
+import com.android.internal.app.IVoiceInteractionManagerService;
import org.junit.After;
import org.junit.Before;
@@ -107,6 +108,9 @@
private CarUserService mUserService;
@Mock
private InitialUserSetter mInitialUserSetter;
+ @Mock
+ private IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
@Override
protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
@@ -150,7 +154,8 @@
+ ", maxGarageModeRunningDurationInSecs="
+ mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
- mSystemInterface, mUserManager, mUserService, mInitialUserSetter);
+ mSystemInterface, mUserManager, mUserService, mInitialUserSetter,
+ mVoiceInteractionManagerService);
mService.init();
mService.setShutdownTimersForTest(0, 0);
mPowerHal.setSignalListener(mPowerSignalListener);
@@ -501,6 +506,7 @@
VehicleApPowerStateShutdownParam.CAN_SLEEP));
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isFalse();
assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+ assertVoiceInteractionDisabled();
mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
// Send the finished signal
@@ -530,6 +536,7 @@
mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
// Since we just woke up from shutdown, wake up time will be 0
assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
+ assertVoiceInteractionEnabled();
assertThat(mDisplayInterface.getDisplayState()).isFalse();
}
@@ -573,6 +580,14 @@
assertStateReceivedForShutdownOrSleepWithPostpone(lastState, expectedSecondParameter);
}
+ private void assertVoiceInteractionEnabled() throws Exception {
+ verify(mVoiceInteractionManagerService).setDisabled(false);
+ }
+
+ private void assertVoiceInteractionDisabled() throws Exception {
+ verify(mVoiceInteractionManagerService).setDisabled(true);
+ }
+
private static void waitForSemaphore(Semaphore semaphore, long timeoutMs)
throws InterruptedException {
if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
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 2b11bd7..e6a81b6 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
@@ -18,6 +18,7 @@
import static android.car.VehiclePropertyIds.CREATE_USER;
import static android.car.VehiclePropertyIds.CURRENT_GEAR;
import static android.car.VehiclePropertyIds.INITIAL_USER_INFO;
+import static android.car.VehiclePropertyIds.REMOVE_USER;
import static android.car.VehiclePropertyIds.SWITCH_USER;
import static android.car.VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION;
import static android.car.test.mocks.CarArgumentMatchers.isProperty;
@@ -53,7 +54,9 @@
import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
@@ -413,28 +416,28 @@
@Test
public void testSwitchUser_invalidTimeout() {
- assertThrows(IllegalArgumentException.class,
- () -> mUserHalService.switchUser(mUser10, 0, mUsersInfo, noOpCallback()));
- assertThrows(IllegalArgumentException.class,
- () -> mUserHalService.switchUser(mUser10, -1, mUsersInfo, noOpCallback()));
+ assertThrows(IllegalArgumentException.class, () -> mUserHalService
+ .switchUser(createUserSwitchRequest(mUser10, mUsersInfo), 0, noOpCallback()));
+ assertThrows(IllegalArgumentException.class, () -> mUserHalService
+ .switchUser(createUserSwitchRequest(mUser10, mUsersInfo), -1, noOpCallback()));
}
@Test
public void testSwitchUser_noUsersInfo() {
- assertThrows(IllegalArgumentException.class,
- () -> mUserHalService.switchUser(mUser10, TIMEOUT_MS, null, noOpCallback()));
+ assertThrows(IllegalArgumentException.class, () -> mUserHalService
+ .switchUser(createUserSwitchRequest(mUser10, null), TIMEOUT_MS, noOpCallback()));
}
@Test
public void testSwitchUser_noCallback() {
- assertThrows(NullPointerException.class,
- () -> mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, null));
+ assertThrows(NullPointerException.class, () -> mUserHalService
+ .switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS, null));
}
@Test
public void testSwitchUser_noTarget() {
- assertThrows(NullPointerException.class,
- () -> mUserHalService.switchUser(null, TIMEOUT_MS, mUsersInfo, noOpCallback()));
+ assertThrows(NullPointerException.class, () -> mUserHalService
+ .switchUser(createUserSwitchRequest(null, mUsersInfo), TIMEOUT_MS, noOpCallback()));
}
@Test
@@ -443,7 +446,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
assertCallbackStatus(callback, HalCallback.STATUS_HAL_SET_TIMEOUT);
@@ -458,7 +462,8 @@
public void testSwitchUser_halDidNotReply() throws Exception {
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
@@ -475,7 +480,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
@@ -493,7 +499,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
@@ -517,7 +524,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
@@ -545,7 +553,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
@@ -567,8 +576,10 @@
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);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback1);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback2);
callback1.assertCalled();
assertCallbackStatus(callback1, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
@@ -590,7 +601,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
@@ -634,30 +646,113 @@
@Test
public void testPostSwitchResponse_noUsersInfo() {
- assertThrows(NullPointerException.class,
- () -> mUserHalService.postSwitchResponse(42, mUser10, null));
+ SwitchUserRequest request = createUserSwitchRequest(mUser10, null);
+ request.requestId = 42;
+ assertThrows(NullPointerException.class, () -> mUserHalService.postSwitchResponse(request));
}
@Test
public void testPostSwitchResponse_HalCalledWithCorrectProp() {
- mUserHalService.postSwitchResponse(42, mUser10, mUsersInfo);
+ SwitchUserRequest request = createUserSwitchRequest(mUser10, mUsersInfo);
+ request.requestId = 42;
+ mUserHalService.postSwitchResponse(request);
ArgumentCaptor<VehiclePropValue> propCaptor =
ArgumentCaptor.forClass(VehiclePropValue.class);
verify(mVehicleHal).set(propCaptor.capture());
VehiclePropValue prop = propCaptor.getValue();
- assertHalSetSwitchUserRequest(prop, SwitchUserMessageType.ANDROID_POST_SWITCH,
- mUser10);
+ assertHalSetSwitchUserRequest(prop, SwitchUserMessageType.ANDROID_POST_SWITCH, mUser10);
+ }
+
+ @Test
+ public void testLegacyUserSwitch_nullRequest() {
+ assertThrows(NullPointerException.class, () -> mUserHalService.legacyUserSwitch(null));
+ }
+
+ @Test
+ public void testLegacyUserSwitch_noMessageType() {
+ SwitchUserRequest request = new SwitchUserRequest();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.legacyUserSwitch(request));
+ }
+
+ @Test
+ public void testLegacyUserSwitch_noTargetUserInfo() {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.legacyUserSwitch(request));
+ }
+
+ @Test
+ public void testRemoveUser_nullRequest() {
+ RemoveUserRequest request = null;
+
+ assertThrows(NullPointerException.class,
+ () -> mUserHalService.removeUser(request));
+ }
+
+ @Test
+ public void testRemoveUser_noRequestId() {
+ RemoveUserRequest request = new RemoveUserRequest();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.removeUser(request));
+ }
+
+ @Test
+ public void testRemoveUser_noRemovedUserInfo() {
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.requestId = 1;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.removeUser(request));
+ }
+
+ @Test
+ public void testRemoveUser_noUsersInfo() {
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.requestId = 1;
+ request.removedUserInfo = mUser10;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.removeUser(request));
+ }
+
+ @Test
+ public void testRemoveUser_HalCalledWithCorrectProp() {
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.removedUserInfo = mUser10;
+ request.usersInfo = mUsersInfo;
+ ArgumentCaptor<VehiclePropValue> propCaptor =
+ ArgumentCaptor.forClass(VehiclePropValue.class);
+
+ mUserHalService.removeUser(request);
+
+ verify(mVehicleHal).set(propCaptor.capture());
+ assertHalSetRemoveUserRequest(propCaptor.getValue(), mUser10);
}
@Test
public void testLegacyUserSwitch_noUsersInfo() {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+ request.targetUser = mUser10;
+
assertThrows(IllegalArgumentException.class,
- () -> mUserHalService.legacyUserSwitch(mUser10, null));
+ () -> mUserHalService.legacyUserSwitch(request));
}
@Test
public void testLegacyUserSwitch_HalCalledWithCorrectProp() {
- mUserHalService.legacyUserSwitch(mUser10, mUsersInfo);
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.messageType = SwitchUserMessageType.LEGACY_ANDROID_SWITCH;
+ request.requestId = 1;
+ request.targetUser = mUser10;
+ request.usersInfo = mUsersInfo;
+
+ mUserHalService.legacyUserSwitch(request);
ArgumentCaptor<VehiclePropValue> propCaptor =
ArgumentCaptor.forClass(VehiclePropValue.class);
verify(mVehicleHal).set(propCaptor.capture());
@@ -1240,6 +1335,15 @@
"PropId: 0x" + Integer.toHexString(prop))).when(mVehicleHal).set(isProperty(prop));
}
+ @NonNull
+ private SwitchUserRequest createUserSwitchRequest(@NonNull UserInfo targetUser,
+ @NonNull UsersInfo usersInfo) {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.targetUser = targetUser;
+ request.usersInfo = usersInfo;
+ return request;
+ }
+
/**
* Creates and set expectations for a valid request.
*/
@@ -1313,6 +1417,17 @@
assertUsersInfo(req, mUsersInfo, 4);
}
+ private void assertHalSetRemoveUserRequest(VehiclePropValue req, UserInfo userInfo) {
+ assertThat(req.prop).isEqualTo(REMOVE_USER);
+ assertWithMessage("wrong request Id on %s", req).that(req.value.int32Values.get(0))
+ .isAtLeast(1);
+ assertWithMessage("user.id mismatch on %s", req).that(req.value.int32Values.get(1))
+ .isEqualTo(userInfo.userId);
+ assertWithMessage("user.flags mismatch on %s", req).that(req.value.int32Values.get(2))
+ .isEqualTo(userInfo.flags);
+ assertUsersInfo(req, mUsersInfo, 3);
+ }
+
private void assertHalSetCreateUserRequest(VehiclePropValue prop, CreateUserRequest request) {
assertThat(prop.prop).isEqualTo(CREATE_USER);
assertWithMessage("wrong request Id on %s", prop).that(prop.value.int32Values.get(0))
diff --git a/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
index 6100dbc..ae86874 100644
--- a/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
@@ -41,6 +41,7 @@
import com.android.car.systeminterface.DisplayInterface;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.systeminterface.SystemStateInterface;
+import com.android.internal.app.IVoiceInteractionManagerService;
import org.junit.After;
import org.junit.Before;
@@ -73,6 +74,8 @@
private Resources mResources;
@Mock
private Car mCar;
+ @Mock
+ private IVoiceInteractionManagerService mVoiceInteractionManagerService;
@Before
public void setUp() throws Exception {
@@ -204,7 +207,7 @@
+ ", maxGarageModeRunningDurationInSecs="
+ mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
- mSystemInterface, null, null, null);
+ mSystemInterface, null, null, null, mVoiceInteractionManagerService);
mService.init();
mService.setShutdownTimersForTest(0, 0);
assertStateReceived(MockedPowerHalService.SET_WAIT_FOR_VHAL, 0);
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
index 540e1d1..1003957 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
@@ -45,6 +45,7 @@
import android.car.user.CarUserManager.UserSwitchUiCallback;
import android.car.user.UserCreationResult;
import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
import android.car.user.UserSwitchResult;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
@@ -188,6 +189,28 @@
}
@Test
+ public void testRemoveUser_success() throws Exception {
+ int userId = 11;
+ int status = UserRemovalResult.STATUS_SUCCESSFUL;
+ when(mService.removeUser(userId)).thenReturn(new UserRemovalResult(status));
+
+ UserRemovalResult result = mMgr.removeUser(11);
+
+ assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_SUCCESSFUL);
+ }
+
+ @Test
+ public void testRemoveUser_remoteException() throws Exception {
+ int userId = 11;
+ doThrow(new RemoteException("D'OH!")).when(mService).removeUser(eq(userId));
+ mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+ UserRemovalResult result = mMgr.removeUser(11);
+
+ assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_HAL_INTERNAL_FAILURE);
+ }
+
+ @Test
public void testSetSwitchUserUICallback_success() throws Exception {
UserSwitchUiCallback callback = (u)-> { };
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index 4e82740..b0f1c29 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -18,10 +18,10 @@
import static android.car.test.mocks.AndroidMockitoHelper.getResult;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmCreateUser;
-import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetSystemUser;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserInfo;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUsers;
import static android.car.test.util.UserTestingHelper.UserInfoBuilder;
+import static android.content.pm.UserInfo.FLAG_ADMIN;
import static android.content.pm.UserInfo.FLAG_EPHEMERAL;
import static android.content.pm.UserInfo.FLAG_GUEST;
@@ -66,6 +66,7 @@
import android.car.user.CarUserManager.UserLifecycleListener;
import android.car.user.UserCreationResult;
import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
import android.car.user.UserSwitchResult;
import android.car.userlib.CarUserManagerHelper;
import android.car.userlib.HalCallback;
@@ -83,6 +84,8 @@
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
@@ -317,21 +320,6 @@
}
/**
- * Test that the {@link CarUserService} doesn't update last active user on user switch in
- * headless system user mode.
- */
- @Test
- public void testLastActiveUserUpdatedOnUserSwitch_headlessSystemUser() throws Exception {
- mockIsHeadlessSystemUser(mRegularUser.id, true);
- mockUmGetSystemUser(mMockedUserManager);
- mockExistingUsers();
-
- sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
-
- verifyLastActiveUserNotSet();
- }
-
- /**
* Test that the {@link CarUserService} sets default guest restrictions on first boot.
*/
@Test
@@ -673,6 +661,73 @@
}
@Test
+ public void testRemoveUser_currentUserCannotBeRemoved() throws Exception {
+ mockCurrentUser(mAdminUser);
+
+ UserRemovalResult result = mCarUserService.removeUser(mAdminUser.id);
+
+ assertThat(result.getStatus())
+ .isEqualTo(UserRemovalResult.STATUS_TARGET_USER_IS_CURRENT_USER);
+ }
+
+ @Test
+ public void testRemoveUser_userNotExist() throws Exception {
+ UserRemovalResult result = mCarUserService.removeUser(15);
+
+ assertThat(result.getStatus())
+ .isEqualTo(UserRemovalResult.STATUS_USER_DOES_NOT_EXIST);
+ }
+
+ @Test
+ public void testRemoveUser_lastAdminUser() throws Exception {
+ mockCurrentUser(mRegularUser);
+ mockExistingUsers();
+
+ UserRemovalResult result = mCarUserService.removeUser(mAdminUser.id);
+
+ assertThat(result.getStatus())
+ .isEqualTo(UserRemovalResult.STATUS_TARGET_USER_IS_LAST_ADMIN_USER);
+ }
+
+ @Test
+ public void testRemoveUser_notLastAdminUser_success() throws Exception {
+ // Give admin rights to regular user.
+ UserInfo currentUser = mRegularUser;
+ currentUser.flags = currentUser.flags | FLAG_ADMIN;
+ mockExistingUsersAndCurrentUser(currentUser);
+ int removeUserId = mAdminUser.id;
+ when(mMockedUserManager.removeUser(removeUserId)).thenReturn(true);
+
+ UserRemovalResult result = mCarUserService.removeUser(removeUserId);
+
+ assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_SUCCESSFUL);
+ assertHalRemove(currentUser.id, removeUserId);
+ }
+
+ @Test
+ public void testRemoveUser_success() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ int removeUserId = mRegularUser.id;
+ when(mMockedUserManager.removeUser(removeUserId)).thenReturn(true);
+
+ UserRemovalResult result = mCarUserService.removeUser(removeUserId);
+
+ assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_SUCCESSFUL);
+ assertHalRemove(mAdminUser.id, removeUserId);
+ }
+
+ @Test
+ public void testRemoveUser_androidFailure() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ int targetUserId = mRegularUser.id;
+ when(mMockedUserManager.removeUser(targetUserId)).thenReturn(false);
+
+ UserRemovalResult result = mCarUserService.removeUser(targetUserId);
+
+ assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_ANDROID_FAILURE);
+ }
+
+ @Test
public void testSwitchUser_nullReceiver() throws Exception {
mockExistingUsersAndCurrentUser(mAdminUser);
@@ -984,12 +1039,7 @@
sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
- ArgumentCaptor<android.hardware.automotive.vehicle.V2_0.UserInfo> targetUser =
- ArgumentCaptor.forClass(android.hardware.automotive.vehicle.V2_0.UserInfo.class);
- ArgumentCaptor<UsersInfo> usersInfo = ArgumentCaptor.forClass(UsersInfo.class);
- verify(mUserHal).legacyUserSwitch(targetUser.capture(), usersInfo.capture());
- assertThat(targetUser.getValue().userId).isEqualTo(mRegularUser.id);
- assertThat(usersInfo.getValue().currentUser.userId).isEqualTo(mAdminUser.id);
+ verify(mUserHal).legacyUserSwitch(isSwitchUserRequest(0, mAdminUser.id, mRegularUser.id));
}
@Test
@@ -1004,7 +1054,7 @@
sendUserSwitchingEvent(mAdminUser.id, mGuestUser.id);
- verify(mUserHal, never()).legacyUserSwitch(any(), any());
+ verify(mUserHal, never()).legacyUserSwitch(any());
}
@Test
@@ -1651,18 +1701,20 @@
halTargetUser.userId = androidTargetUser.id;
halTargetUser.flags = UserHalHelper.convertFlags(androidTargetUser);
UsersInfo usersInfo = newUsersInfo(currentUserId);
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.targetUser = halTargetUser;
+ request.usersInfo = usersInfo;
BlockingAnswer<Void> blockingAnswer = BlockingAnswer.forVoidReturn(10_000, (invocation) -> {
Log.d(TAG, "Answering " + invocation + " with " + response);
@SuppressWarnings("unchecked")
HalCallback<SwitchUserResponse> callback = (HalCallback<SwitchUserResponse>) invocation
- .getArguments()[3];
+ .getArguments()[2];
callback.onResponse(HalCallback.STATUS_OK, response);
});
- doAnswer(blockingAnswer).when(mUserHal).switchUser(eq(halTargetUser),
- eq(mAsyncCallTimeoutMs), eq(usersInfo), notNull());
+ doAnswer(blockingAnswer).when(mUserHal).switchUser(eq(request), eq(mAsyncCallTimeoutMs),
+ notNull());
return blockingAnswer;
-
}
private void mockHalSwitch(@UserIdInt int currentUserId,
@@ -1673,15 +1725,18 @@
halTargetUser.userId = androidTargetUser.id;
halTargetUser.flags = UserHalHelper.convertFlags(androidTargetUser);
UsersInfo usersInfo = newUsersInfo(currentUserId);
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.targetUser = halTargetUser;
+ request.usersInfo = usersInfo;
+
doAnswer((invocation) -> {
Log.d(TAG, "Answering " + invocation + " with " + response);
@SuppressWarnings("unchecked")
HalCallback<SwitchUserResponse> callback =
- (HalCallback<SwitchUserResponse>) invocation.getArguments()[3];
+ (HalCallback<SwitchUserResponse>) invocation.getArguments()[2];
callback.onResponse(callbackStatus, response);
return null;
- }).when(mUserHal).switchUser(eq(halTargetUser), eq(mAsyncCallTimeoutMs), eq(usersInfo),
- notNull());
+ }).when(mUserHal).switchUser(eq(request), eq(mAsyncCallTimeoutMs), notNull());
}
private void mockHalGetUserIdentificationAssociation(@NonNull UserInfo user,
@@ -1772,7 +1827,6 @@
for (int i = 0; i < actual.numberUsers; i++) {
assertSameUser(actual.existingUsers.get(i), mExistingUsers.get(i));
}
-
}
private void assertSameUser(android.hardware.automotive.vehicle.V2_0.UserInfo halUser,
@@ -1877,38 +1931,34 @@
}
private void assertNoPostSwitch() {
- verify(mUserHal, never()).postSwitchResponse(anyInt(), any(), any());
+ verify(mUserHal, never()).postSwitchResponse(any());
}
private void assertPostSwitch(int requestId, int currentId, int targetId) {
- // verify post switch response
- ArgumentCaptor<android.hardware.automotive.vehicle.V2_0.UserInfo> targetUser =
- ArgumentCaptor.forClass(android.hardware.automotive.vehicle.V2_0.UserInfo.class);
- ArgumentCaptor<UsersInfo> usersInfo = ArgumentCaptor.forClass(UsersInfo.class);
- verify(mUserHal).postSwitchResponse(eq(requestId), targetUser.capture(),
- usersInfo.capture());
- assertThat(targetUser.getValue().userId).isEqualTo(targetId);
- assertThat(usersInfo.getValue().currentUser.userId).isEqualTo(currentId);
+ verify(mUserHal).postSwitchResponse(isSwitchUserRequest(requestId, currentId, targetId));
}
private void assertHalSwitch(int currentId, int targetId) {
- verify(mUserHal).switchUser(isHalUser(targetId), eq(mAsyncCallTimeoutMs),
- isHalCurrentUser(currentId), any());
+ verify(mUserHal).switchUser(isSwitchUserRequest(0, currentId, targetId),
+ eq(mAsyncCallTimeoutMs), any());
}
private void assertNoHalUserCreation() {
verify(mUserHal, never()).createUser(any(), eq(mAsyncCallTimeoutMs), any());
}
- @NonNull
- private static android.hardware.automotive.vehicle.V2_0.UserInfo isHalUser(
- @UserIdInt int userId) {
- return argThat(new UserInfoMatcher(userId));
+ private void assertHalRemove(int currentId, int removeUserId) {
+ ArgumentCaptor<RemoveUserRequest> request =
+ ArgumentCaptor.forClass(RemoveUserRequest.class);
+ verify(mUserHal).removeUser(request.capture());
+ assertThat(request.getValue().removedUserInfo.userId).isEqualTo(removeUserId);
+ assertThat(request.getValue().usersInfo.currentUser.userId).isEqualTo(currentId);
}
@NonNull
- private static UsersInfo isHalCurrentUser(@UserIdInt int userId) {
- return argThat(new UsersInfoCurrentUserIdMatcher(userId));
+ private static SwitchUserRequest isSwitchUserRequest(int requestId,
+ @UserIdInt int currentUserId, @UserIdInt int targetUserId) {
+ return argThat(new SwitchUserRequestMatcher(requestId, currentUserId, targetUserId));
}
static final class FakeCarOccupantZoneService {
@@ -2034,53 +2084,46 @@
}
}
- private static final class UserInfoMatcher
- implements ArgumentMatcher<android.hardware.automotive.vehicle.V2_0.UserInfo> {
-
- private static final String MY_TAG =
- android.hardware.automotive.vehicle.V2_0.UserInfo.class.getSimpleName();
-
- private final @UserIdInt int mUserId;
-
- private UserInfoMatcher(@UserIdInt int userId) {
- mUserId = userId;
- }
-
- @Override
- public boolean matches(android.hardware.automotive.vehicle.V2_0.UserInfo argument) {
- if (argument == null) {
- Log.w(MY_TAG, "null argument");
- return false;
- }
- if (argument.userId != mUserId) {
- Log.w(MY_TAG, "wrong user id on " + argument + "; expected " + mUserId);
- return false;
- }
- Log.d(MY_TAG, "Good News, Everyone! " + argument + " matches " + this);
- return true;
- }
- }
-
- private static final class UsersInfoCurrentUserIdMatcher implements ArgumentMatcher<UsersInfo> {
-
+ private static final class SwitchUserRequestMatcher
+ implements ArgumentMatcher<SwitchUserRequest> {
private static final String MY_TAG = UsersInfo.class.getSimpleName();
- private final @UserIdInt int mUserId;
+ private final int mRequestId;
+ private final @UserIdInt int mCurrentUserId;
+ private final @UserIdInt int mTargetUserId;
- private UsersInfoCurrentUserIdMatcher(@UserIdInt int userId) {
- mUserId = userId;
+
+ private SwitchUserRequestMatcher(int requestId, @UserIdInt int currentUserId,
+ @UserIdInt int targetUserId) {
+ mCurrentUserId = currentUserId;
+ mTargetUserId = targetUserId;
+ mRequestId = requestId;
}
@Override
- public boolean matches(UsersInfo argument) {
+ public boolean matches(SwitchUserRequest argument) {
if (argument == null) {
Log.w(MY_TAG, "null argument");
return false;
}
- if (argument.currentUser.userId != mUserId) {
- Log.w(MY_TAG, "wrong user id on " + argument + "; expected " + mUserId);
+ if (argument.usersInfo.currentUser.userId != mCurrentUserId) {
+ Log.w(MY_TAG,
+ "wrong current user id on " + argument + "; expected " + mCurrentUserId);
return false;
}
+
+ if (argument.targetUser.userId != mTargetUserId) {
+ Log.w(MY_TAG,
+ "wrong target user id on " + argument + "; expected " + mTargetUserId);
+ return false;
+ }
+
+ if (argument.requestId != mRequestId) {
+ Log.w(MY_TAG,
+ "wrong request Id on " + argument + "; expected " + mTargetUserId);
+ return false;
+ }
+
Log.d(MY_TAG, "Good News, Everyone! " + argument + " matches " + this);
return true;
}
diff --git a/user/car-user-lib/Android.bp b/user/car-user-lib/Android.bp
index a9a5fc1..12bc950 100644
--- a/user/car-user-lib/Android.bp
+++ b/user/car-user-lib/Android.bp
@@ -18,6 +18,7 @@
"src/**/*.java",
],
static_libs: [
+ "android.car.settings",
"android.hardware.automotive.vehicle-V2.0-java",
],
product_variables: {
diff --git a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
index 4079e28..15a7d1a 100644
--- a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
@@ -17,10 +17,12 @@
package android.car.userlib;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.car.settings.CarSettings;
import android.content.Context;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
@@ -30,7 +32,6 @@
import android.sysprop.CarProperties;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.UserIcons;
import com.google.android.collect.Sets;
@@ -57,6 +58,7 @@
public final class CarUserManagerHelper {
private static final String TAG = "CarUserManagerHelper";
+ private static final boolean DEBUG = false;
private static final int BOOT_USER_NOT_FOUND = -1;
/**
@@ -97,14 +99,41 @@
* Sets the last active user.
*/
public void setLastActiveUser(@UserIdInt int userId) {
- Settings.Global.putInt(
- mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID, userId);
+ if (UserHelper.isHeadlessSystemUser(userId)) {
+ if (DEBUG) Log.d(TAG, "setLastActiveUser(): ignoring headless system user " + userId);
+ return;
+ }
+ setUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_USER_ID, userId);
+
+ // TODO(b/155918094): change method to receive a UserInfo instead
+ UserInfo user = mUserManager.getUserInfo(userId);
+ if (user == null) {
+ Log.w(TAG, "setLastActiveUser(): user " + userId + " doesn't exist");
+ return;
+ }
+ if (!user.isEphemeral()) {
+ setUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID, userId);
+ }
}
- private int getLastActiveUser() {
- return Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID,
- /* default user id= */ UserHandle.USER_SYSTEM);
+ private void setUserIdGlobalProperty(@NonNull String name, @UserIdInt int userId) {
+ if (DEBUG) Log.d(TAG, "setting global property " + name + " to " + userId);
+
+ Settings.Global.putInt(mContext.getContentResolver(), name, userId);
+ }
+
+ private int getUserIdGlobalProperty(@NonNull String name) {
+ int userId = Settings.Global.getInt(mContext.getContentResolver(), name,
+ UserHandle.USER_NULL);
+ if (DEBUG) Log.d(TAG, "getting global property " + name + ": " + userId);
+
+ return userId;
+ }
+
+ private void resetUserIdGlobalProperty(@NonNull String name) {
+ if (DEBUG) Log.d(TAG, "resetting global property " + name);
+
+ Settings.Global.putInt(mContext.getContentResolver(), name, UserHandle.USER_NULL);
}
/**
@@ -125,7 +154,6 @@
* @return user id of the initial user to boot into on the device, or
* {@link UserHandle#USER_NULL} if there is no user available.
*/
- @VisibleForTesting
int getInitialUser(boolean usesOverrideUserIdProperty) {
List<Integer> allUsers = userInfoListToUserIdList(getAllUsers());
@@ -148,18 +176,29 @@
}
// If the last active user is not the SYSTEM user and is a real user, return it
- int lastActiveUser = getLastActiveUser();
- if (lastActiveUser != UserHandle.USER_SYSTEM
- && allUsers.contains(lastActiveUser)) {
- Log.i(TAG, "Last active user loaded for initial user, user id: "
- + lastActiveUser);
+ int lastActiveUser = getUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_USER_ID);
+ if (allUsers.contains(lastActiveUser)) {
+ Log.i(TAG, "Last active user loaded for initial user: " + lastActiveUser);
return lastActiveUser;
}
+ resetUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_USER_ID);
+
+ int lastPersistentUser = getUserIdGlobalProperty(
+ CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+ if (allUsers.contains(lastPersistentUser)) {
+ Log.i(TAG, "Last active, persistent user loaded for initial user: "
+ + lastPersistentUser);
+ return lastPersistentUser;
+ }
+ resetUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
// If all else fails, return the smallest user id
int returnId = Collections.min(allUsers);
- Log.i(TAG, "Saved ids were invalid. Returning smallest user id, user id: "
- + returnId);
+ // TODO(b/158101909): the smallest user id is not always the initial user; a better approach
+ // would be looking for the first ADMIN user, or keep track of all last active users (not
+ // just the very last)
+ Log.w(TAG, "Last active user (" + lastActiveUser + ") not found. Returning smallest user id"
+ + " instead: " + returnId);
return returnId;
}
diff --git a/user/car-user-lib/src/android/car/userlib/UserHalHelper.java b/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
index e57b452..1d5cd67 100644
--- a/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
@@ -27,6 +27,8 @@
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue;
@@ -60,9 +62,12 @@
private static final boolean DEBUG = false;
public static final int INITIAL_USER_INFO_PROPERTY = 299896583;
+ public static final int SWITCH_USER_PROPERTY = 299896584;
public static final int CREATE_USER_PROPERTY = 299896585;
+ public static final int REMOVE_USER_PROPERTY = 299896586;
public static final int USER_IDENTIFICATION_ASSOCIATION_PROPERTY = 299896587;
+
private static final String STRING_SEPARATOR = "\\|\\|";
/**
@@ -515,6 +520,48 @@
}
/**
+ * Creates a generic {@link VehiclePropValue} (that can be sent to HAL) from a
+ * {@link SwitchUserRequest}.
+ *
+ * @throws IllegalArgumentException if the request doesn't have the proper format.
+ */
+ @NonNull
+ public static VehiclePropValue toVehiclePropValue(@NonNull SwitchUserRequest request) {
+ Objects.requireNonNull(request, "request cannot be null");
+ checkArgument(request.messageType > 0, "invalid messageType on %s", request);
+ android.hardware.automotive.vehicle.V2_0.UserInfo targetInfo = request.targetUser;
+ UsersInfo usersInfo = request.usersInfo;
+ Objects.requireNonNull(targetInfo);
+ checkValid(usersInfo);
+
+ VehiclePropValue propValue = createPropRequest(SWITCH_USER_PROPERTY, request.requestId,
+ request.messageType);
+ addUserInfo(propValue, targetInfo);
+ addUsersInfo(propValue, usersInfo);
+ return propValue;
+ }
+
+ /**
+ * Creates a generic {@link VehiclePropValue} (that can be sent to HAL) from a
+ * {@link RemoveUserRequest}.
+ *
+ * @throws IllegalArgumentException if the request doesn't have the proper format.
+ */
+ @NonNull
+ public static VehiclePropValue toVehiclePropValue(@NonNull RemoveUserRequest request) {
+ checkArgument(request.requestId > 0, "invalid requestId on %s", request);
+ android.hardware.automotive.vehicle.V2_0.UserInfo removedUserInfo = request.removedUserInfo;
+ Objects.requireNonNull(removedUserInfo);
+ UsersInfo usersInfo = request.usersInfo;
+ checkValid(usersInfo);
+
+ VehiclePropValue propValue = createPropRequest(REMOVE_USER_PROPERTY, request.requestId);
+ addUserInfo(propValue, removedUserInfo);
+ addUsersInfo(propValue, usersInfo);
+ return propValue;
+ }
+
+ /**
* Creates a {@link UsersInfo} instance populated with the current users.
*/
@NonNull