Merge "Computepipe runner engine" into rvc-dev
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..6239f78
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "auto-postsubmit": [
+ {
+ "name": "AndroidCarApiTest",
+ "options": [
+ {
+ "include-filter": "android.car.apitest.PreInstalledPackagesTest"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
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 60a174d..c919397 100644
--- a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
+++ b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
@@ -34,9 +34,9 @@
150008 car_helper_svc_connected (pending_operations|1)
150009 car_helper_hal_request (request_type|1)
150010 car_helper_hal_response (result_code|1)
-150011 car_helper_hal_default_behavior (fallback|1)
-150012 car_helper_hal_start_user (user_id|1)
-150013 car_helper_hal_create_user (flags|1), (safe_name|3)
+150011 car_helper_hal_default_behavior (fallback|1),(user_locales|3)
+150012 car_helper_hal_start_user (user_id|1),(user_locales|3)
+150013 car_helper_hal_create_user (flags|1),(safe_name|3),(user_locales|3)
150014 car_helper_pre_creation_requested (number_users|1),(number_guests|1)
150015 car_helper_pre_creation_status (number_existing_users|1),(number_users_to_add|1),(number_users_to_remove|1),(number_existing_guests|1),(number_guests_to_add|1),(number_guests_to_remove|1),(number_invalid_users_to_remove|1)
@@ -65,7 +65,7 @@
#### User-related tags (range 150100 - 150199)
150100 car_user_svc_initial_user_info_req (request_type|1),(timeout|1)
-150101 car_user_svc_initial_user_info_resp (status|1),(action|1),(user_id|1),(flags|1),(safe_name|3)
+150101 car_user_svc_initial_user_info_resp (status|1),(action|1),(user_id|1),(flags|1),(safe_name|3),(user_locales|3)
150103 car_user_svc_set_initial_user (user_id|1)
150104 car_user_svc_set_lifecycle_listener (uid|1)
150105 car_user_svc_reset_lifecycle_listener (uid|1)
@@ -75,9 +75,12 @@
150109 car_user_svc_get_user_auth_req (uid|1),(user_id|1),(number_types|1)
150110 car_user_svc_get_user_auth_resp (number_values|1)
150111 car_user_svc_switch_user_ui_req (user_id|1)
+150112 car_user_svc_switch_user_from_hal_req (request_id|1),(uid|1)
+150113 car_user_svc_set_user_auth_req (uid|1),(user_id|1),(number_associations|1)
+150114 car_user_svc_set_user_auth_resp (number_values|1),(error_message|3)
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)
+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)
150142 car_user_hal_switch_user_req (request_id|1),(user_id|1),(timeout|1)
150143 car_user_hal_switch_user_resp (request_id|1),(status|1),(result|1),(error_message|3)
150144 car_user_hal_post_switch_user_req (request_id|1),(target_user_id|1),(current_user_id|1)
@@ -86,6 +89,7 @@
150147 car_user_hal_legacy_switch_user_req (request_id|1),(target_user_id|1),(current_user_id|1)
150148 car_user_hal_set_user_auth_req (int32values|4)
150149 car_user_hal_set_user_auth_resp (int32values|4),(error_message|3)
+150150 car_user_hal_oem_switch_user_req (request_id|1),(target_user_id|1)
150171 car_user_mgr_add_listener (uid|1)
150172 car_user_mgr_remove_listener (uid|1)
@@ -94,3 +98,5 @@
150175 car_user_mgr_switch_user_response (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)
diff --git a/car-lib/src/android/car/CarBugreportManager.java b/car-lib/src/android/car/CarBugreportManager.java
index 68c11d3..1d7f862 100644
--- a/car-lib/src/android/car/CarBugreportManager.java
+++ b/car-lib/src/android/car/CarBugreportManager.java
@@ -161,16 +161,16 @@
* bugreport and makes them available through a extra output file. Currently the extra
* output contains the screenshots for all the physical displays.
*
- * <p>The file descriptor is closed when bugreport is written or if an exception happens.
+ * <p>It closes provided file descriptors. The callback runs on a background thread.
*
* <p>This method is enabled only for one bug reporting app. It can be configured using
* {@code config_car_bugreport_application} string that is defined in
* {@code packages/services/Car/service/res/values/config.xml}. To learn more please
* see {@code packages/services/Car/tests/BugReportApp/README.md}.
*
- * @param output the zipped bugreport file
+ * @param output the zipped bugreport file.
* @param extraOutput a zip file that contains extra files generated for automotive.
- * @param callback the callback for reporting dump status
+ * @param callback the callback for reporting dump status.
*/
@RequiresPermission(android.Manifest.permission.DUMP)
public void requestBugreport(
@@ -187,11 +187,27 @@
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
} finally {
+ // Safely close the FDs on this side, because binder dups them.
IoUtils.closeQuietly(output);
IoUtils.closeQuietly(extraOutput);
}
}
+ /**
+ * Cancels the running bugreport. It doesn't guarantee immediate cancellation and after
+ * calling this method, callbacks provided in {@link #requestBugreport} might still get fired.
+ * The next {@link startBugreport} should be called after a delay to allow the system to fully
+ * complete the cancellation.
+ */
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void cancelBugreport() {
+ try {
+ mService.cancelBugreport();
+ } catch (RemoteException e) {
+ handleRemoteExceptionFromCarService(e);
+ }
+ }
+
@Override
public void onCarDisconnected() {
}
diff --git a/car-lib/src/android/car/ICarBugreportService.aidl b/car-lib/src/android/car/ICarBugreportService.aidl
index 2673c80..54ae4eb 100644
--- a/car-lib/src/android/car/ICarBugreportService.aidl
+++ b/car-lib/src/android/car/ICarBugreportService.aidl
@@ -23,7 +23,7 @@
*
* @hide
*/
- interface ICarBugreportService {
+interface ICarBugreportService {
/**
* Starts bugreport service to capture a zipped bugreport. The caller needs to provide
@@ -34,4 +34,9 @@
*/
void requestBugreport(in ParcelFileDescriptor output,
in ParcelFileDescriptor extraOutput, ICarBugreportCallback callback) = 1;
- }
+
+ /**
+ * Cancels the running bugreport.
+ */
+ void cancelBugreport() = 2;
+}
diff --git a/car-lib/src/android/car/ICarUserService.aidl b/car-lib/src/android/car/ICarUserService.aidl
index 2eccdbb..921c146 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -17,7 +17,7 @@
package android.car;
import android.content.pm.UserInfo;
-import android.car.user.GetUserIdentificationAssociationResponse;
+import android.car.user.UserIdentificationAssociationResponse;
import android.car.user.UserSwitchResult;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.os.IResultReceiver;
@@ -26,16 +26,17 @@
interface ICarUserService {
UserInfo createDriver(in String name, boolean admin);
UserInfo createPassenger(in String name, int driverId);
- boolean switchDriver(int driverId);
- oneway void switchUser(int tagerUserId, int timeoutMs,
- in AndroidFuture<UserSwitchResult> result);
+ void switchDriver(int driverId, in AndroidFuture<UserSwitchResult> receiver);
+ void switchUser(int tagerUserId, int timeoutMs, in AndroidFuture<UserSwitchResult> receiver);
List<UserInfo> getAllDrivers();
List<UserInfo> getPassengers(int driverId);
boolean startPassenger(int passengerId, int zoneId);
boolean stopPassenger(int passengerId);
- oneway void setLifecycleListenerForUid(in IResultReceiver listener);
- oneway void resetLifecycleListenerForUid();
- oneway void getInitialUserInfo(int requestType, int timeoutMs, in IResultReceiver receiver);
- GetUserIdentificationAssociationResponse getUserIdentificationAssociation(in int[] types);
- oneway void setUserSwitchUiCallback(in IResultReceiver callback);
+ void setLifecycleListenerForUid(in IResultReceiver listener);
+ void resetLifecycleListenerForUid();
+ void getInitialUserInfo(int requestType, int timeoutMs, in IResultReceiver receiver);
+ UserIdentificationAssociationResponse getUserIdentificationAssociation(in int[] types);
+ void setUserIdentificationAssociation(int timeoutMs, in int[] types, in int[] values,
+ in AndroidFuture<UserIdentificationAssociationResponse> result);
+ void setUserSwitchUiCallback(in IResultReceiver callback);
}
diff --git a/car-lib/src/android/car/VehicleAreaSeat.java b/car-lib/src/android/car/VehicleAreaSeat.java
index 792ca27..611827d 100644
--- a/car-lib/src/android/car/VehicleAreaSeat.java
+++ b/car-lib/src/android/car/VehicleAreaSeat.java
@@ -16,16 +16,26 @@
package android.car;
import android.annotation.IntDef;
-import android.car.hardware.CarPropertyValue;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * VehicleAreaSeat is an abstraction for a seat in a car. Some car APIs like
- * {@link CarPropertyValue} may provide control per seat and
- * values defined here should be used to distinguish different seats.
+ * Object used to indicate the area value for car properties which have area type
+ * {@link VehicleAreaType#VEHICLE_AREA_TYPE_SEAT}.
+ * <p>
+ * The constants defined by {@link VehicleAreaSeat} indicate the position for area type
+ * {@link VehicleAreaType#VEHICLE_AREA_TYPE_SEAT}. A property can have a single or a combination of
+ * positions. Developers can query the position using
+ * {@link android.car.hardware.property.CarPropertyManager#getAreaId(int, int)}.
+ * </p><p>
+ * Refer to {@link android.car.hardware.CarPropertyConfig#getAreaIds()} for more information about
+ * areaId.
+ * </p>
*/
+
+// This class is only designed to provide constants for VehicleAreaSeat. The constants should
+// be same as VehicleAreaSeat in /hardware/interfaces/automotive/vehicle/2.0/types.hal.
public final class VehicleAreaSeat {
/** List of vehicle's seats. */
public static final int SEAT_UNKNOWN = 0;
diff --git a/car-lib/src/android/car/VehicleAreaType.java b/car-lib/src/android/car/VehicleAreaType.java
index 826ffe3..5cd5c7a 100644
--- a/car-lib/src/android/car/VehicleAreaType.java
+++ b/car-lib/src/android/car/VehicleAreaType.java
@@ -16,15 +16,24 @@
package android.car;
import android.annotation.IntDef;
+import android.car.hardware.CarPropertyConfig;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * Represents vehicle area such as window, door, seat, etc.
- * See also {@link VehicleAreaDoor}, {@link VehicleAreaSeat},
- * {@link VehicleAreaWindow},
+ * Object used to indicate area types for car properties.
+ * <p>
+ * The constants defined by {@link VehicleAreaType} indicate the area types for properties. A
+ * property only has one area type. Developers can query the area type using
+ * {@link CarPropertyConfig#getPropertyType()}.
+ * </p><p>
+ * Refer to {@link VehicleAreaSeat} and {@link VehicleAreaWheel} for more information about areaId.
+ * </p>
*/
+
+// This class is only designed to provide constants for VehicleAreaType. The constants should
+// exactly be same as VehicleAreaType in /hardware/interfaces/automotive/vehicle/2.0/types.hal.
public final class VehicleAreaType {
/** Used for global properties */
public static final int VEHICLE_AREA_TYPE_GLOBAL = 0;
diff --git a/car-lib/src/android/car/VehicleOilLevel.java b/car-lib/src/android/car/VehicleOilLevel.java
index 5a21a2d..75fd1da 100644
--- a/car-lib/src/android/car/VehicleOilLevel.java
+++ b/car-lib/src/android/car/VehicleOilLevel.java
@@ -16,7 +16,7 @@
package android.car;
import android.annotation.IntDef;
-import android.annotation.SystemApi;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java b/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
index be8e8c4..1167810 100644
--- a/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
+++ b/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
@@ -41,6 +41,7 @@
*
* @hide
*/
+ @Deprecated
@SystemApi
public static final String CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION";
@@ -53,6 +54,7 @@
*
* @hide
*/
+ @Deprecated
@SystemApi
public static final String KEY_EXTRA_ACTIVITY_STATE =
"android.car.cluster.ClusterActivityState";
@@ -64,6 +66,7 @@
*
* @hide
*/
+ @Deprecated
@SystemApi
public void startActivity(Intent intent) {
// No-op
@@ -81,6 +84,7 @@
*
* @hide
*/
+ @Deprecated
@SystemApi
public void registerCallback(String category, Callback callback) {
// No-op
@@ -95,6 +99,7 @@
*
* @hide
*/
+ @Deprecated
@SystemApi
public void unregisterCallback(Callback callback) {
// No-op
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
index a66258c..9687efb 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
@@ -38,15 +38,21 @@
@Nullable private NavigationRenderer mNavigationRenderer;
/**
- * Calls once when instrument cluster should be created.
+ * Called when instrument cluster renderer is created.
*/
- abstract public void onCreate(Context context);
+ public abstract void onCreate(Context context);
- abstract public void onStart();
+ /**
+ * Called when instrument cluster renderer is started.
+ */
+ public abstract void onStart();
- abstract public void onStop();
+ /**
+ * Called when instrument cluster renderer is stopped.
+ */
+ public abstract void onStop();
- abstract protected NavigationRenderer createNavigationRenderer();
+ protected abstract NavigationRenderer createNavigationRenderer();
/** The method is thread-safe, callers should cache returned object. */
@Nullable
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index cfb4e44..5272548 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -52,6 +52,7 @@
import com.android.internal.annotations.GuardedBy;
import java.io.FileDescriptor;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
@@ -524,7 +525,6 @@
@SuppressWarnings("deprecation")
public void onNavigationStateChanged(@Nullable Bundle bundle) throws RemoteException {
assertClusterManagerPermission();
- assertContextOwnership();
mUiHandler.post(() -> {
if (mNavigationRenderer != null) {
mNavigationRenderer.onNavigationStateChanged(bundle);
@@ -537,18 +537,6 @@
assertClusterManagerPermission();
return runAndWaitResult(() -> mNavigationRenderer.getNavigationProperties());
}
-
- private void assertContextOwnership() {
- int uid = getCallingUid();
- int pid = getCallingPid();
-
- synchronized (mLock) {
- if (mNavContextOwner.mUid != uid || mNavContextOwner.mPid != pid) {
- throw new IllegalStateException("Client {uid:" + uid + ", pid: " + pid + "} is"
- + " not an owner of APP_FOCUS_TYPE_NAVIGATION " + mNavContextOwner);
- }
- }
- }
}
private void assertClusterManagerPermission() {
@@ -599,8 +587,8 @@
if (uri.getQueryParameter(BITMAP_QUERY_WIDTH).isEmpty() || uri.getQueryParameter(
BITMAP_QUERY_HEIGHT).isEmpty()) {
throw new IllegalArgumentException(
- "Uri must have '" + BITMAP_QUERY_WIDTH + "' and '" + BITMAP_QUERY_HEIGHT
- + "' query parameters");
+ "Uri must have '" + BITMAP_QUERY_WIDTH + "' and '" + BITMAP_QUERY_HEIGHT
+ + "' query parameters");
}
ContextOwner contextOwner = getNavigationContextOwner();
@@ -610,7 +598,6 @@
}
String host = uri.getHost();
-
if (!contextOwner.mAuthorities.contains(host)) {
Log.e(TAG, "Uri points to an authority not handled by the current context owner: "
+ uri + " (valid authorities: " + contextOwner.mAuthorities + ")");
@@ -626,19 +613,19 @@
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Requesting bitmap: " + uri);
}
- ParcelFileDescriptor fileDesc = getContentResolver()
- .openFileDescriptor(filteredUid, "r");
- if (fileDesc != null) {
- Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDesc.getFileDescriptor());
- fileDesc.close();
- return bitmap;
- } else {
- Log.e(TAG, "Failed to create pipe for uri string: " + uri);
+ try (ParcelFileDescriptor fileDesc = getContentResolver()
+ .openFileDescriptor(filteredUid, "r")) {
+ if (fileDesc != null) {
+ Bitmap bitmap = BitmapFactory.decodeFileDescriptor(
+ fileDesc.getFileDescriptor());
+ return bitmap;
+ } else {
+ Log.e(TAG, "Failed to create pipe for uri string: " + uri);
+ }
}
- } catch (Throwable e) {
+ } catch (IOException e) {
Log.e(TAG, "Unable to fetch uri: " + uri, e);
}
-
return null;
}
@@ -708,27 +695,24 @@
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Requesting bitmap: " + uri);
}
- ParcelFileDescriptor fileDesc = getContentResolver()
- .openFileDescriptor(filteredUid, "r");
- if (fileDesc != null) {
- bitmap = BitmapFactory.decodeFileDescriptor(fileDesc.getFileDescriptor());
- fileDesc.close();
- return bitmap;
- } else {
- Log.e(TAG, "Failed to create pipe for uri string: " + uri);
+ try (ParcelFileDescriptor fileDesc = getContentResolver()
+ .openFileDescriptor(filteredUid, "r")) {
+ if (fileDesc != null) {
+ bitmap = BitmapFactory.decodeFileDescriptor(fileDesc.getFileDescriptor());
+ return bitmap;
+ } else {
+ Log.e(TAG, "Failed to create pipe for uri string: " + uri);
+ }
}
-
if (bitmap.getWidth() != width || bitmap.getHeight() != height) {
bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
}
mCache.put(uri.toString(), bitmap);
}
-
return bitmap;
- } catch (Throwable e) {
+ } catch (IOException e) {
Log.e(TAG, "Unable to fetch uri: " + uri, e);
}
-
return null;
}
}
diff --git a/car-lib/src/android/car/content/pm/CarPackageManager.java b/car-lib/src/android/car/content/pm/CarPackageManager.java
index 5d5936b..98c5822 100644
--- a/car-lib/src/android/car/content/pm/CarPackageManager.java
+++ b/car-lib/src/android/car/content/pm/CarPackageManager.java
@@ -108,8 +108,8 @@
@SystemApi
public void setAppBlockingPolicy(
String packageName, CarAppBlockingPolicy policy, @SetPolicyFlags int flags) {
- if ((flags & FLAG_SET_POLICY_WAIT_FOR_CHANGE) != 0 &&
- Looper.getMainLooper().isCurrentThread()) {
+ if ((flags & FLAG_SET_POLICY_WAIT_FOR_CHANGE) != 0
+ && Looper.getMainLooper().isCurrentThread()) {
throw new IllegalStateException(
"FLAG_SET_POLICY_WAIT_FOR_CHANGE cannot be used in main thread");
}
diff --git a/car-lib/src/android/car/diagnostic/CarDiagnosticEvent.java b/car-lib/src/android/car/diagnostic/CarDiagnosticEvent.java
index 94abbb5..901d14e 100644
--- a/car-lib/src/android/car/diagnostic/CarDiagnosticEvent.java
+++ b/car-lib/src/android/car/diagnostic/CarDiagnosticEvent.java
@@ -50,13 +50,13 @@
* Sparse array that contains the mapping of OBD2 diagnostic properties to their values for
* integer valued properties
*/
- private final SparseIntArray intValues;
+ private final SparseIntArray mIntValues;
/**
* Sparse array that contains the mapping of OBD2 diagnostic properties to their values for
* float valued properties
*/
- private final SparseArray<Float> floatValues;
+ private final SparseArray<Float> mFloatValues;
/**
* Diagnostic Troubleshooting Code (DTC) that was detected and caused this frame to be stored
@@ -68,18 +68,18 @@
frameType = in.readInt();
timestamp = in.readLong();
int len = in.readInt();
- floatValues = new SparseArray<>(len);
+ mFloatValues = new SparseArray<>(len);
for (int i = 0; i < len; ++i) {
int key = in.readInt();
float value = in.readFloat();
- floatValues.put(key, value);
+ mFloatValues.put(key, value);
}
len = in.readInt();
- intValues = new SparseIntArray(len);
+ mIntValues = new SparseIntArray(len);
for (int i = 0; i < len; ++i) {
int key = in.readInt();
int value = in.readInt();
- intValues.put(key, value);
+ mIntValues.put(key, value);
}
dtc = (String) in.readValue(String.class.getClassLoader());
// version 1 up to here
@@ -94,17 +94,17 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(frameType);
dest.writeLong(timestamp);
- dest.writeInt(floatValues.size());
- for (int i = 0; i < floatValues.size(); ++i) {
- int key = floatValues.keyAt(i);
+ dest.writeInt(mFloatValues.size());
+ for (int i = 0; i < mFloatValues.size(); ++i) {
+ int key = mFloatValues.keyAt(i);
dest.writeInt(key);
- dest.writeFloat(floatValues.get(key));
+ dest.writeFloat(mFloatValues.get(key));
}
- dest.writeInt(intValues.size());
- for (int i = 0; i < intValues.size(); ++i) {
- int key = intValues.keyAt(i);
+ dest.writeInt(mIntValues.size());
+ for (int i = 0; i < mIntValues.size(); ++i) {
+ int key = mIntValues.keyAt(i);
dest.writeInt(key);
- dest.writeInt(intValues.get(key));
+ dest.writeInt(mIntValues.get(key));
}
dest.writeValue(dtc);
}
@@ -141,19 +141,19 @@
jsonWriter.name("timestamp").value(timestamp);
jsonWriter.name("intValues").beginArray();
- for (int i = 0; i < intValues.size(); ++i) {
+ for (int i = 0; i < mIntValues.size(); ++i) {
jsonWriter.beginObject();
- jsonWriter.name("id").value(intValues.keyAt(i));
- jsonWriter.name("value").value(intValues.valueAt(i));
+ jsonWriter.name("id").value(mIntValues.keyAt(i));
+ jsonWriter.name("value").value(mIntValues.valueAt(i));
jsonWriter.endObject();
}
jsonWriter.endArray();
jsonWriter.name("floatValues").beginArray();
- for (int i = 0; i < floatValues.size(); ++i) {
+ for (int i = 0; i < mFloatValues.size(); ++i) {
jsonWriter.beginObject();
- jsonWriter.name("id").value(floatValues.keyAt(i));
- jsonWriter.name("value").value(floatValues.valueAt(i));
+ jsonWriter.name("id").value(mFloatValues.keyAt(i));
+ jsonWriter.name("value").value(mFloatValues.valueAt(i));
jsonWriter.endObject();
}
jsonWriter.endArray();
@@ -184,8 +184,8 @@
String dtc) {
this.frameType = frameType;
this.timestamp = timestamp;
- this.floatValues = floatValues;
- this.intValues = intValues;
+ mFloatValues = floatValues;
+ mIntValues = intValues;
this.dtc = dtc;
}
@@ -308,16 +308,16 @@
* @hide
*/
public CarDiagnosticEvent withVendorSensorsRemoved() {
- SparseIntArray newIntValues = intValues.clone();
- SparseArray<Float> newFloatValues = floatValues.clone();
- for (int i = 0; i < intValues.size(); ++i) {
- int key = intValues.keyAt(i);
+ SparseIntArray newIntValues = mIntValues.clone();
+ SparseArray<Float> newFloatValues = mFloatValues.clone();
+ for (int i = 0; i < mIntValues.size(); ++i) {
+ int key = mIntValues.keyAt(i);
if (key >= android.car.diagnostic.IntegerSensorIndex.LAST_SYSTEM) {
newIntValues.delete(key);
}
}
- for (int i = 0; i < floatValues.size(); ++i) {
- int key = floatValues.keyAt(i);
+ for (int i = 0; i < mFloatValues.size(); ++i) {
+ int key = mFloatValues.keyAt(i);
if (key >= android.car.diagnostic.FloatSensorIndex.LAST_SYSTEM) {
newFloatValues.delete(key);
}
@@ -337,8 +337,8 @@
/** @hide */
public boolean isEmptyFrame() {
- boolean empty = (0 == intValues.size());
- empty &= (0 == floatValues.size());
+ boolean empty = (0 == mIntValues.size());
+ empty &= (0 == mFloatValues.size());
if (isFreezeFrame()) empty &= dtc.isEmpty();
return empty;
}
@@ -372,37 +372,42 @@
if (!(otherObject instanceof CarDiagnosticEvent)) {
return false;
}
- CarDiagnosticEvent otherEvent = (CarDiagnosticEvent)otherObject;
- if (otherEvent.frameType != frameType)
+ CarDiagnosticEvent otherEvent = (CarDiagnosticEvent) otherObject;
+ if (otherEvent.frameType != frameType) {
return false;
- if (otherEvent.timestamp != timestamp)
+ }
+ if (otherEvent.timestamp != timestamp) {
return false;
- if (otherEvent.intValues.size() != intValues.size())
+ }
+ if (otherEvent.mIntValues.size() != mIntValues.size()) {
return false;
- if (otherEvent.floatValues.size() != floatValues.size())
+ }
+ if (otherEvent.mFloatValues.size() != mFloatValues.size()) {
return false;
- if (!Objects.equals(dtc, otherEvent.dtc))
+ }
+ if (!Objects.equals(dtc, otherEvent.dtc)) {
return false;
- for (int i = 0; i < intValues.size(); ++i) {
- int key = intValues.keyAt(i);
- int otherKey = otherEvent.intValues.keyAt(i);
+ }
+ for (int i = 0; i < mIntValues.size(); ++i) {
+ int key = mIntValues.keyAt(i);
+ int otherKey = otherEvent.mIntValues.keyAt(i);
if (key != otherKey) {
return false;
}
- int value = intValues.valueAt(i);
- int otherValue = otherEvent.intValues.valueAt(i);
+ int value = mIntValues.valueAt(i);
+ int otherValue = otherEvent.mIntValues.valueAt(i);
if (value != otherValue) {
return false;
}
}
- for (int i = 0; i < floatValues.size(); ++i) {
- int key = floatValues.keyAt(i);
- int otherKey = otherEvent.floatValues.keyAt(i);
+ for (int i = 0; i < mFloatValues.size(); ++i) {
+ int key = mFloatValues.keyAt(i);
+ int otherKey = otherEvent.mFloatValues.keyAt(i);
if (key != otherKey) {
return false;
}
- float value = floatValues.valueAt(i);
- float otherValue = otherEvent.floatValues.valueAt(i);
+ float value = mFloatValues.valueAt(i);
+ float otherValue = otherEvent.mFloatValues.valueAt(i);
if (value != otherValue) {
return false;
}
@@ -412,22 +417,22 @@
@Override
public int hashCode() {
- Integer[] intKeys = new Integer[intValues.size()];
- Integer[] floatKeys = new Integer[floatValues.size()];
+ Integer[] intKeys = new Integer[mIntValues.size()];
+ Integer[] floatKeys = new Integer[mFloatValues.size()];
Integer[] intValues = new Integer[intKeys.length];
Float[] floatValues = new Float[floatKeys.length];
for (int i = 0; i < intKeys.length; ++i) {
- intKeys[i] = this.intValues.keyAt(i);
- intValues[i] = this.intValues.valueAt(i);
+ intKeys[i] = mIntValues.keyAt(i);
+ intValues[i] = mIntValues.valueAt(i);
}
for (int i = 0; i < floatKeys.length; ++i) {
- floatKeys[i] = this.floatValues.keyAt(i);
- floatValues[i] = this.floatValues.valueAt(i);
+ floatKeys[i] = mFloatValues.keyAt(i);
+ floatValues[i] = mFloatValues.valueAt(i);
}
- int intKeysHash = Objects.hash((Object[])intKeys);
- int intValuesHash = Objects.hash((Object[])intValues);
- int floatKeysHash = Objects.hash((Object[])floatKeys);
- int floatValuesHash = Objects.hash((Object[])floatValues);
+ int intKeysHash = Objects.hash((Object[]) intKeys);
+ int intValuesHash = Objects.hash((Object[]) intValues);
+ int floatKeysHash = Objects.hash((Object[]) floatKeys);
+ int floatValuesHash = Objects.hash((Object[]) floatValues);
return Objects.hash(frameType,
timestamp,
dtc,
@@ -443,13 +448,13 @@
"%s diagnostic frame {\n"
+ "\ttimestamp: %d, "
+ "DTC: %s\n"
- + "\tintValues: %s\n"
- + "\tfloatValues: %s\n}",
+ + "\tmIntValues: %s\n"
+ + "\tmFloatValues: %s\n}",
isLiveFrame() ? "live" : "freeze",
timestamp,
dtc,
- intValues.toString(),
- floatValues.toString());
+ mIntValues.toString(),
+ mFloatValues.toString());
}
/**
@@ -458,7 +463,7 @@
*/
public int getSystemIntegerSensor(
@android.car.diagnostic.IntegerSensorIndex.SensorIndex int sensor, int defaultValue) {
- return intValues.get(sensor, defaultValue);
+ return mIntValues.get(sensor, defaultValue);
}
/**
@@ -467,7 +472,7 @@
*/
public float getSystemFloatSensor(
@android.car.diagnostic.FloatSensorIndex.SensorIndex int sensor, float defaultValue) {
- return floatValues.get(sensor, defaultValue);
+ return mFloatValues.get(sensor, defaultValue);
}
/**
@@ -475,7 +480,7 @@
* Returns defaultValue otherwise.
*/
public int getVendorIntegerSensor(int sensor, int defaultValue) {
- return intValues.get(sensor, defaultValue);
+ return mIntValues.get(sensor, defaultValue);
}
/**
@@ -483,7 +488,7 @@
* Returns defaultValue otherwise.
*/
public float getVendorFloatSensor(int sensor, float defaultValue) {
- return floatValues.get(sensor, defaultValue);
+ return mFloatValues.get(sensor, defaultValue);
}
/**
@@ -492,9 +497,9 @@
*/
public @Nullable Integer getSystemIntegerSensor(
@android.car.diagnostic.IntegerSensorIndex.SensorIndex int sensor) {
- int index = intValues.indexOfKey(sensor);
+ int index = mIntValues.indexOfKey(sensor);
if (index < 0) return null;
- return intValues.valueAt(index);
+ return mIntValues.valueAt(index);
}
/**
@@ -503,9 +508,9 @@
*/
public @Nullable Float getSystemFloatSensor(
@android.car.diagnostic.FloatSensorIndex.SensorIndex int sensor) {
- int index = floatValues.indexOfKey(sensor);
+ int index = mFloatValues.indexOfKey(sensor);
if (index < 0) return null;
- return floatValues.valueAt(index);
+ return mFloatValues.valueAt(index);
}
/**
@@ -513,9 +518,9 @@
* Returns null otherwise.
*/
public @Nullable Integer getVendorIntegerSensor(int sensor) {
- int index = intValues.indexOfKey(sensor);
+ int index = mIntValues.indexOfKey(sensor);
if (index < 0) return null;
- return intValues.valueAt(index);
+ return mIntValues.valueAt(index);
}
/**
@@ -523,9 +528,9 @@
* Returns null otherwise.
*/
public @Nullable Float getVendorFloatSensor(int sensor) {
- int index = floatValues.indexOfKey(sensor);
+ int index = mFloatValues.indexOfKey(sensor);
if (index < 0) return null;
- return floatValues.valueAt(index);
+ return mFloatValues.valueAt(index);
}
/**
@@ -658,6 +663,9 @@
mIncompleteBitmask = incompleteBitmask;
}
+ /**
+ * Returns the {@link IgnitionMonitor} associated with the value passed as parameter.
+ */
public IgnitionMonitor fromValue(int value) {
boolean available = (0 != (value & mAvailableBitmask));
boolean incomplete = (0 != (value & mIncompleteBitmask));
@@ -721,8 +729,9 @@
* Returns null otherwise.
*/
public @Nullable CompressionIgnitionMonitors asCompressionIgnitionMonitors() {
- if (this instanceof CompressionIgnitionMonitors)
+ if (this instanceof CompressionIgnitionMonitors) {
return (CompressionIgnitionMonitors) this;
+ }
return null;
}
}
@@ -907,7 +916,8 @@
* Returns null otherwise.
*/
public @Nullable @SecondaryAirStatus.Status Integer getSecondaryAirStatus() {
- return getSystemIntegerSensor(android.car.diagnostic.IntegerSensorIndex.COMMANDED_SECONDARY_AIR_STATUS);
+ return getSystemIntegerSensor(
+ android.car.diagnostic.IntegerSensorIndex.COMMANDED_SECONDARY_AIR_STATUS);
}
/**
@@ -916,9 +926,11 @@
*/
public @Nullable CommonIgnitionMonitors getIgnitionMonitors() {
Integer ignitionMonitorsType =
- getSystemIntegerSensor(android.car.diagnostic.IntegerSensorIndex.IGNITION_MONITORS_SUPPORTED);
+ getSystemIntegerSensor(
+ android.car.diagnostic.IntegerSensorIndex.IGNITION_MONITORS_SUPPORTED);
Integer ignitionMonitorsBitmask =
- getSystemIntegerSensor(android.car.diagnostic.IntegerSensorIndex.IGNITION_SPECIFIC_MONITORS);
+ getSystemIntegerSensor(
+ android.car.diagnostic.IntegerSensorIndex.IGNITION_SPECIFIC_MONITORS);
if (null == ignitionMonitorsType) return null;
if (null == ignitionMonitorsBitmask) return null;
switch (ignitionMonitorsType) {
diff --git a/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java b/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
index 8c2f8e3..9799707 100644
--- a/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
+++ b/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
@@ -54,7 +54,7 @@
public @interface FrameType {}
/** @hide */
- public static final @FrameType int FRAME_TYPES[] = {
+ public static final @FrameType int[] FRAME_TYPES = {
FRAME_TYPE_LIVE,
FRAME_TYPE_FREEZE
};
@@ -95,7 +95,7 @@
@Override
public void onCarDisconnected() {
- synchronized(mActiveListeners) {
+ synchronized (mActiveListeners) {
mActiveListeners.clear();
}
}
@@ -107,7 +107,7 @@
*
* @param carDiagnosticEvent
*/
- void onDiagnosticEvent(final CarDiagnosticEvent carDiagnosticEvent);
+ void onDiagnosticEvent(CarDiagnosticEvent carDiagnosticEvent);
}
// OnDiagnosticEventListener registration
@@ -134,7 +134,7 @@
public boolean registerListener(
OnDiagnosticEventListener listener, @FrameType int frameType, int rate) {
assertFrameType(frameType);
- synchronized(mActiveListeners) {
+ synchronized (mActiveListeners) {
boolean needsServerUpdate = false;
CarDiagnosticListeners listeners = mActiveListeners.get(frameType);
if (listeners == null) {
@@ -159,8 +159,8 @@
* @param listener
*/
public void unregisterListener(OnDiagnosticEventListener listener) {
- synchronized(mActiveListeners) {
- for(@FrameType int frameType : FRAME_TYPES) {
+ synchronized (mActiveListeners) {
+ for (@FrameType int frameType : FRAME_TYPES) {
doUnregisterListenerLocked(listener, frameType);
}
}
@@ -177,7 +177,7 @@
if (listeners.isEmpty()) {
try {
mService.unregisterDiagnosticListener(frameType,
- mListenerToService);
+ mListenerToService);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
// continue for local clean-up
@@ -340,12 +340,12 @@
extends Stub {
private final WeakReference<CarDiagnosticManager> mManager;
- public CarDiagnosticEventListenerToService(CarDiagnosticManager manager) {
+ CarDiagnosticEventListenerToService(CarDiagnosticManager manager) {
mManager = new WeakReference<>(manager);
}
private void handleOnDiagnosticEvents(CarDiagnosticManager manager,
- List<CarDiagnosticEvent> events) {
+ List<CarDiagnosticEvent> events) {
manager.mHandlerCallback.sendEvents(events);
}
@@ -372,8 +372,8 @@
}
mLastUpdateTime = updateTime;
final boolean hasVendorExtensionPermission = mVendorExtensionPermission.checkGranted();
- final CarDiagnosticEvent eventToDispatch = hasVendorExtensionPermission ?
- event :
+ final CarDiagnosticEvent eventToDispatch = hasVendorExtensionPermission
+ ? event :
event.withVendorSensorsRemoved();
List<OnDiagnosticEventListener> listeners;
synchronized (mActiveListeners) {
diff --git a/car-lib/src/android/car/diagnostic/FloatSensorIndex.java b/car-lib/src/android/car/diagnostic/FloatSensorIndex.java
index 6ec7e8c..790d43f 100644
--- a/car-lib/src/android/car/diagnostic/FloatSensorIndex.java
+++ b/car-lib/src/android/car/diagnostic/FloatSensorIndex.java
@@ -12,12 +12,13 @@
* 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.diagnostic;
import android.annotation.IntDef;
import android.annotation.SystemApi;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/car-lib/src/android/car/diagnostic/IntegerSensorIndex.java b/car-lib/src/android/car/diagnostic/IntegerSensorIndex.java
index 15291f7..83fd303 100644
--- a/car-lib/src/android/car/diagnostic/IntegerSensorIndex.java
+++ b/car-lib/src/android/car/diagnostic/IntegerSensorIndex.java
@@ -12,12 +12,13 @@
* 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.diagnostic;
import android.annotation.IntDef;
import android.annotation.SystemApi;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/car-lib/src/android/car/drivingstate/CarDrivingStateEvent.java b/car-lib/src/android/car/drivingstate/CarDrivingStateEvent.java
index bd553ce..db2cf55 100644
--- a/car-lib/src/android/car/drivingstate/CarDrivingStateEvent.java
+++ b/car-lib/src/android/car/drivingstate/CarDrivingStateEvent.java
@@ -84,16 +84,19 @@
dest.writeLong(timeStamp);
}
- public static final Parcelable.Creator<CarDrivingStateEvent> CREATOR
- = new Parcelable.Creator<CarDrivingStateEvent>() {
- public CarDrivingStateEvent createFromParcel(Parcel in) {
- return new CarDrivingStateEvent(in);
- }
+ public static final Parcelable.Creator<CarDrivingStateEvent> CREATOR =
+ new Parcelable.Creator<CarDrivingStateEvent>() {
- public CarDrivingStateEvent[] newArray(int size) {
- return new CarDrivingStateEvent[size];
- }
- };
+ @Override
+ public CarDrivingStateEvent createFromParcel(Parcel in) {
+ return new CarDrivingStateEvent(in);
+ }
+
+ @Override
+ public CarDrivingStateEvent[] newArray(int size) {
+ return new CarDrivingStateEvent[size];
+ }
+ };
public CarDrivingStateEvent(int value, long time) {
eventValue = value;
diff --git a/car-lib/src/android/car/drivingstate/CarDrivingStateManager.java b/car-lib/src/android/car/drivingstate/CarDrivingStateManager.java
index 4053c5c..0998e7d 100644
--- a/car-lib/src/android/car/drivingstate/CarDrivingStateManager.java
+++ b/car-lib/src/android/car/drivingstate/CarDrivingStateManager.java
@@ -61,8 +61,8 @@
/** @hide */
@Override
public synchronized void onCarDisconnected() {
- mListenerToService = null;
- mDrvStateEventListener = null;
+ mListenerToService = null;
+ mDrvStateEventListener = null;
}
/**
@@ -182,7 +182,7 @@
ICarDrivingStateChangeListener.Stub {
private final WeakReference<CarDrivingStateManager> mDrvStateMgr;
- public CarDrivingStateChangeListenerToService(CarDrivingStateManager manager) {
+ CarDrivingStateChangeListenerToService(CarDrivingStateManager manager) {
mDrvStateMgr = new WeakReference<>(manager);
}
@@ -216,7 +216,7 @@
private static final class EventCallbackHandler extends Handler {
private final WeakReference<CarDrivingStateManager> mDrvStateMgr;
- public EventCallbackHandler(CarDrivingStateManager manager, Looper looper) {
+ EventCallbackHandler(CarDrivingStateManager manager, Looper looper) {
super(looper);
mDrvStateMgr = new WeakReference<>(manager);
}
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
index 79bd8e7..735e0e3 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
@@ -304,7 +304,7 @@
ICarUxRestrictionsChangeListener.Stub {
private final WeakReference<CarUxRestrictionsManager> mUxRestrictionsManager;
- public CarUxRestrictionsChangeListenerToService(CarUxRestrictionsManager manager) {
+ CarUxRestrictionsChangeListenerToService(CarUxRestrictionsManager manager) {
mUxRestrictionsManager = new WeakReference<>(manager);
}
@@ -337,7 +337,7 @@
private static final class EventCallbackHandler extends Handler {
private final WeakReference<CarUxRestrictionsManager> mUxRestrictionsManager;
- public EventCallbackHandler(CarUxRestrictionsManager manager, Looper looper) {
+ EventCallbackHandler(CarUxRestrictionsManager manager, Looper looper) {
super(looper);
mUxRestrictionsManager = new WeakReference<>(manager);
}
diff --git a/car-lib/src/android/car/hardware/CarPropertyConfig.java b/car-lib/src/android/car/hardware/CarPropertyConfig.java
index a74a361..df7ac5f 100644
--- a/car-lib/src/android/car/hardware/CarPropertyConfig.java
+++ b/car-lib/src/android/car/hardware/CarPropertyConfig.java
@@ -434,8 +434,8 @@
mMaxValue = maxValue;
}
- public static final Parcelable.Creator<AreaConfig<Object>> CREATOR
- = getCreator(Object.class);
+ public static final Parcelable.Creator<AreaConfig<Object>> CREATOR =
+ getCreator(Object.class);
private static <E> Parcelable.Creator<AreaConfig<E>> getCreator(final Class<E> clazz) {
return new Creator<AreaConfig<E>>() {
@@ -457,8 +457,13 @@
mMaxValue = (T) in.readValue(getClass().getClassLoader());
}
- @Nullable public T getMinValue() { return mMinValue; }
- @Nullable public T getMaxValue() { return mMaxValue; }
+ @Nullable public T getMinValue() {
+ return mMinValue;
+ }
+
+ @Nullable public T getMaxValue() {
+ return mMaxValue;
+ }
@Override
public int describeContents() {
@@ -473,10 +478,10 @@
@Override
public String toString() {
- return "CarAreaConfig{" +
- "mMinValue=" + mMinValue +
- ", mMaxValue=" + mMaxValue +
- '}';
+ return "CarAreaConfig{"
+ + "mMinValue=" + mMinValue
+ + ", mMaxValue=" + mMaxValue
+ + '}';
}
}
@@ -629,6 +634,9 @@
return this;
}
+ /**
+ * Builds a new {@link CarPropertyConfig}.
+ */
public CarPropertyConfig<T> build() {
return new CarPropertyConfig<>(mAccess, mAreaType, mChangeMode, mConfigArray,
mConfigString, mMaxSampleRate, mMinSampleRate,
diff --git a/car-lib/src/android/car/hardware/CarPropertyValue.java b/car-lib/src/android/car/hardware/CarPropertyValue.java
index 38af395..b946af9 100644
--- a/car-lib/src/android/car/hardware/CarPropertyValue.java
+++ b/car-lib/src/android/car/hardware/CarPropertyValue.java
@@ -26,6 +26,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
/**
* Stores values broken down by area for a vehicle property.
@@ -35,7 +36,7 @@
*
*/
public final class CarPropertyValue<T> implements Parcelable {
- private final static Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+ private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private final int mPropertyId;
private final int mAreaId;
@@ -152,7 +153,7 @@
// Special handling for String and byte[] to mitigate transaction buffer limitations.
if (String.class.equals(valueClass)) {
- dest.writeBlob(((String)mValue).getBytes(DEFAULT_CHARSET));
+ dest.writeBlob(((String) mValue).getBytes(DEFAULT_CHARSET));
} else if (byte[].class.equals(valueClass)) {
dest.writeBlob((byte[]) mValue);
} else {
@@ -199,12 +200,12 @@
/** @hide */
@Override
public String toString() {
- return "CarPropertyValue{" +
- "mPropertyId=0x" + toHexString(mPropertyId) +
- ", mAreaId=0x" + toHexString(mAreaId) +
- ", mStatus=" + mStatus +
- ", mTimestamp=" + mTimestamp +
- ", mValue=" + mValue +
- '}';
+ return "CarPropertyValue{"
+ + "mPropertyId=0x" + toHexString(mPropertyId)
+ + ", mAreaId=0x" + toHexString(mAreaId)
+ + ", mStatus=" + mStatus
+ + ", mTimestamp=" + mTimestamp
+ + ", mValue=" + mValue
+ + '}';
}
}
diff --git a/car-lib/src/android/car/hardware/CarSensorConfig.java b/car-lib/src/android/car/hardware/CarSensorConfig.java
index 77f8dc5..4910def 100644
--- a/car-lib/src/android/car/hardware/CarSensorConfig.java
+++ b/car-lib/src/android/car/hardware/CarSensorConfig.java
@@ -19,7 +19,6 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.ArrayList;
/**
* A CarSensorConfig object corresponds to a single sensor type coming from the car.
@@ -28,20 +27,20 @@
public class CarSensorConfig implements Parcelable {
/** List of property specific mapped elements in bundle for WHEEL_TICK_DISTANCE sensor*/
/** @hide */
- public final static String WHEEL_TICK_DISTANCE_SUPPORTED_WHEELS =
- "android.car.wheelTickDistanceSupportedWheels";
+ public static final String WHEEL_TICK_DISTANCE_SUPPORTED_WHEELS =
+ "android.car.wheelTickDistanceSupportedWheels";
/** @hide */
- public final static String WHEEL_TICK_DISTANCE_FRONT_LEFT_UM_PER_TICK =
- "android.car.wheelTickDistanceFrontLeftUmPerTick";
+ public static final String WHEEL_TICK_DISTANCE_FRONT_LEFT_UM_PER_TICK =
+ "android.car.wheelTickDistanceFrontLeftUmPerTick";
/** @hide */
- public final static String WHEEL_TICK_DISTANCE_FRONT_RIGHT_UM_PER_TICK =
- "android.car.wheelTickDistanceFrontRightUmPerTick";
+ public static final String WHEEL_TICK_DISTANCE_FRONT_RIGHT_UM_PER_TICK =
+ "android.car.wheelTickDistanceFrontRightUmPerTick";
/** @hide */
- public final static String WHEEL_TICK_DISTANCE_REAR_RIGHT_UM_PER_TICK =
- "android.car.wheelTickDistanceRearRightUmPerTick";
+ public static final String WHEEL_TICK_DISTANCE_REAR_RIGHT_UM_PER_TICK =
+ "android.car.wheelTickDistanceRearRightUmPerTick";
/** @hide */
- public final static String WHEEL_TICK_DISTANCE_REAR_LEFT_UM_PER_TICK =
- "android.car.wheelTickDistanceRearLeftUmPerTick";
+ public static final String WHEEL_TICK_DISTANCE_REAR_LEFT_UM_PER_TICK =
+ "android.car.wheelTickDistanceRearLeftUmPerTick";
/** Config data stored in Bundle */
private final Bundle mConfig;
@@ -67,16 +66,19 @@
}
/** @hide */
- public static final Parcelable.Creator<CarSensorConfig> CREATOR
- = new Parcelable.Creator<CarSensorConfig>() {
- public CarSensorConfig createFromParcel(Parcel in) {
- return new CarSensorConfig(in);
- }
+ public static final Parcelable.Creator<CarSensorConfig> CREATOR =
+ new Parcelable.Creator<CarSensorConfig>() {
- public CarSensorConfig[] newArray(int size) {
- return new CarSensorConfig[size];
- }
- };
+ @Override
+ public CarSensorConfig createFromParcel(Parcel in) {
+ return new CarSensorConfig(in);
+ }
+
+ @Override
+ public CarSensorConfig[] newArray(int size) {
+ return new CarSensorConfig[size];
+ }
+ };
/** @hide */
public CarSensorConfig(int type, Bundle b) {
@@ -101,10 +103,9 @@
public int getInt(String key) {
if (mConfig.containsKey(key)) {
return mConfig.getInt(key);
- } else {
- throw new IllegalArgumentException("SensorType " + mType +
- " does not contain key: " + key);
}
+ throw new IllegalArgumentException("SensorType " + mType
+ + " does not contain key: " + key);
}
/** @hide */
diff --git a/car-lib/src/android/car/hardware/CarSensorEvent.java b/car-lib/src/android/car/hardware/CarSensorEvent.java
index b92a789..3f33a8d 100644
--- a/car-lib/src/android/car/hardware/CarSensorEvent.java
+++ b/car-lib/src/android/car/hardware/CarSensorEvent.java
@@ -164,8 +164,8 @@
dest.writeLongArray(longValues);
}
- public static final Parcelable.Creator<CarSensorEvent> CREATOR
- = new Parcelable.Creator<CarSensorEvent>() {
+ public static final Parcelable.Creator<CarSensorEvent> CREATOR =
+ new Parcelable.Creator<CarSensorEvent>() {
public CarSensorEvent createFromParcel(Parcel in) {
return new CarSensorEvent(in);
}
@@ -203,6 +203,9 @@
"Invalid sensor type: expected %d, got %d", type, sensorType));
}
+ /**
+ * Environment data with timestamp and temperature.
+ */
public static class EnvironmentData {
public long timestamp;
/** If unsupported by the car, this value is NaN. */
@@ -577,8 +580,7 @@
* CarSensorEvent.
* @hide
*/
- public CarFuelDoorOpenData getCarFuelDoorOpenData(
- CarFuelDoorOpenData data) {
+ public CarFuelDoorOpenData getCarFuelDoorOpenData(CarFuelDoorOpenData data) {
checkType(CarSensorManager.SENSOR_TYPE_FUEL_DOOR_OPEN);
if (data == null) {
data = new CarFuelDoorOpenData();
@@ -608,8 +610,7 @@
* CarSensorEvent.
* @hide
*/
- public CarEvBatteryLevelData getCarEvBatteryLevelData(
- CarEvBatteryLevelData data) {
+ public CarEvBatteryLevelData getCarEvBatteryLevelData(CarEvBatteryLevelData data) {
checkType(CarSensorManager.SENSOR_TYPE_EV_BATTERY_LEVEL);
if (data == null) {
data = new CarEvBatteryLevelData();
@@ -646,8 +647,7 @@
* CarSensorEvent.
* @hide
*/
- public CarEvChargePortOpenData getCarEvChargePortOpenData(
- CarEvChargePortOpenData data) {
+ public CarEvChargePortOpenData getCarEvChargePortOpenData(CarEvChargePortOpenData data) {
checkType(CarSensorManager.SENSOR_TYPE_EV_CHARGE_PORT_OPEN);
if (data == null) {
data = new CarEvChargePortOpenData();
@@ -668,7 +668,7 @@
/**
* Convenience method for obtaining a {@link CarEvChargePortConnectedData} object from a
- * CarSensorEvent object with type {@link CarSensorManager#SENSOR_TYPE_EV_CHARGE_PORT_CONNECTED}.
+ * CarSensorEvent with type {@link CarSensorManager#SENSOR_TYPE_EV_CHARGE_PORT_CONNECTED}.
*
* @param data an optional output parameter which, if non-null, will be used by this method
* instead of a newly created object.
@@ -677,7 +677,7 @@
* @hide
*/
public CarEvChargePortConnectedData getCarEvChargePortConnectedData(
- CarEvChargePortConnectedData data) {
+ CarEvChargePortConnectedData data) {
checkType(CarSensorManager.SENSOR_TYPE_EV_CHARGE_PORT_CONNECTED);
if (data == null) {
data = new CarEvChargePortConnectedData();
@@ -709,7 +709,7 @@
* @hide
*/
public CarEvBatteryChargeRateData getCarEvBatteryChargeRateData(
- CarEvBatteryChargeRateData data) {
+ CarEvBatteryChargeRateData data) {
checkType(CarSensorManager.SENSOR_TYPE_EV_BATTERY_CHARGE_RATE);
if (data == null) {
data = new CarEvBatteryChargeRateData();
diff --git a/car-lib/src/android/car/hardware/CarSensorManager.java b/car-lib/src/android/car/hardware/CarSensorManager.java
index 082c8eb..d047aa1 100644
--- a/car-lib/src/android/car/hardware/CarSensorManager.java
+++ b/car-lib/src/android/car/hardware/CarSensorManager.java
@@ -496,7 +496,7 @@
* A CarSensorConfig object is returned for every sensor type. However, if there is no
* config, the data will be empty.
*
- * @param sensor type to request
+ * @param type sensor type to request
* @return CarSensorConfig object
* @hide
*/
diff --git a/car-lib/src/android/car/hardware/CarVendorExtensionManager.java b/car-lib/src/android/car/hardware/CarVendorExtensionManager.java
index b796156..639eab1 100644
--- a/car-lib/src/android/car/hardware/CarVendorExtensionManager.java
+++ b/car-lib/src/android/car/hardware/CarVendorExtensionManager.java
@@ -45,8 +45,8 @@
@SystemApi
public final class CarVendorExtensionManager extends CarManagerBase {
- private final static boolean DBG = false;
- private final static String TAG = CarVendorExtensionManager.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final String TAG = CarVendorExtensionManager.class.getSimpleName();
private final CarPropertyManager mPropertyManager;
@GuardedBy("mLock")
diff --git a/car-lib/src/android/car/hardware/cabin/CarCabinManager.java b/car-lib/src/android/car/hardware/cabin/CarCabinManager.java
index 7318176..7114ad8 100644
--- a/car-lib/src/android/car/hardware/cabin/CarCabinManager.java
+++ b/car-lib/src/android/car/hardware/cabin/CarCabinManager.java
@@ -57,8 +57,8 @@
@Deprecated
@SystemApi
public final class CarCabinManager extends CarManagerBase {
- private final static boolean DBG = false;
- private final static String TAG = "CarCabinManager";
+ private static final boolean DBG = false;
+ private static final String TAG = CarCabinManager.class.getSimpleName();
private final CarPropertyManager mCarPropertyMgr;
private final ArraySet<CarCabinEventCallback> mCallbacks = new ArraySet<>();
private CarPropertyEventListenerToBase mListenerToBase = null;
@@ -416,7 +416,7 @@
private static class CarPropertyEventListenerToBase implements CarPropertyEventCallback {
private final WeakReference<CarCabinManager> mManager;
- public CarPropertyEventListenerToBase(CarCabinManager manager) {
+ CarPropertyEventListenerToBase(CarCabinManager manager) {
mManager = new WeakReference<>(manager);
}
diff --git a/car-lib/src/android/car/hardware/power/CarPowerManager.java b/car-lib/src/android/car/hardware/power/CarPowerManager.java
index d9ff33d..54d7b28 100644
--- a/car-lib/src/android/car/hardware/power/CarPowerManager.java
+++ b/car-lib/src/android/car/hardware/power/CarPowerManager.java
@@ -35,8 +35,8 @@
*/
@SystemApi
public class CarPowerManager extends CarManagerBase {
- private final static boolean DBG = false;
- private final static String TAG = "CarPowerManager";
+ private static final boolean DBG = false;
+ private static final String TAG = CarPowerManager.class.getSimpleName();
private final Object mLock = new Object();
private final ICarPower mService;
@@ -215,7 +215,7 @@
* @hide
*/
public void setListenerWithCompletion(CarPowerStateListenerWithCompletion listener) {
- synchronized(mLock) {
+ synchronized (mLock) {
if (mListener != null || mListenerWithCompletion != null) {
throw new IllegalStateException("Listener must be cleared first");
}
diff --git a/car-lib/src/android/car/hardware/property/CarPropertyEvent.java b/car-lib/src/android/car/hardware/property/CarPropertyEvent.java
index c423c2f..4d5498d 100644
--- a/car-lib/src/android/car/hardware/property/CarPropertyEvent.java
+++ b/car-lib/src/android/car/hardware/property/CarPropertyEvent.java
@@ -42,12 +42,16 @@
/**
* @return EventType field
*/
- public int getEventType() { return mEventType; }
+ public int getEventType() {
+ return mEventType;
+ }
/**
* Returns {@link CarPropertyValue} associated with this event.
*/
- public CarPropertyValue<?> getCarPropertyValue() { return mCarPropertyValue; }
+ public CarPropertyValue<?> getCarPropertyValue() {
+ return mCarPropertyValue;
+ }
@Override
public int describeContents() {
@@ -61,16 +65,19 @@
dest.writeParcelable(mCarPropertyValue, flags);
}
- public static final Parcelable.Creator<CarPropertyEvent> CREATOR
- = new Parcelable.Creator<CarPropertyEvent>() {
+ public static final Parcelable.Creator<CarPropertyEvent> CREATOR =
+ new Parcelable.Creator<CarPropertyEvent>() {
+
+ @Override
public CarPropertyEvent createFromParcel(Parcel in) {
return new CarPropertyEvent(in);
}
+ @Override
public CarPropertyEvent[] newArray(int size) {
return new CarPropertyEvent[size];
}
- };
+ };
/**
* Constructor for {@link CarPropertyEvent}.
diff --git a/car-lib/src/android/car/hardware/property/CarPropertyManager.java b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
index 5c5cebb..e45eaa8 100644
--- a/car-lib/src/android/car/hardware/property/CarPropertyManager.java
+++ b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
@@ -176,10 +176,11 @@
for (CarPropertyConfig carPropertyConfig : configs) {
mConfigMap.put(carPropertyConfig.getPropertyId(), carPropertyConfig);
}
- } catch (Exception e) {
+ } catch (RemoteException e) {
Log.e(TAG, "getPropertyList exception ", e);
throw new RuntimeException(e);
}
+
Handler eventHandler = getEventHandler();
if (eventHandler == null) {
mHandler = null;
diff --git a/car-lib/src/android/car/navigation/CarNavigationStatusManager.java b/car-lib/src/android/car/navigation/CarNavigationStatusManager.java
index 2aa2f10..e94a188 100644
--- a/car-lib/src/android/car/navigation/CarNavigationStatusManager.java
+++ b/car-lib/src/android/car/navigation/CarNavigationStatusManager.java
@@ -61,6 +61,9 @@
* @param bundle object holding data about the navigation event. This information is
* generated using <a href="https://developer.android.com/reference/androidx/car/cluster/navigation/NavigationState.html#toParcelable()">
* androidx.car.cluster.navigation.NavigationState#toParcelable()</a>
+ *
+ * @throws IllegalStateException if the client is not holding
+ * {@link android.car.CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION} focus.
*/
@RequiresPermission(Car.PERMISSION_CAR_NAVIGATION_MANAGER)
public void sendNavigationStateChange(Bundle bundle) {
diff --git a/car-lib/src/android/car/settings/CarSettings.java b/car-lib/src/android/car/settings/CarSettings.java
index e9af8eb..ef78ff6 100644
--- a/car-lib/src/android/car/settings/CarSettings.java
+++ b/car-lib/src/android/car/settings/CarSettings.java
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2016 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
+ * 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
+ * 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.
+ * 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.settings;
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index 784ee48..867186c 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.myUid;
import android.annotation.CallbackExecutor;
@@ -32,7 +31,6 @@
import android.car.Car;
import android.car.CarManagerBase;
import android.car.ICarUserService;
-import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.IBinder;
@@ -54,7 +52,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -69,8 +69,7 @@
private static final String TAG = CarUserManager.class.getSimpleName();
private static final int HAL_TIMEOUT_MS = CarProperties.user_hal_timeout().orElse(5_000);
- // TODO(b/144120654): STOPSHIP - set to false
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
/**
* {@link UserLifecycleEvent} called when the user is starting, for components to initialize
@@ -231,6 +230,8 @@
/**
* Adds a listener for {@link UserLifecycleEvent user lifecycle events}.
*
+ * @throws IllegalStateException if the listener was already added.
+ *
* @hide
*/
@SystemApi
@@ -238,15 +239,13 @@
@RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
public void addListener(@NonNull @CallbackExecutor Executor executor,
@NonNull UserLifecycleListener listener) {
- checkInteractAcrossUsersPermission();
-
- // TODO(b/144120654): add unit tests to validate input
- // - executor cannot be null
- // - listener cannot be null
- // - listener must not be added before
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(listener, "listener cannot be null");
int uid = myUid();
synchronized (mLock) {
+ Preconditions.checkState(mListeners == null || !mListeners.containsKey(listener),
+ "already called for this listener");
if (mReceiver == null) {
mReceiver = new LifecycleResultReceiver();
try {
@@ -271,24 +270,20 @@
/**
* Removes a listener for {@link UserLifecycleEvent user lifecycle events}.
*
+ * @throws IllegalStateException if the listener was not added beforehand.
+ *
* @hide
*/
@SystemApi
@TestApi
@RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
public void removeListener(@NonNull UserLifecycleListener listener) {
- checkInteractAcrossUsersPermission();
+ Objects.requireNonNull(listener, "listener cannot be null");
- // TODO(b/144120654): add unit tests to validate input
- // - listener cannot be null
- // - listener must not be added before
int uid = myUid();
synchronized (mLock) {
- if (mListeners == null) {
- Log.w(TAG, "removeListener(): no listeners for uid " + uid);
- return;
- }
-
+ Preconditions.checkState(mListeners != null && mListeners.containsKey(listener),
+ "not called for this listener yet");
mListeners.remove(listener);
if (!mListeners.isEmpty()) {
@@ -318,14 +313,14 @@
*
* @hide
*/
- @Nullable
+ @NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
- public GetUserIdentificationAssociationResponse getUserIdentificationAssociation(
+ public UserIdentificationAssociationResponse getUserIdentificationAssociation(
@NonNull int... types) {
Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
EventLog.writeEvent(EventLogTags.CAR_USER_MGR_GET_USER_AUTH_REQ, types.length);
try {
- GetUserIdentificationAssociationResponse response =
+ UserIdentificationAssociationResponse response =
mService.getUserIdentificationAssociation(types);
if (response != null) {
EventLog.writeEvent(EventLogTags.CAR_USER_MGR_GET_USER_AUTH_RESP,
@@ -338,6 +333,59 @@
}
/**
+ * Sets the user authentication types associated with this manager's user.
+ *
+ * @hide
+ */
+ @NonNull
+ public AndroidFuture<UserIdentificationAssociationResponse> setUserIdentificationAssociation(
+ @NonNull int[] types, @NonNull int[] values) {
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(values), "must have at least one value");
+ if (types.length != values.length) {
+ throw new IllegalArgumentException("types (" + Arrays.toString(types) + ") and values ("
+ + Arrays.toString(values) + ") should have the same length");
+ }
+ // TODO(b/153900032): move this logic to a common helper
+ Object[] loggedValues = new Integer[types.length * 2];
+ for (int i = 0; i < types.length; i++) {
+ loggedValues[i * 2] = types[i];
+ loggedValues[i * 2 + 1 ] = values[i];
+ }
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SET_USER_AUTH_REQ, loggedValues);
+
+ try {
+ AndroidFuture<UserIdentificationAssociationResponse> future =
+ new AndroidFuture<UserIdentificationAssociationResponse>() {
+ @Override
+ protected void onCompleted(UserIdentificationAssociationResponse result,
+ Throwable err) {
+ if (result != null) {
+ int[] rawValues = result.getValues();
+ // TODO(b/153900032): move this logic to a common helper
+ Object[] loggedValues = new Object[rawValues.length];
+ for (int i = 0; i < rawValues.length; i++) {
+ loggedValues[i] = rawValues[i];
+ }
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SET_USER_AUTH_RESP,
+ loggedValues);
+ } else {
+ Log.w(TAG, "setUserIdentificationAssociation(" + Arrays.toString(types)
+ + ", " + Arrays.toString(values) + ") failed: " + err);
+ }
+ super.onCompleted(result, err);
+ };
+ };
+ mService.setUserIdentificationAssociation(HAL_TIMEOUT_MS, types, values, future);
+ return future;
+ } catch (RemoteException e) {
+ AndroidFuture<UserIdentificationAssociationResponse> future = new AndroidFuture<>();
+ future.complete(UserIdentificationAssociationResponse.forFailure());
+ return handleRemoteExceptionFromCarService(e, future);
+ }
+ }
+
+ /**
* Sets a callback to be notified before user switch. It should only be used by Car System UI.
*
* @hide
@@ -431,20 +479,6 @@
}
}
- private void checkInteractAcrossUsersPermission() {
- checkInteractAcrossUsersPermission(getContext());
- }
-
- private static void checkInteractAcrossUsersPermission(Context context) {
- if (context.checkSelfPermission(INTERACT_ACROSS_USERS) != PERMISSION_GRANTED
- && context.checkSelfPermission(INTERACT_ACROSS_USERS_FULL) != PERMISSION_GRANTED) {
- throw new SecurityException(
- "Must have either " + android.Manifest.permission.INTERACT_ACROSS_USERS + " or "
- + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
- + " permission");
- }
- }
-
// NOTE: this method is called by ExperimentalCarUserManager, so it can get the mService.
// "Real" ExperimentalCarUserManager instances should be obtained through
// ExperimentalCarUserManager.from(mCarUserManager)
diff --git a/car-lib/src/android/car/user/ExperimentalCarUserManager.java b/car-lib/src/android/car/user/ExperimentalCarUserManager.java
index e5a2011..1396971 100644
--- a/car-lib/src/android/car/user/ExperimentalCarUserManager.java
+++ b/car-lib/src/android/car/user/ExperimentalCarUserManager.java
@@ -27,6 +27,9 @@
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.infra.AndroidFuture;
import java.util.ArrayList;
import java.util.Collections;
@@ -42,6 +45,8 @@
@ExperimentalFeature
public final class ExperimentalCarUserManager extends CarManagerBase {
+ private static final String TAG = ExperimentalCarUserManager.class.getSimpleName();
+
/**
* User id representing invalid user.
*/
@@ -108,16 +113,31 @@
* Switches a driver to the given user.
*
* @param driverId User id of the driver to switch to.
- * @return {@code true} if user switching succeeds, or {@code false} if it fails.
+ * @return an {@link AndroidFuture} that can be used to track operation's completion and
+ * retrieve its result (if any).
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
- public boolean switchDriver(@UserIdInt int driverId) {
+ public AndroidFuture<UserSwitchResult> switchDriver(@UserIdInt int driverId) {
try {
- return mService.switchDriver(driverId);
+ AndroidFuture<UserSwitchResult> future = new AndroidFuture<>() {
+ @Override
+ protected void onCompleted(UserSwitchResult result, Throwable err) {
+ if (result == null) {
+ Log.w(TAG, "switchDriver(" + driverId + ") failed: " + err);
+ }
+ super.onCompleted(result, err);
+ }
+ };
+ mService.switchDriver(driverId, future);
+ return future;
} catch (RemoteException e) {
- return handleRemoteExceptionFromCarService(e, false);
+ AndroidFuture<UserSwitchResult> future = new AndroidFuture<>();
+ future.complete(
+ new UserSwitchResult(UserSwitchResult.STATUS_HAL_INTERNAL_FAILURE, null));
+ handleRemoteExceptionFromCarService(e);
+ return future;
}
}
diff --git a/car-lib/src/android/car/user/GetUserIdentificationAssociationResponse.java b/car-lib/src/android/car/user/GetUserIdentificationAssociationResponse.java
deleted file mode 100644
index 12bee07..0000000
--- a/car-lib/src/android/car/user/GetUserIdentificationAssociationResponse.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.car.user;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcelable;
-
-import com.android.internal.util.DataClass;
-
-/**
- * Results of a {@link CarUserManager#getUserIdentificationAssociation(int[]) request.
- *
- * @hide
- */
-@DataClass(
- genToString = true,
- genHiddenConstructor = true,
- genHiddenConstDefs = true)
-public final class GetUserIdentificationAssociationResponse implements Parcelable {
-
- /**
- * Gets the error message returned by the HAL.
- */
- @Nullable
- private String mErrorMessage;
-
- /**
- * Gets the list of
- * {@link android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationValue}
- * associates with the request.
- */
- @NonNull
- private final int[] mValues;
-
-
-
-
- // 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/GetUserIdentificationAssociationResponse.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- /**
- * Creates a new GetUserIdentificationAssociationResponse.
- *
- * @param errorMessage
- * Gets the error message returned by the HAL.
- * @param values
- * Gets the list of
- * {@link android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationValue}
- * associates with the request.
- * @hide
- */
- @DataClass.Generated.Member
- public GetUserIdentificationAssociationResponse(
- @Nullable String errorMessage,
- @NonNull int[] values) {
- this.mErrorMessage = errorMessage;
- this.mValues = values;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mValues);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- /**
- * Gets the error message returned by the HAL.
- */
- @DataClass.Generated.Member
- public @Nullable String getErrorMessage() {
- return mErrorMessage;
- }
-
- /**
- * Gets the list of
- * {@link android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationValue}
- * associates with the request.
- */
- @DataClass.Generated.Member
- public @NonNull int[] getValues() {
- return mValues;
- }
-
- @Override
- @DataClass.Generated.Member
- public String toString() {
- // You can override field toString logic by defining methods like:
- // String fieldNameToString() { ... }
-
- return "GetUserIdentificationAssociationResponse { " +
- "errorMessage = " + mErrorMessage + ", " +
- "values = " + java.util.Arrays.toString(mValues) +
- " }";
- }
-
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- byte flg = 0;
- if (mErrorMessage != null) flg |= 0x1;
- dest.writeByte(flg);
- if (mErrorMessage != null) dest.writeString(mErrorMessage);
- dest.writeIntArray(mValues);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- /* package-private */ GetUserIdentificationAssociationResponse(@NonNull android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- String errorMessage = (flg & 0x1) == 0 ? null : in.readString();
- int[] values = in.createIntArray();
-
- this.mErrorMessage = errorMessage;
- this.mValues = values;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mValues);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<GetUserIdentificationAssociationResponse> CREATOR
- = new Parcelable.Creator<GetUserIdentificationAssociationResponse>() {
- @Override
- public GetUserIdentificationAssociationResponse[] newArray(int size) {
- return new GetUserIdentificationAssociationResponse[size];
- }
-
- @Override
- public GetUserIdentificationAssociationResponse createFromParcel(@NonNull android.os.Parcel in) {
- return new GetUserIdentificationAssociationResponse(in);
- }
- };
-
- @DataClass.Generated(
- time = 1587769987549L,
- codegenVersion = "1.0.15",
- sourceFile = "packages/services/Car/car-lib/src/android/car/user/GetUserIdentificationAssociationResponse.java",
- inputSignatures = "private @android.annotation.Nullable java.lang.String mErrorMessage\nprivate final @android.annotation.NonNull int[] mValues\nclass GetUserIdentificationAssociationResponse 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/GetUserIdentificationAssociationResponse.aidl b/car-lib/src/android/car/user/UserIdentificationAssociationResponse.aidl
similarity index 92%
rename from car-lib/src/android/car/user/GetUserIdentificationAssociationResponse.aidl
rename to car-lib/src/android/car/user/UserIdentificationAssociationResponse.aidl
index d8ac0dc..9e292d4 100644
--- a/car-lib/src/android/car/user/GetUserIdentificationAssociationResponse.aidl
+++ b/car-lib/src/android/car/user/UserIdentificationAssociationResponse.aidl
@@ -16,4 +16,4 @@
package android.car.user;
-parcelable GetUserIdentificationAssociationResponse;
+parcelable UserIdentificationAssociationResponse;
diff --git a/car-lib/src/android/car/user/UserIdentificationAssociationResponse.java b/car-lib/src/android/car/user/UserIdentificationAssociationResponse.java
new file mode 100644
index 0000000..d2cdb44
--- /dev/null
+++ b/car-lib/src/android/car/user/UserIdentificationAssociationResponse.java
@@ -0,0 +1,244 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Results of a {@link CarUserManager#getUserIdentificationAssociation(int[]) request.
+ *
+ * @hide
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstructor = false,
+ genHiddenConstDefs = true)
+public final class UserIdentificationAssociationResponse implements Parcelable {
+
+ /**
+ * Whether the request was successful.
+ *
+ * <p>A successful option has non-null {@link #getValues()}
+ */
+ private final boolean mSuccess;
+
+ /**
+ * Gets the error message returned by the HAL.
+ */
+ @Nullable
+ private final String mErrorMessage;
+
+ /**
+ * Gets the list of values associated with the request.
+ *
+ * <p><b>NOTE: </b>It's only set when the response is {@link #isSuccess() successful}.
+ *
+ * <p>For {@link CarUserManager#getUserIdentificationAssociation(int...)}, the values are
+ * defined on
+ * {@link android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationValue}.
+ *
+ * <p>For {@link CarUserManager#setUserIdentificationAssociation(int...)}, the values are
+ * defined on
+ * {@link android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue}.
+ */
+ @Nullable
+ private final int[] mValues;
+
+ private UserIdentificationAssociationResponse(
+ boolean success,
+ @Nullable String errorMessage,
+ @Nullable int[] values) {
+ this.mSuccess = success;
+ this.mErrorMessage = errorMessage;
+ this.mValues = values;
+ }
+
+ /**
+ * Factory method for failed UserIdentificationAssociationResponse requests.
+ */
+ @NonNull
+ public static UserIdentificationAssociationResponse forFailure() {
+ return forFailure(/* errorMessage= */ null);
+ }
+
+ /**
+ * Factory method for failed UserIdentificationAssociationResponse requests.
+ */
+ @NonNull
+ public static UserIdentificationAssociationResponse forFailure(@Nullable String errorMessage) {
+ return new UserIdentificationAssociationResponse(/* success= */ false,
+ errorMessage, /* values= */ null);
+ }
+
+ /**
+ * Factory method for successful UserIdentificationAssociationResponse requests.
+ */
+ @NonNull
+ public static UserIdentificationAssociationResponse forSuccess(@NonNull int[] values) {
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(values), "must have at least one value");
+ return new UserIdentificationAssociationResponse(/* success= */ true,
+ /* errorMessage= */ null, Objects.requireNonNull(values));
+ }
+
+ /**
+ * Factory method for successful UserIdentificationAssociationResponse requests.
+ */
+ @NonNull
+ public static UserIdentificationAssociationResponse forSuccess(@NonNull int[] values,
+ @Nullable String errorMessage) {
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(values), "must have at least one value");
+ return new UserIdentificationAssociationResponse(/* success= */ true,
+ errorMessage, Objects.requireNonNull(values));
+ }
+
+
+
+ // 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/UserIdentificationAssociationResponse.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Whether the request was successful.
+ *
+ * <p>A successful option has non-null {@link #getValues()}
+ */
+ @DataClass.Generated.Member
+ public boolean isSuccess() {
+ return mSuccess;
+ }
+
+ /**
+ * Gets the error message returned by the HAL.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Gets the list of values associated with the request.
+ *
+ * <p><b>NOTE: </b>It's only set when the response is {@link #isSuccess() successful}.
+ *
+ * <p>For {@link CarUserManager#getUserIdentificationAssociation(int...)}, the values are
+ * defined on
+ * {@link android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationValue}.
+ *
+ * <p>For {@link CarUserManager#setUserIdentificationAssociation(int...)}, the values are
+ * defined on
+ * {@link android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue}.
+ */
+ @DataClass.Generated.Member
+ public @Nullable int[] getValues() {
+ return mValues;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "UserIdentificationAssociationResponse { " +
+ "success = " + mSuccess + ", " +
+ "errorMessage = " + mErrorMessage + ", " +
+ "values = " + java.util.Arrays.toString(mValues) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mSuccess) flg |= 0x1;
+ if (mErrorMessage != null) flg |= 0x2;
+ if (mValues != null) flg |= 0x4;
+ dest.writeByte(flg);
+ if (mErrorMessage != null) dest.writeString(mErrorMessage);
+ if (mValues != null) dest.writeIntArray(mValues);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ UserIdentificationAssociationResponse(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean success = (flg & 0x1) != 0;
+ String errorMessage = (flg & 0x2) == 0 ? null : in.readString();
+ int[] values = (flg & 0x4) == 0 ? null : in.createIntArray();
+
+ this.mSuccess = success;
+ this.mErrorMessage = errorMessage;
+ this.mValues = values;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<UserIdentificationAssociationResponse> CREATOR
+ = new Parcelable.Creator<UserIdentificationAssociationResponse>() {
+ @Override
+ public UserIdentificationAssociationResponse[] newArray(int size) {
+ return new UserIdentificationAssociationResponse[size];
+ }
+
+ @Override
+ public UserIdentificationAssociationResponse createFromParcel(@NonNull android.os.Parcel in) {
+ return new UserIdentificationAssociationResponse(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1588982917194L,
+ codegenVersion = "1.0.15",
+ sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserIdentificationAssociationResponse.java",
+ inputSignatures = "private final boolean mSuccess\nprivate final @android.annotation.Nullable java.lang.String mErrorMessage\nprivate final @android.annotation.Nullable int[] mValues\npublic static @android.annotation.NonNull android.car.user.UserIdentificationAssociationResponse forFailure()\npublic static @android.annotation.NonNull android.car.user.UserIdentificationAssociationResponse forFailure(java.lang.String)\npublic static @android.annotation.NonNull android.car.user.UserIdentificationAssociationResponse forSuccess(int[])\npublic static @android.annotation.NonNull android.car.user.UserIdentificationAssociationResponse forSuccess(int[],java.lang.String)\nclass UserIdentificationAssociationResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=false, 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 5d3e50f..5792bf6 100644
--- a/car-lib/src/android/car/user/UserSwitchResult.java
+++ b/car-lib/src/android/car/user/UserSwitchResult.java
@@ -42,6 +42,7 @@
* @hide
*/
public static final int STATUS_SUCCESSFUL = 1;
+
/**
* {@link UserSwitchStatus} called when user switch is only successful for Hal but not for
* Android. Hal user switch rollover message have been sent.
@@ -49,6 +50,7 @@
* @hide
*/
public static final int STATUS_ANDROID_FAILURE = 2;
+
/**
* {@link UserSwitchStatus} called when user switch is failed for HAL. User switch for Android
* is not called.
@@ -56,6 +58,7 @@
* @hide
*/
public static final int STATUS_HAL_FAILURE = 3;
+
/**
* {@link UserSwitchStatus} called when user switch is failed for HAL for some internal error.
* User switch for Android is not called.
@@ -63,12 +66,14 @@
* @hide
*/
public static final int STATUS_HAL_INTERNAL_FAILURE = 4;
+
/**
* {@link UserSwitchStatus} called when target user is same as current user.
*
* @hide
*/
public static final int STATUS_ALREADY_REQUESTED_USER = 5;
+
/**
* {@link UserSwitchStatus} called when another user switch request for the same target user is
* in process.
@@ -86,6 +91,14 @@
public static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST = 7;
/**
+ * {@link UserSwitchStatus} called 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;
+
+ /**
* Gets the user switch result status.
*
* @return either {@link UserSwitchResult#STATUS_SUCCESSFUL},
@@ -93,8 +106,9 @@
* {@link UserSwitchResult#STATUS_HAL_FAILURE},
* {@link UserSwitchResult#STATUS_HAL_INTERNAL_FAILURE},
* {@link UserSwitchResult#STATUS_ALREADY_REQUESTED_USER},
- * {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO}, or
- * {@link UserSwitchResult#STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST}.
+ * {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO}
+ * {@link UserSwitchResult#STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST}, or
+ * {@link UserSwitchResult#STATUS_INVALID_REQUEST}.
*/
private final int mStatus;
@@ -105,6 +119,7 @@
private final String mErrorMessage;
+
// Code below generated by codegen v1.0.15.
//
// DO NOT MODIFY!
@@ -126,7 +141,8 @@
STATUS_HAL_INTERNAL_FAILURE,
STATUS_ALREADY_REQUESTED_USER,
STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO,
- STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST
+ STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST,
+ STATUS_INVALID_REQUEST
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
@@ -150,6 +166,8 @@
return "STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO";
case STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST:
return "STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST";
+ case STATUS_INVALID_REQUEST:
+ return "STATUS_INVALID_REQUEST";
default: return Integer.toHexString(value);
}
}
@@ -165,8 +183,9 @@
* {@link UserSwitchResult#STATUS_HAL_FAILURE},
* {@link UserSwitchResult#STATUS_HAL_INTERNAL_FAILURE},
* {@link UserSwitchResult#STATUS_ALREADY_REQUESTED_USER},
- * {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO}, or
- * {@link UserSwitchResult#STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST}.
+ * {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO}
+ * {@link UserSwitchResult#STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST}, or
+ * {@link UserSwitchResult#STATUS_INVALID_REQUEST}.
* @param errorMessage
* Gets the error message, if any.
* @hide
@@ -189,8 +208,9 @@
* {@link UserSwitchResult#STATUS_HAL_FAILURE},
* {@link UserSwitchResult#STATUS_HAL_INTERNAL_FAILURE},
* {@link UserSwitchResult#STATUS_ALREADY_REQUESTED_USER},
- * {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO}, or
- * {@link UserSwitchResult#STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST}.
+ * {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO}
+ * {@link UserSwitchResult#STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST}, or
+ * {@link UserSwitchResult#STATUS_INVALID_REQUEST}.
*/
@DataClass.Generated.Member
public int getStatus() {
@@ -266,10 +286,10 @@
};
@DataClass.Generated(
- time = 1587768100440L,
+ time = 1589580732431L,
codegenVersion = "1.0.15",
sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserSwitchResult.java",
- inputSignatures = "public static final int STATUS_SUCCESSFUL\npublic static final int STATUS_ANDROID_FAILURE\npublic static final int STATUS_HAL_FAILURE\npublic static final int STATUS_HAL_INTERNAL_FAILURE\npublic static final int STATUS_ALREADY_REQUESTED_USER\npublic static final int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO\npublic static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST\nprivate final int mStatus\nprivate final @android.annotation.Nullable java.lang.String mErrorMessage\nclass UserSwitchResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final int STATUS_SUCCESSFUL\npublic static final int STATUS_ANDROID_FAILURE\npublic static final int STATUS_HAL_FAILURE\npublic static final int STATUS_HAL_INTERNAL_FAILURE\npublic static final int STATUS_ALREADY_REQUESTED_USER\npublic static final int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO\npublic static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST\npublic static final int STATUS_INVALID_REQUEST\nprivate final int mStatus\nprivate final @android.annotation.Nullable java.lang.String mErrorMessage\nclass UserSwitchResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/car-lib/src/android/car/vms/VmsAssociatedLayer.java b/car-lib/src/android/car/vms/VmsAssociatedLayer.java
index d34aa5d..7443857 100644
--- a/car-lib/src/android/car/vms/VmsAssociatedLayer.java
+++ b/car-lib/src/android/car/vms/VmsAssociatedLayer.java
@@ -24,7 +24,9 @@
import com.android.internal.util.DataClass;
-import java.util.*;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
/**
* A Vehicle Map Service layer with a list of publisher IDs it is associated with.
diff --git a/car-lib/src/android/car/vms/VmsOperationRecorder.java b/car-lib/src/android/car/vms/VmsOperationRecorder.java
index 81265d2..f56c475 100644
--- a/car-lib/src/android/car/vms/VmsOperationRecorder.java
+++ b/car-lib/src/android/car/vms/VmsOperationRecorder.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.car.vms;
import android.annotation.SystemApi;
@@ -224,6 +240,7 @@
return Log.isLoggable(TAG, LEVEL);
}
+ /** Logs the message passed as parameter. */
public void write(String msg) {
Log.println(LEVEL, TAG, msg);
}
diff --git a/car-lib/src/android/car/watchdog/CarWatchdogManager.java b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
index fff1283..d8fb7d9 100644
--- a/car-lib/src/android/car/watchdog/CarWatchdogManager.java
+++ b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
@@ -51,7 +51,7 @@
public final class CarWatchdogManager extends CarManagerBase {
private static final String TAG = CarWatchdogManager.class.getSimpleName();
- private static final boolean DEBUG = true; // STOPSHIP if true (b/151474489)
+ private static final boolean DEBUG = false; // STOPSHIP if true
private static final int INVALID_SESSION_ID = -1;
private static final int NUMBER_OF_CONDITIONS_TO_BE_MET = 2;
// Message ID representing main thread activeness checking.
diff --git a/car-lib/src/com/android/car/internal/FeatureUtil.java b/car-lib/src/com/android/car/internal/FeatureUtil.java
index ad33eb9..c601a9d 100644
--- a/car-lib/src/com/android/car/internal/FeatureUtil.java
+++ b/car-lib/src/com/android/car/internal/FeatureUtil.java
@@ -21,6 +21,9 @@
*/
public class FeatureUtil {
+ /**
+ * Ensures the flag passes as parameter is enabled.
+ */
public static void assertFeature(boolean featureFlag) {
if (!featureFlag) {
throw new IllegalStateException("Feature not enabled");
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 e5c5740..35f0274 100644
--- a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
+++ b/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
@@ -31,10 +31,12 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.os.Trace;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
+import android.util.TimingsTraceLog;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.internal.util.Preconditions;
@@ -81,6 +83,8 @@
*/
public abstract class AbstractExtendedMockitoTestCase {
+ private static final boolean TRACE = false;
+
private static final boolean VERBOSE = false;
private static final String TAG = AbstractExtendedMockitoTestCase.class.getSimpleName();
@@ -92,23 +96,46 @@
private MockitoSession mSession;
private MockSettings mSettings;
+ @Nullable
+ private final TimingsTraceLog mTracer;
+
@Rule
public final WtfCheckerRule mWtfCheckerRule = new WtfCheckerRule();
+ protected AbstractExtendedMockitoTestCase() {
+ mTracer = TRACE ? new TimingsTraceLog(TAG, Trace.TRACE_TAG_APP) : null;
+ }
+
@Before
public final void startSession() {
- if (VERBOSE) Log.v(TAG, getLogPrefix() + "startSession()");
+ beginTrace("startSession()");
+
+ beginTrace("startMocking()");
mSession = newSessionBuilder().startMocking();
+ endTrace();
+
+ beginTrace("MockSettings()");
mSettings = new MockSettings();
+ endTrace();
+
+ beginTrace("interceptWtfCalls()");
interceptWtfCalls();
+ endTrace();
+
+ endTrace(); // startSession
}
@After
public final void finishSession() {
- if (VERBOSE) Log.v(TAG, getLogPrefix() + "finishSession()");
+ beginTrace("finishSession()");
if (mSession != null) {
+ beginTrace("finishMocking()");
mSession.finishMocking();
+ endTrace();
+ } else {
+ Log.w(TAG, getClass().getSimpleName() + ".finishSession(): no session");
}
+ endTrace();
}
/**
@@ -172,7 +199,10 @@
protected final void mockGetCurrentUser(@UserIdInt int userId) {
if (VERBOSE) Log.v(TAG, getLogPrefix() + "mockGetCurrentUser(" + userId + ")");
assertSpied(ActivityManager.class);
+
+ beginTrace("mockAmGetCurrentUser-" + userId);
AndroidMockitoHelper.mockAmGetCurrentUser(userId);
+ endTrace();
}
/**
@@ -186,10 +216,40 @@
protected final void mockIsHeadlessSystemUserMode(boolean mode) {
if (VERBOSE) Log.v(TAG, getLogPrefix() + "mockIsHeadlessSystemUserMode(" + mode + ")");
assertSpied(UserManager.class);
+
+ beginTrace("mockUmIsHeadlessSystemUserMode");
AndroidMockitoHelper.mockUmIsHeadlessSystemUserMode(mode);
+ endTrace();
}
- protected void interceptWtfCalls() {
+ /**
+ * Starts a tracing message.
+ *
+ * <p>MUST be followed by a {@link #endTrace()} calls.
+ *
+ * <p>Ignored if {@value #VERBOSE} is {@code false}.
+ */
+ protected final void beginTrace(@NonNull String message) {
+ if (mTracer == null) return;
+
+ Log.d(TAG, getLogPrefix() + message);
+ mTracer.traceBegin(message);
+ }
+
+ /**
+ * Ends a tracing call.
+ *
+ * <p>MUST be called after {@link #beginTrace(String)}.
+ *
+ * <p>Ignored if {@value #VERBOSE} is {@code false}.
+ */
+ protected final void endTrace() {
+ if (mTracer == null) return;
+
+ mTracer.traceEnd();
+ }
+
+ private void interceptWtfCalls() {
doAnswer((invocation) -> {
return addWtf(invocation);
}).when(() -> Log.wtf(anyString(), anyString()));
@@ -245,14 +305,23 @@
.spyStatic(Slog.class);
onSessionBuilder(customBuilder);
+
+ if (VERBOSE) Log.v(TAG, "spied classes" + customBuilder.mStaticSpiedClasses);
+
return builder.initMocks(this);
}
- private String getLogPrefix() {
+ /**
+ * Gets a prefix for {@link Log} calls
+ */
+ protected String getLogPrefix() {
return getClass().getSimpleName() + ".";
}
- private void assertSpied(Class<?> clazz) {
+ /**
+ * Asserts the given class is being spied in the Mockito session.
+ */
+ protected void assertSpied(Class<?> clazz) {
Preconditions.checkArgument(mStaticSpiedClasses.contains(clazz),
"did not call spyStatic() on %s", clazz.getName());
}
@@ -295,20 +364,26 @@
@Override
public void evaluate() throws Throwable {
String testName = description.getMethodName();
-
if (VERBOSE) Log.v(TAG, "running " + testName);
+ beginTrace("evaluate-" + testName);
base.evaluate();
+ endTrace();
Method testMethod = AbstractExtendedMockitoTestCase.this.getClass()
.getMethod(testName);
ExpectWtf expectWtfAnnotation = testMethod.getAnnotation(ExpectWtf.class);
- if (expectWtfAnnotation != null) {
- if (VERBOSE) Log.v(TAG, "expecting wtf()");
- verifyWtfLogged();
- } else {
- if (VERBOSE) Log.v(TAG, "NOT expecting wtf()");
- verifyWtfNeverLogged();
+ beginTrace("verify-wtfs");
+ try {
+ if (expectWtfAnnotation != null) {
+ if (VERBOSE) Log.v(TAG, "expecting wtf()");
+ verifyWtfLogged();
+ } else {
+ if (VERBOSE) Log.v(TAG, "NOT expecting wtf()");
+ verifyWtfNeverLogged();
+ }
+ } finally {
+ endTrace();
}
}
};
@@ -351,6 +426,9 @@
when(Settings.System.getIntForUser(any(), any(), anyInt(), anyInt()))
.thenAnswer(getIntAnswer);
+
+ when(Settings.System.putStringForUser(any(), any(), anyString(), anyInt()))
+ .thenAnswer(insertObjectAnswer);
}
private Object insertObjectFromInvocation(InvocationOnMock invocation,
@@ -378,22 +456,27 @@
@Nullable
private <T> T get(String key, T defaultValue, Class<T> clazz) {
- if (VERBOSE) Log.v(TAG, "Getting Setting " + key);
+ if (VERBOSE) {
+ Log.v(TAG, "get(): key=" + key + ", default=" + defaultValue + ", class=" + clazz);
+ }
Object value = mSettingsMapping.get(key);
if (value == null) {
+ if (VERBOSE) Log.v(TAG, "not found");
return defaultValue;
}
+
+ if (VERBOSE) Log.v(TAG, "returning " + value);
return safeCast(value, clazz);
}
- private <T> T safeCast(Object value, Class<T> clazz) {
+ private static <T> T safeCast(Object value, Class<T> clazz) {
if (value == null) {
return null;
}
Preconditions.checkArgument(value.getClass() == clazz,
"Setting value has class %s but requires class %s",
value.getClass(), clazz);
- return (T) value;
+ return clazz.cast(value);
}
private String getString(String key) {
@@ -401,7 +484,7 @@
}
public int getInt(String key) {
- return (int) get(key, null, Integer.class);
+ return get(key, null, Integer.class);
}
}
diff --git a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java b/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
index 4a26391..d05b013 100644
--- a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
@@ -24,16 +24,24 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.car.test.util.UserTestingHelper;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
import android.os.IBinder;
import android.os.IInterface;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.os.UserManager;
+import com.android.internal.infra.AndroidFuture;
+
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/**
@@ -41,6 +49,8 @@
*/
public final class AndroidMockitoHelper {
+ private static final long ASYNC_TIMEOUT_MS = 500;
+
/**
* Mocks a call to {@link ActivityManager#getCurrentUser()}.
*
@@ -75,10 +85,27 @@
public static UserInfo mockUmGetUserInfo(@NonNull UserManager um, @UserIdInt int userId,
@UserInfoFlag int flags) {
Objects.requireNonNull(um);
- UserInfo userInfo = new UserTestingHelper.UserInfoBuilder(userId).setFlags(flags).build();
- when(um.getUserInfo(userId)).thenReturn(userInfo);
+ UserInfo user = new UserTestingHelper.UserInfoBuilder(userId).setFlags(flags).build();
+ mockUmGetUserInfo(um, user);
+ return user;
+ }
- return userInfo;
+ /**
+ * Mocks {@code UserManager.getUserInfo(userId)} to return the given {@link UserInfo}.
+ */
+ @NonNull
+ public static void mockUmGetUserInfo(@NonNull UserManager um, @NonNull UserInfo user) {
+ when(um.getUserInfo(user.id)).thenReturn(user);
+ }
+
+ /**
+ * Mocks {@code UserManager.getUserInfo(userId)} when the {@code userId} is the system user's.
+ */
+ @NonNull
+ public static void mockUmGetSystemUser(@NonNull UserManager um) {
+ UserInfo user = new UserTestingHelper.UserInfoBuilder(UserHandle.USER_SYSTEM)
+ .setFlags(UserInfo.FLAG_SYSTEM).build();
+ when(um.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(user);
}
/**
@@ -144,6 +171,40 @@
when(binder.queryLocalInterface(anyString())).thenReturn(service);
}
+ /**
+ * Mocks a call to {@link Context#getSystemService(Class)}.
+ */
+ public static <T> void mockContextGetService(@NonNull Context context,
+ @NonNull Class<T> serviceClass, @NonNull T service) {
+ when(context.getSystemService(serviceClass)).thenReturn(service);
+ if (serviceClass.equals(PackageManager.class)) {
+ when(context.getPackageManager()).thenReturn(PackageManager.class.cast(service));
+ }
+ }
+
+ /**
+ * Gets the result of a future, or throw a {@link IllegalStateException} if it times out after
+ * {@value #ASYNC_TIMEOUT_MS} ms.
+ */
+ @NonNull
+ public static <T> T getResult(@NonNull AndroidFuture<T> future)
+ throws InterruptedException, ExecutionException {
+ return getResult(future, ASYNC_TIMEOUT_MS);
+ }
+
+ /**
+ * Gets the result of a future, or throw a {@link IllegalStateException} if it times out.
+ */
+ @NonNull
+ public static <T> T getResult(@NonNull AndroidFuture<T> future, long timeoutMs)
+ throws InterruptedException, ExecutionException {
+ try {
+ return future.get(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ throw new IllegalStateException("not called in " + ASYNC_TIMEOUT_MS + "ms", e);
+ }
+ }
+
private AndroidMockitoHelper() {
throw new UnsupportedOperationException("contains only static methods");
}
diff --git a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
index af50638..ab772a9 100644
--- a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
@@ -19,6 +19,7 @@
import android.util.Log;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
@@ -33,7 +34,7 @@
*
* @param timeoutMs how long to wait for
*
- * @throws IllegalStateException} if it times out.
+ * @throws {@link IllegalStateException} if it times out.
*/
public static void await(@NonNull CountDownLatch latch, long timeoutMs)
throws InterruptedException {
@@ -43,6 +44,20 @@
}
/**
+ * Waits for a semaphore.
+ *
+ * @param timeoutMs how long to wait for
+ *
+ * @throws {@link IllegalStateException} if it times out.
+ */
+ public static void await(@NonNull Semaphore semaphore, long timeoutMs)
+ throws InterruptedException {
+ if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException(semaphore + " not released in " + timeoutMs + " ms");
+ }
+ }
+
+ /**
* Silently waits for a latch to be counted down, without throwing any exception if it isn't.
*
* @param timeoutMs how long to wait for
diff --git a/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java b/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
index f50d80a..5ed572f 100644
--- a/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
+++ b/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
@@ -23,17 +23,30 @@
import android.car.user.CarUserManager.UserLifecycleEvent;
import android.car.user.CarUserManager.UserLifecycleEventType;
import android.car.user.CarUserManager.UserLifecycleListener;
-import android.os.UserHandle;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
- * UserLifecycleListener that blocks until an event is received.
+ * {@link UserLifecycleListener} that blocks until the proper events are received.
+ *
+ * <p>It can be used in 2 "modes":
+ *
+ * <ul>
+ * <li>{@link #forAnyEvent()}: it blocks (through the {@link #waitForAnyEvent()} call) until any
+ * any event is received. It doesn't allow any customization (other than
+ * {@link Builder#setTimeout(long)}).
+ * <li>{@link #forSpecificEvents()}: it blocks (through the {@link #waitForEvents()} call) until
+ * all events specified by the {@link Builder} are received.
+ * </ul>
*/
public final class BlockingUserLifecycleListener implements UserLifecycleListener {
@@ -41,31 +54,55 @@
private static final long DEFAULT_TIMEOUT_MS = 2_000;
+ private final Object mLock = new Object();
+
private final CountDownLatch mLatch = new CountDownLatch(1);
+
+ @GuardedBy("mLock")
private final List<UserLifecycleEvent> mAllReceivedEvents = new ArrayList<>();
+ @GuardedBy("mLock")
private final List<UserLifecycleEvent> mExpectedEventsReceived = new ArrayList<>();
@UserLifecycleEventType
private final List<Integer> mExpectedEventTypes;
+ @UserLifecycleEventType
+ private final List<Integer> mExpectedEventTypesLeft;
+
@UserIdInt
- private final int mForUserId;
+ @Nullable
+ private final Integer mForUserId;
+
+ @UserIdInt
+ @Nullable
+ private final Integer mForPreviousUserId;
private final long mTimeoutMs;
private BlockingUserLifecycleListener(Builder builder) {
- mExpectedEventTypes = builder.mExpectedEventTypes;
+ mExpectedEventTypes = Collections
+ .unmodifiableList(new ArrayList<>(builder.mExpectedEventTypes));
+ mExpectedEventTypesLeft = builder.mExpectedEventTypes;
mTimeoutMs = builder.mTimeoutMs;
mForUserId = builder.mForUserId;
+ mForPreviousUserId = builder.mForPreviousUserId;
+ Log.d(TAG, "constructor: " + this);
}
/**
- * Builds a new instance with default timeout of {@value #DEFAULT_TIMEOUT_MS} and
- * which waits for one - and any one - event.
+ * Creates a builder for tests that need to wait for an arbitrary event.
*/
@NonNull
- public static BlockingUserLifecycleListener newDefaultListener() {
- return new Builder().build();
+ public static Builder forAnyEvent() {
+ return new Builder(/* forAnyEvent= */ true);
+ }
+
+ /**
+ * Creates a builder for tests that need to wait for specific events.
+ */
+ @NonNull
+ public static Builder forSpecificEvents() {
+ return new Builder(/* forAnyEvent= */ false);
}
/**
@@ -73,12 +110,22 @@
*/
public static final class Builder {
private long mTimeoutMs = DEFAULT_TIMEOUT_MS;
+ private final boolean mForAnyEvent;
+
+ private Builder(boolean forAnyEvent) {
+ mForAnyEvent = forAnyEvent;
+ }
@UserLifecycleEventType
- private List<Integer> mExpectedEventTypes = new ArrayList<>();
+ private final List<Integer> mExpectedEventTypes = new ArrayList<>();
@UserIdInt
- private int mForUserId = UserHandle.USER_NULL;
+ @Nullable
+ private Integer mForUserId;
+
+ @UserIdInt
+ @Nullable
+ private Integer mForPreviousUserId;
/**
* Sets the timeout.
@@ -90,8 +137,12 @@
/**
* Sets the expected type - once the given event is received, the listener will unblock.
+ *
+ * @throws IllegalStateException if builder is {@link #forAnyEvent}.
+ * @throws IllegalArgumentException if the expected type was already added.
*/
public Builder addExpectedEvent(@UserLifecycleEventType int eventType) {
+ assertNotForAnyEvent();
mExpectedEventTypes.add(eventType);
return this;
}
@@ -100,11 +151,21 @@
* Filters received events just for the given user.
*/
public Builder forUser(@UserIdInt int userId) {
+ assertNotForAnyEvent();
mForUserId = userId;
return this;
}
/**
+ * Filters received events just for the given previous user.
+ */
+ public Builder forPreviousUser(@UserIdInt int userId) {
+ assertNotForAnyEvent();
+ mForPreviousUserId = userId;
+ return this;
+ }
+
+ /**
* Builds a new instance.
*/
@NonNull
@@ -112,114 +173,144 @@
return new BlockingUserLifecycleListener(Builder.this);
}
+ private void assertNotForAnyEvent() {
+ Preconditions.checkState(!mForAnyEvent, "not allowed forAnyEvent()");
+ }
}
@Override
public void onEvent(UserLifecycleEvent event) {
- Log.d(TAG, "onEvent(): expecting=" + expectedEventsToString()
- + ", received=" + event);
- mAllReceivedEvents.add(event);
+ synchronized (mLock) {
+ Log.d(TAG, "onEvent(): expecting=" + mExpectedEventTypesLeft + ", received=" + event);
- if (expectingSpecificUser() && event.getUserId() != mForUserId) {
- Log.w(TAG, "ignoring event for different user");
- return;
- }
+ mAllReceivedEvents.add(event);
- Integer actualType = event.getEventType();
- boolean removed = mExpectedEventTypes.remove(actualType);
- if (removed) {
- Log.v(TAG, "event removed; still expecting for " + expectedEventsToString());
- mExpectedEventsReceived.add(event);
- } else {
- Log.v(TAG, "event not removed");
- }
+ if (expectingSpecificUser() && event.getUserId() != mForUserId) {
+ Log.w(TAG, "ignoring event for different user (expecting " + mForUserId + ")");
+ return;
+ }
- if (mExpectedEventTypes.isEmpty()) {
- Log.d(TAG, "all expected events received, counting down " + mLatch);
- mLatch.countDown();
+ if (expectingSpecificPreviousUser()
+ && event.getPreviousUserId() != mForPreviousUserId) {
+ Log.w(TAG, "ignoring event for different previous user (expecting "
+ + mForPreviousUserId + ")");
+ return;
+ }
+
+ Integer actualType = event.getEventType();
+ boolean removed = mExpectedEventTypesLeft.remove(actualType);
+ if (removed) {
+ Log.v(TAG, "event removed; still expecting for "
+ + toString(mExpectedEventTypesLeft));
+ mExpectedEventsReceived.add(event);
+ } else {
+ Log.v(TAG, "event not removed");
+ }
+
+ if (mExpectedEventTypesLeft.isEmpty() && mLatch.getCount() == 1) {
+ Log.d(TAG, "all expected events received, counting down " + mLatch);
+ mLatch.countDown();
+ }
}
}
/**
- * Helper method for {@link #waitForEvents()} when caller is expecting just one event.
+ * Blocks until any event is received, and returns it.
+ *
+ * @throws IllegalStateException if listener was built using {@link #forSpecificEvents()}.
+ * @throws IllegalStateException if it times out before any event is received.
+ * @throws InterruptedException if interrupted before any event is received.
*/
@Nullable
- public UserLifecycleEvent waitForEvent() throws InterruptedException {
- List<UserLifecycleEvent> receivedEvents = waitForEvents();
- UserLifecycleEvent event = receivedEvents.isEmpty() ? null : receivedEvents.get(0);
- Log.v(TAG, "waitForEvent(): returning " + event);
+ public UserLifecycleEvent waitForAnyEvent() throws InterruptedException {
+ Preconditions.checkState(isForAnyEvent(),
+ "cannot call waitForEvent() when built with expected events");
+ waitForExpectedEvents();
+
+ UserLifecycleEvent event;
+ synchronized (mLock) {
+ event = mAllReceivedEvents.isEmpty() ? null : mAllReceivedEvents.get(0);
+ Log.v(TAG, "waitForAnyEvent(): returning " + event);
+ }
return event;
}
/**
- * Blocks until all expected {@link #onEvent(UserLifecycleEvent)} are received.
+ * Blocks until the events specified in the {@link Builder} are received, and returns them.
*
- * @throws IllegalStateException if it times out before all events are received.
- * @throws InterruptedException if interrupted before all events are received.
+ * @throws IllegalStateException if listener was built without any call to
+ * {@link Builder#addExpectedEvent(int)} or using {@link #forAnyEvent().
+ * @throws IllegalStateException if it times out before all specified events are received.
+ * @throws InterruptedException if interrupted before all specified events are received.
*/
@NonNull
public List<UserLifecycleEvent> waitForEvents() throws InterruptedException {
- if (!mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS)) {
- String errorMessage = "did not receive all events in " + mTimeoutMs + " seconds; "
- + "received only " + allReceivedEventsToString() + ", still waiting for "
- + expectedEventsToString();
- Log.e(TAG, errorMessage);
- throw new IllegalStateException(errorMessage);
+ Preconditions.checkState(!isForAnyEvent(),
+ "cannot call waitForEvents() when built without specific expected events");
+ waitForExpectedEvents();
+ List<UserLifecycleEvent> events;
+ synchronized (mLock) {
+ events = mExpectedEventsReceived;
}
- Log.v(TAG, "waitForEvents(): returning " + mAllReceivedEvents);
- return getAllReceivedEvents();
+ Log.v(TAG, "waitForEvents(): returning " + events);
+ return events;
}
/**
- * Gets a list with all received events.
+ * Gets a list with all received events until now.
*/
@NonNull
public List<UserLifecycleEvent> getAllReceivedEvents() {
- return mAllReceivedEvents;
- }
-
- /**
- * Gets a list with just the received events set by the builder.
- */
- @NonNull
- public List<UserLifecycleEvent> getExpectedEventsReceived() {
- return mExpectedEventsReceived;
+ Preconditions.checkState(!isForAnyEvent(),
+ "cannot call getAllReceivedEvents() when built without specific expected events");
+ synchronized (mLock) {
+ return Collections.unmodifiableList(new ArrayList<>(mAllReceivedEvents));
+ }
}
@Override
public String toString() {
- return "[" + getClass().getSimpleName() + ": "
- + "timeout=" + mTimeoutMs + "ms, "
- + (expectingSpecificUser() ? "forUser=" + mForUserId : "")
- + ",received=" + allReceivedEventsToString()
- + ", waiting=" + expectedEventsToString()
- + "]";
+ return "[" + getClass().getSimpleName() + ": " + stateToString() + "]";
}
- private String allReceivedEventsToString() {
- String receivedEvents = mAllReceivedEvents
- .stream()
- .map((e) -> CarUserManager.lifecycleEventTypeToString(e.getEventType()))
- .collect(Collectors.toList())
- .toString();
- return expectingSpecificUser()
- ? "{user=" + mForUserId + ", events=" + receivedEvents + "}"
- : receivedEvents;
+ @NonNull
+ private String stateToString() {
+ synchronized (mLock) {
+ return "timeout=" + mTimeoutMs + "ms"
+ + ",expectedEventTypes=" + toString(mExpectedEventTypes)
+ + ",expectedEventTypesLeft=" + toString(mExpectedEventTypesLeft)
+ + (expectingSpecificUser() ? ",forUser=" + mForUserId : "")
+ + (expectingSpecificPreviousUser() ? ",forPrevUser=" + mForPreviousUserId : "")
+ + ",received=" + mAllReceivedEvents
+ + ",waiting=" + mExpectedEventTypesLeft;
+ }
}
- private String expectedEventsToString() {
- String expectedTypes = mExpectedEventTypes
- .stream()
- .map((type) -> CarUserManager.lifecycleEventTypeToString(type))
+ private void waitForExpectedEvents() throws InterruptedException {
+ if (!mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS)) {
+ String errorMessage = "did not receive all expected events (" + stateToString() + ")";
+ Log.e(TAG, errorMessage);
+ throw new IllegalStateException(errorMessage);
+ }
+ }
+
+ @NonNull
+ private static String toString(@NonNull List<Integer> eventTypes) {
+ return eventTypes.stream()
+ .map((i) -> CarUserManager.lifecycleEventTypeToString(i))
.collect(Collectors.toList())
.toString();
- return expectingSpecificUser()
- ? "{user=" + mForUserId + ", types=" + expectedTypes + "}"
- : expectedTypes;
+ }
+
+ private boolean isForAnyEvent() {
+ return mExpectedEventTypes.isEmpty();
}
private boolean expectingSpecificUser() {
- return mForUserId != UserHandle.USER_NULL;
+ return mForUserId != null;
}
+ private boolean expectingSpecificPreviousUser() {
+ return mForPreviousUserId != null;
+ }
}
diff --git a/car-test-lib/src/android/car/testapi/CarMockitoHelper.java b/car-test-lib/src/android/car/testapi/CarMockitoHelper.java
index d69a0f8..13c5361 100644
--- a/car-test-lib/src/android/car/testapi/CarMockitoHelper.java
+++ b/car-test-lib/src/android/car/testapi/CarMockitoHelper.java
@@ -22,12 +22,15 @@
import android.annotation.NonNull;
import android.car.Car;
import android.os.RemoteException;
+import android.util.Log;
/**
* Provides common Mockito calls for Car-specific classes.
*/
public final class CarMockitoHelper {
+ private static final String TAG = CarMockitoHelper.class.getSimpleName();
+
/**
* Mocks a call to {@link Car#handleRemoteExceptionFromCarService(RemoteException, Object)} so
* it returns the passed as 2nd argument.
@@ -35,7 +38,9 @@
public static void mockHandleRemoteExceptionFromCarServiceWithDefaultValue(
@NonNull Car car) {
doAnswer((invocation) -> {
- return invocation.getArguments()[1];
+ Object returnValue = invocation.getArguments()[1];
+ Log.v(TAG, "mocking handleRemoteExceptionFromCarService(): " + returnValue);
+ return returnValue;
}).when(car).handleRemoteExceptionFromCarService(isA(RemoteException.class), any());
}
diff --git a/car_product/overlay/frameworks/base/core/res/res/drawable-night/item_background_material.xml b/car_product/overlay/frameworks/base/core/res/res/drawable-night/item_background_material.xml
new file mode 100644
index 0000000..cb36bb5
--- /dev/null
+++ b/car_product/overlay/frameworks/base/core/res/res/drawable-night/item_background_material.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#2371cd">
+ <item android:id="@android:id/mask">
+ <color android:color="@android:color/white" />
+ </item>
+</ripple>
diff --git a/car_product/overlay/frameworks/base/core/res/res/drawable/item_background_material.xml b/car_product/overlay/frameworks/base/core/res/res/drawable/item_background_material.xml
new file mode 100644
index 0000000..9311499
--- /dev/null
+++ b/car_product/overlay/frameworks/base/core/res/res/drawable/item_background_material.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#4b9eff">
+ <item android:id="@android:id/mask">
+ <color android:color="@android:color/white" />
+ </item>
+</ripple>
diff --git a/car_product/overlay/frameworks/base/core/res/res/layout/alert_dialog_button_bar_material.xml b/car_product/overlay/frameworks/base/core/res/res/layout/alert_dialog_button_bar_material.xml
index 0df7215..b227bec 100644
--- a/car_product/overlay/frameworks/base/core/res/res/layout/alert_dialog_button_bar_material.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/layout/alert_dialog_button_bar_material.xml
@@ -31,13 +31,13 @@
android:paddingTop="@*android:dimen/button_bar_layout_top_padding"
android:layoutDirection="locale"
android:orientation="horizontal"
- android:gravity="center_vertical">
+ android:gravity="left|center_vertical">
<Button
android:id="@*android:id/button3"
style="@*android:style/CarAction1"
android:background="@*android:drawable/car_dialog_button_background"
- android:layout_marginEnd="@*android:dimen/button_end_margin"
+ android:layout_marginRight="@*android:dimen/button_end_margin"
android:layout_width="wrap_content"
android:layout_height="@*android:dimen/button_layout_height" />
@@ -45,7 +45,7 @@
android:id="@*android:id/button2"
style="@*android:style/CarAction1"
android:background="@*android:drawable/car_dialog_button_background"
- android:layout_marginEnd="@*android:dimen/button_end_margin"
+ android:layout_marginRight="@*android:dimen/button_end_margin"
android:layout_width="wrap_content"
android:layout_height="@*android:dimen/button_layout_height" />
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index 4ba64f0..60e5e20 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -101,6 +101,11 @@
<!-- Turn off Wallpaper service -->
<bool name="config_enableWallpaperService">false</bool>
+ <!-- Flag specifying whether user-switch operations have custom UI. When false, user-switch
+ UI is handled by ActivityManagerService. On AAOS, this value should be true since the
+ UserSwitchUi is implemented by Car SystemUI.-->
+ <bool name="config_customUserSwitchUi">true</bool>
+
<!-- Whether to only install system packages on a user if they're whitelisted for that user
type. Override the default value in framework config file.
0 - disable whitelist (install all system packages; no logging)
diff --git a/car_product/sepolicy/private/carservice_app.te b/car_product/sepolicy/private/carservice_app.te
index de8d874..d62cb27 100644
--- a/car_product/sepolicy/private/carservice_app.te
+++ b/car_product/sepolicy/private/carservice_app.te
@@ -69,6 +69,7 @@
allow carservice_app sysfs_fs_lifetime_write:file { getattr open read };
set_prop(carservice_app, ctl_start_prop)
+set_prop(carservice_app, ctl_stop_prop)
unix_socket_connect(carservice_app, dumpstate, dumpstate)
# Allow reading vehicle-specific configuration
diff --git a/evs/apps/default/Android.bp b/evs/apps/default/Android.bp
index 7fd60ef..df6fd0d 100644
--- a/evs/apps/default/Android.bp
+++ b/evs/apps/default/Android.bp
@@ -63,10 +63,6 @@
"LabeledChecker.png",
],
- strip: {
- keep_symbols: true,
- },
-
init_rc: ["evs_app.rc"],
cflags: ["-DLOG_TAG=\"EvsApp\""] + [
diff --git a/evs/apps/default/ConfigManager.h b/evs/apps/default/ConfigManager.h
index 6acd83d..9c6d1a2 100644
--- a/evs/apps/default/ConfigManager.h
+++ b/evs/apps/default/ConfigManager.h
@@ -19,6 +19,8 @@
#include <vector>
#include <string>
+#include <system/graphics-base.h>
+
class ConfigManager {
public:
@@ -91,6 +93,12 @@
const DisplayInfo& getActiveDisplay() const { return mDisplays[mActiveDisplayId]; };
void useExternalMemory(bool flag) { mUseExternalMemory = flag; }
bool getUseExternalMemory() const { return mUseExternalMemory; }
+ void setExternalMemoryFormat(android_pixel_format_t format) {
+ mExternalMemoryFormat = format;
+ }
+ android_pixel_format_t getExternalMemoryFormat() const {
+ return mExternalMemoryFormat;
+ }
private:
// Camera information
@@ -103,6 +111,9 @@
// Memory management
bool mUseExternalMemory;
+ // Format of external memory
+ android_pixel_format_t mExternalMemoryFormat;
+
// Car body information (assumes front wheel steering and origin at center of rear axel)
// Note that units aren't specified and don't matter as long as all length units are consistent
// within the JSON file from which we parse. That is, if everything is in meters, that's fine.
diff --git a/evs/apps/default/RenderDirectView.cpp b/evs/apps/default/RenderDirectView.cpp
index 2938521..68b731e 100644
--- a/evs/apps/default/RenderDirectView.cpp
+++ b/evs/apps/default/RenderDirectView.cpp
@@ -116,7 +116,8 @@
mCameraDesc.v1.cameraId.c_str(),
foundCfg ? std::move(targetCfg) : nullptr,
sDisplay,
- mConfig.getUseExternalMemory()));
+ mConfig.getUseExternalMemory(),
+ mConfig.getExternalMemoryFormat()));
if (!mTexture) {
LOG(ERROR) << "Failed to set up video texture for " << mCameraDesc.v1.cameraId;
// TODO: For production use, we may actually want to fail in this case, but not yet...
diff --git a/evs/apps/default/StreamHandler.cpp b/evs/apps/default/StreamHandler.cpp
index b1cfd1f..d350af1 100644
--- a/evs/apps/default/StreamHandler.cpp
+++ b/evs/apps/default/StreamHandler.cpp
@@ -30,6 +30,7 @@
StreamHandler::StreamHandler(android::sp <IEvsCamera> pCamera,
uint32_t numBuffers,
bool useOwnBuffers,
+ android_pixel_format_t format,
int32_t width,
int32_t height)
: mCamera(pCamera),
@@ -46,7 +47,6 @@
const auto usage = GRALLOC_USAGE_HW_TEXTURE |
GRALLOC_USAGE_SW_READ_RARELY |
GRALLOC_USAGE_SW_WRITE_OFTEN;
- const auto format = HAL_PIXEL_FORMAT_RGBA_8888;
for (auto i = 0; i < numBuffers; ++i) {
unsigned pixelsPerLine;
android::status_t result = alloc.allocate(width,
@@ -64,10 +64,10 @@
BufferDesc_1_1 buf;
AHardwareBuffer_Desc* pDesc =
reinterpret_cast<AHardwareBuffer_Desc *>(&buf.buffer.description);
- pDesc->width = 640;
- pDesc->height = 360;
+ pDesc->width = width;
+ pDesc->height = height;
pDesc->layers = 1;
- pDesc->format = HAL_PIXEL_FORMAT_RGBA_8888;
+ pDesc->format = format;
pDesc->usage = GRALLOC_USAGE_HW_TEXTURE |
GRALLOC_USAGE_SW_READ_RARELY |
GRALLOC_USAGE_SW_WRITE_OFTEN;
diff --git a/evs/apps/default/StreamHandler.h b/evs/apps/default/StreamHandler.h
index f877c78..cb22b36 100644
--- a/evs/apps/default/StreamHandler.h
+++ b/evs/apps/default/StreamHandler.h
@@ -51,6 +51,7 @@
StreamHandler(android::sp <IEvsCamera> pCamera,
uint32_t numBuffers = 2,
bool useOwnBuffers = false,
+ android_pixel_format_t format = HAL_PIXEL_FORMAT_RGBA_8888,
int32_t width = 640,
int32_t height = 360);
void shutdown();
diff --git a/evs/apps/default/VideoTex.cpp b/evs/apps/default/VideoTex.cpp
index 94e734a..7491dfe 100644
--- a/evs/apps/default/VideoTex.cpp
+++ b/evs/apps/default/VideoTex.cpp
@@ -137,29 +137,39 @@
const char* evsCameraId,
std::unique_ptr<Stream> streamCfg,
EGLDisplay glDisplay,
- bool useExternalMemory) {
+ bool useExternalMemory,
+ android_pixel_format_t format) {
// Set up the camera to feed this texture
sp<IEvsCamera> pCamera = nullptr;
+ sp<StreamHandler> pStreamHandler = nullptr;
if (streamCfg != nullptr) {
pCamera = pEnum->openCamera_1_1(evsCameraId, *streamCfg);
+
+ // Initialize the stream that will help us update this texture's contents
+ pStreamHandler = new StreamHandler(pCamera,
+ 2, // number of buffers
+ useExternalMemory,
+ format,
+ streamCfg->width,
+ streamCfg->height);
} else {
pCamera =
IEvsCamera::castFrom(pEnum->openCamera(evsCameraId))
.withDefault(nullptr);
+
+ // Initialize the stream with the default resolution
+ pStreamHandler = new StreamHandler(pCamera,
+ 2, // number of buffers
+ useExternalMemory,
+ format);
}
- if (pCamera.get() == nullptr) {
+ if (pCamera == nullptr) {
LOG(ERROR) << "Failed to allocate new EVS Camera interface for " << evsCameraId;
return nullptr;
}
- // Initialize the stream that will help us update this texture's contents
- sp<StreamHandler> pStreamHandler = new StreamHandler(pCamera,
- 2, // number of buffers
- useExternalMemory,
- streamCfg->width,
- streamCfg->height);
- if (pStreamHandler.get() == nullptr) {
+ if (pStreamHandler == nullptr) {
LOG(ERROR) << "Failed to allocate FrameHandler";
return nullptr;
}
diff --git a/evs/apps/default/VideoTex.h b/evs/apps/default/VideoTex.h
index d884faa..097d086 100644
--- a/evs/apps/default/VideoTex.h
+++ b/evs/apps/default/VideoTex.h
@@ -16,6 +16,9 @@
#ifndef VIDEOTEX_H
#define VIDEOTEX_H
+#include "StreamHandler.h"
+#include "TexWrapper.h"
+
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
@@ -25,9 +28,7 @@
#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
#include <android/hardware/camera/device/3.2/ICameraDevice.h>
-
-#include "TexWrapper.h"
-#include "StreamHandler.h"
+#include <system/graphics-base.h>
using ::android::hardware::camera::device::V3_2::Stream;
using namespace ::android::hardware::automotive::evs::V1_1;
@@ -38,7 +39,8 @@
const char *evsCameraId,
std::unique_ptr<Stream> streamCfg,
EGLDisplay glDisplay,
- bool useExternalMemory);
+ bool useExternalMemory,
+ android_pixel_format_t format);
public:
VideoTex() = delete;
@@ -62,10 +64,13 @@
};
+// Creates a video texture to draw the camera preview. format is effective only
+// when useExternalMemory is true.
VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
const char * deviceName,
std::unique_ptr<Stream> streamCfg,
EGLDisplay glDisplay,
- bool useExternalMemory = false);
+ bool useExternalMemory = false,
+ android_pixel_format_t format = HAL_PIXEL_FORMAT_RGBA_8888);
#endif // VIDEOTEX_H
diff --git a/evs/apps/default/evs_app.cpp b/evs/apps/default/evs_app.cpp
index a968990..9f2f2c8 100644
--- a/evs/apps/default/evs_app.cpp
+++ b/evs/apps/default/evs_app.cpp
@@ -14,24 +14,25 @@
* limitations under the License.
*/
+#include "ConfigManager.h"
+#include "EvsStateControl.h"
+#include "EvsVehicleListener.h"
+
#include <stdio.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h> // arraysize
+#include <android-base/strings.h>
#include <hidl/HidlTransportSupport.h>
+#include <hwbinder/ProcessState.h>
#include <utils/Errors.h>
#include <utils/StrongPointer.h>
#include <utils/Log.h>
-#include "android-base/macros.h" // arraysize
-#include "android-base/logging.h"
-#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
-#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
-
-#include <hwbinder/ProcessState.h>
-
-#include "EvsStateControl.h"
-#include "EvsVehicleListener.h"
-#include "ConfigManager.h"
+using android::base::EqualsIgnoreCase;
// libhidl:
using android::hardware::configureRpcThreadpool;
@@ -66,6 +67,24 @@
}
+static bool convertStringToFormat(const char* str, android_pixel_format_t* output) {
+ bool result = true;
+ if (EqualsIgnoreCase(str, "RGBA8888")) {
+ *output = HAL_PIXEL_FORMAT_RGBA_8888;
+ } else if (EqualsIgnoreCase(str, "YV12")) {
+ *output = HAL_PIXEL_FORMAT_YV12;
+ } else if (EqualsIgnoreCase(str, "NV21")) {
+ *output = HAL_PIXEL_FORMAT_YCrCb_420_SP;
+ } else if (EqualsIgnoreCase(str, "YUYV")) {
+ *output = HAL_PIXEL_FORMAT_YCBCR_422_I;
+ } else {
+ result = false;
+ }
+
+ return result;
+}
+
+
// Main entry point
int main(int argc, char** argv)
{
@@ -77,6 +96,7 @@
const char* evsServiceName = "default";
int displayId = 1;
bool useExternalMemory = false;
+ android_pixel_format_t extMemoryFormat = HAL_PIXEL_FORMAT_RGBA_8888;
for (int i=1; i< argc; i++) {
if (strcmp(argv[i], "--test") == 0) {
useVehicleHal = false;
@@ -90,6 +110,19 @@
displayId = std::stoi(argv[++i]);
} else if (strcmp(argv[i], "--extmem") == 0) {
useExternalMemory = true;
+ if (i + 1 >= argc) {
+ // use RGBA8888 by default
+ LOG(INFO) << "External buffer format is not set. "
+ << "RGBA8888 will be used.";
+ } else {
+ if (!convertStringToFormat(argv[i + 1], &extMemoryFormat)) {
+ LOG(WARNING) << "Color format string " << argv[i + 1]
+ << " is unknown or not supported. RGBA8888 will be used.";
+ } else {
+ // move the index
+ ++i;
+ }
+ }
} else {
printf("Ignoring unrecognized command line arg '%s'\n", argv[i]);
printHelp = true;
@@ -97,11 +130,23 @@
}
if (printHelp) {
printf("Options include:\n");
- printf(" --test Do not talk to Vehicle Hal, but simulate 'reverse' instead\n");
- printf(" --hw Bypass EvsManager by connecting directly to EvsEnumeratorHw\n");
- printf(" --mock Connect directly to EvsEnumeratorHw-Mock\n");
- printf(" --display Specify the display to use\n");
- printf(" --extmem Application allocates buffers to capture camera frames\n");
+ printf(" --test\n\tDo not talk to Vehicle Hal, but simulate 'reverse' instead\n");
+ printf(" --hw\n\tBypass EvsManager by connecting directly to EvsEnumeratorHw\n");
+ printf(" --mock\n\tConnect directly to EvsEnumeratorHw-Mock\n");
+ printf(" --display\n\tSpecify the display to use\n");
+ printf(" --extmem <format>\n\t"
+ "Application allocates buffers to capture camera frames. "
+ "Available format strings are (case insensitive):\n");
+ printf("\t\tRGBA8888: 4x8-bit RGBA format. This is the default format to be used "
+ "when no format is specified.\n");
+ printf("\t\tYV12: YUV420 planar format with a full resolution Y plane "
+ "followed by a V values, with U values last.\n");
+ printf("\t\tNV21: A biplanar format with a full resolution Y plane "
+ "followed by a single chrome plane with weaved V and U values.\n");
+ printf("\t\tYUYV: Packed format with a half horizontal chrome resolution. "
+ "Known as YUV4:2:2.\n");
+
+ return EXIT_FAILURE;
}
// Load our configuration information
@@ -140,6 +185,7 @@
}
config.setActiveDisplayId(displayId);
config.useExternalMemory(useExternalMemory);
+ config.setExternalMemoryFormat(extMemoryFormat);
// Connect to the Vehicle HAL so we can monitor state
sp<IVehicle> pVnet;
diff --git a/evs/apps/demo_app_evs_support_lib/Android.bp b/evs/apps/demo_app_evs_support_lib/Android.bp
index 5380a4a..ae0073a 100644
--- a/evs/apps/demo_app_evs_support_lib/Android.bp
+++ b/evs/apps/demo_app_evs_support_lib/Android.bp
@@ -30,10 +30,6 @@
include_dirs: ["packages/services/Car/evs/support_library"],
- strip: {
- keep_symbols: true,
- },
-
init_rc: ["evs_app_support_lib.rc"],
cflags: ["-DLOG_TAG=\"EvsAppSupportLib\""] + [
diff --git a/evs/manager/1.0/Android.bp b/evs/manager/1.0/Android.bp
index 260e0b7..82bcdd8 100644
--- a/evs/manager/1.0/Android.bp
+++ b/evs/manager/1.0/Android.bp
@@ -39,10 +39,6 @@
init_rc: ["android.automotive.evs.manager@1.0.rc"],
- strip: {
- keep_symbols: true,
- },
-
cflags: ["-DLOG_TAG=\"EvsManagerV1_0\""] + [
"-DGL_GLEXT_PROTOTYPES",
"-DEGL_EGLEXT_PROTOTYPES",
diff --git a/evs/manager/1.1/Android.bp b/evs/manager/1.1/Android.bp
index 694aa06..891d615 100644
--- a/evs/manager/1.1/Android.bp
+++ b/evs/manager/1.1/Android.bp
@@ -20,35 +20,36 @@
name: "android.automotive.evs.manager@1.1",
srcs: [
- "service.cpp",
"Enumerator.cpp",
"HalCamera.cpp",
- "VirtualCamera.cpp",
"HalDisplay.cpp",
+ "VirtualCamera.cpp",
+ "service.cpp",
+ "stats/CameraUsageStats.cpp",
+ "stats/LooperWrapper.cpp",
+ "stats/StatsCollector.cpp",
"sync/unique_fd.cpp",
"sync/unique_fence.cpp",
"sync/unique_timeline.cpp",
],
shared_libs: [
- "libbase",
- "libcutils",
- "libutils",
- "libui",
- "libsync",
- "libhidlbase",
- "libhardware",
- "libcamera_metadata",
"android.hardware.automotive.evs@1.0",
"android.hardware.automotive.evs@1.1",
+ "libbase",
+ "libcamera_metadata",
+ "libcutils",
+ "libhardware",
+ "libhidlbase",
+ "libprocessgroup",
+ "libstatslog",
+ "libsync",
+ "libui",
+ "libutils",
],
init_rc: ["android.automotive.evs.manager@1.1.rc"],
- strip: {
- keep_symbols: true,
- },
-
cflags: ["-DLOG_TAG=\"EvsManagerV1_1\""] + [
"-DGL_GLEXT_PROTOTYPES",
"-DEGL_EGLEXT_PROTOTYPES",
diff --git a/evs/manager/1.1/Enumerator.cpp b/evs/manager/1.1/Enumerator.cpp
index 8417828..fdf3500 100644
--- a/evs/manager/1.1/Enumerator.cpp
+++ b/evs/manager/1.1/Enumerator.cpp
@@ -14,22 +14,50 @@
* limitations under the License.
*/
-#include <android-base/parseint.h>
-#include <android-base/strings.h>
-#include <android-base/logging.h>
-#include <hwbinder/IPCThreadState.h>
-#include <cutils/android_filesystem_config.h>
-
#include "Enumerator.h"
#include "HalDisplay.h"
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android-base/stringprintf.h>
+#include <cutils/android_filesystem_config.h>
+#include <hwbinder/IPCThreadState.h>
+
+namespace {
+
+ const char* kSingleIndent = "\t";
+ const char* kDumpOptionAll = "all";
+ const char* kDumpDeviceCamera = "camera";
+ const char* kDumpDeviceDisplay = "display";
+
+ const char* kDumpCameraCommandCurrent = "--current";
+ const char* kDumpCameraCommandCollected = "--collected";
+ const char* kDumpCameraCommandCustom = "--custom";
+ const char* kDumpCameraCommandCustomStart = "start";
+ const char* kDumpCameraCommandCustomStop = "stop";
+
+ const int kDumpCameraMinNumArgs = 4;
+ const int kOptionDumpDeviceTypeIndex = 1;
+ const int kOptionDumpCameraTypeIndex = 2;
+ const int kOptionDumpCameraCommandIndex = 3;
+ const int kOptionDumpCameraArgsStartIndex = 4;
+
+}
+
namespace android {
namespace automotive {
namespace evs {
namespace V1_1 {
namespace implementation {
+using ::android::base::Error;
using ::android::base::EqualsIgnoreCase;
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+using ::android::base::WriteStringToFd;
using CameraDesc_1_0 = ::android::hardware::automotive::evs::V1_0::CameraDesc;
using CameraDesc_1_1 = ::android::hardware::automotive::evs::V1_1::CameraDesc;
@@ -43,15 +71,32 @@
// Get an internal display identifier.
mHwEnumerator->getDisplayIdList(
[this](const auto& displayPorts) {
- if (displayPorts.size() > 0) {
- mInternalDisplayPort = displayPorts[0];
- } else {
+ for (auto& port : displayPorts) {
+ mDisplayPorts.push_back(port);
+ }
+
+ // The first element is the internal display
+ mInternalDisplayPort = mDisplayPorts.front();
+ if (mDisplayPorts.size() < 1) {
LOG(WARNING) << "No display is available to EVS service.";
}
}
);
}
+ // Starts the statistics collection
+ mMonitorEnabled = false;
+ mClientsMonitor = new StatsCollector();
+ if (mClientsMonitor != nullptr) {
+ auto result = mClientsMonitor->startCollection();
+ if (!result.ok()) {
+ LOG(ERROR) << "Failed to start the usage monitor: "
+ << result.error();
+ } else {
+ mMonitorEnabled = true;
+ }
+ }
+
return result;
}
@@ -188,7 +233,10 @@
if (device == nullptr) {
LOG(ERROR) << "Failed to open hardware camera " << cameraId;
} else {
- hwCamera = new HalCamera(device, cameraId);
+ // Calculates the usage statistics record identifier
+ auto fn = mCameraDevices.hash_function();
+ auto recordId = fn(cameraId) & 0xFF;
+ hwCamera = new HalCamera(device, cameraId, recordId);
if (hwCamera == nullptr) {
LOG(ERROR) << "Failed to allocate camera wrapper object";
mHwEnumerator->closeCamera(device);
@@ -239,6 +287,9 @@
// NOTE: This should drop our last reference to the camera, resulting in its
// destruction.
mActiveCameras.erase(halCamera->getId());
+ if (mMonitorEnabled) {
+ mClientsMonitor->unregisterClientToMonitor(halCamera->getId());
+ }
}
}
@@ -277,7 +328,10 @@
success = false;
break;
} else {
- hwCamera = new HalCamera(device, id, streamCfg);
+ // Calculates the usage statistics record identifier
+ auto fn = mCameraDevices.hash_function();
+ auto recordId = fn(id) & 0xFF;
+ hwCamera = new HalCamera(device, id, recordId, streamCfg);
if (hwCamera == nullptr) {
LOG(ERROR) << "Failed to allocate camera wrapper object";
mHwEnumerator->closeCamera(device);
@@ -295,6 +349,10 @@
// Add the hardware camera to our list, which will keep it alive via ref count
mActiveCameras.try_emplace(id, hwCamera);
+ if (mMonitorEnabled) {
+ mClientsMonitor->registerClientToMonitor(hwCamera);
+ }
+
sourceCameras.push_back(hwCamera);
} else {
if (it->second->getStreamConfig().id != streamCfg.id) {
@@ -392,7 +450,7 @@
// TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and
// wraps the IEvsDisplay object the driver returns. We may want to remove this
// additional class when it is fixed properly.
- sp<IEvsDisplay_1_0> pHalDisplay = new HalDisplay(pActiveDisplay);
+ sp<IEvsDisplay_1_0> pHalDisplay = new HalDisplay(pActiveDisplay, mInternalDisplayPort);
mActiveDisplay = pHalDisplay;
return pHalDisplay;
@@ -444,6 +502,11 @@
return nullptr;
}
+ if (std::find(mDisplayPorts.begin(), mDisplayPorts.end(), id) == mDisplayPorts.end()) {
+ LOG(ERROR) << "No display is available on the port " << static_cast<int32_t>(id);
+ return nullptr;
+ }
+
// We simply keep track of the most recently opened display instance.
// In the underlying layers we expect that a new open will cause the previous
// object to be destroyed. This avoids any race conditions associated with
@@ -462,7 +525,7 @@
// TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and
// wraps the IEvsDisplay object the driver returns. We may want to remove this
// additional class when it is fixed properly.
- sp<IEvsDisplay_1_1> pHalDisplay = new HalDisplay(pActiveDisplay);
+ sp<IEvsDisplay_1_1> pHalDisplay = new HalDisplay(pActiveDisplay, id);
mActiveDisplay = pHalDisplay;
return pHalDisplay;
@@ -504,7 +567,7 @@
if (fd.getNativeHandle() != nullptr && fd->numFds > 0) {
cmdDump(fd->data[0], options);
} else {
- LOG(ERROR) << "Invalid parameters";
+ LOG(ERROR) << "Given file descriptor is not valid.";
}
return {};
@@ -513,7 +576,8 @@
void Enumerator::cmdDump(int fd, const hidl_vec<hidl_string>& options) {
if (options.size() == 0) {
- dprintf(fd, "No option is given");
+ WriteStringToFd("No option is given.\n", fd);
+ cmdHelp(fd);
return;
}
@@ -525,19 +589,26 @@
} else if (EqualsIgnoreCase(option, "--dump")) {
cmdDumpDevice(fd, options);
} else {
- dprintf(fd, "Invalid option: %s\n", option.c_str());
+ WriteStringToFd(StringPrintf("Invalid option: %s\n", option.c_str()),
+ fd);
}
}
void Enumerator::cmdHelp(int fd) {
- dprintf(fd, "Usage: \n\n");
- dprintf(fd, "--help: shows this help.\n");
- dprintf(fd, "--list [all|camera|display]: list camera or display devices or both "
- "available to EVS manager.\n");
- dprintf(fd, "--dump [all|camera|display] <device id>: "
- "show current status of the target device or all devices "
- "when no device is given.\n");
+ WriteStringToFd("--help: shows this help.\n"
+ "--list [all|camera|display]: lists camera or display devices or both "
+ "available to EVS manager.\n"
+ "--dump camera [all|device_id] --[current|collected|custom] [args]\n"
+ "\tcurrent: shows the current status\n"
+ "\tcollected: shows 10 most recent periodically collected camera usage "
+ "statistics\n"
+ "\tcustom: starts/stops collecting the camera usage statistics\n"
+ "\t\tstart [interval] [duration]: starts collecting usage statistics "
+ "at every [interval] during [duration]. Interval and duration are in "
+ "milliseconds.\n"
+ "\t\tstop: stops collecting usage statistics and shows collected records.\n"
+ "--dump display: shows current status of the display\n", fd);
}
@@ -546,18 +617,25 @@
bool listDisplays = true;
if (options.size() > 1) {
const std::string option = options[1];
- const bool listAll = EqualsIgnoreCase(option, "all");
- listCameras = listAll || EqualsIgnoreCase(option, "camera");
- listDisplays = listAll || EqualsIgnoreCase(option, "display");
+ const bool listAll = EqualsIgnoreCase(option, kDumpOptionAll);
+ listCameras = listAll || EqualsIgnoreCase(option, kDumpDeviceCamera);
+ listDisplays = listAll || EqualsIgnoreCase(option, kDumpDeviceDisplay);
if (!listCameras && !listDisplays) {
- dprintf(fd, "Unrecognized option, %s, is ignored.\n", option.c_str());
+ WriteStringToFd(StringPrintf("Unrecognized option, %s, is ignored.\n",
+ option.c_str()),
+ fd);
+
+ // Nothing to show, return
+ return;
}
}
+ std::string buffer;
if (listCameras) {
- dprintf(fd, "Camera devices available to EVS service:\n");
+ StringAppendF(&buffer,"Camera devices available to EVS service:\n");
if (mCameraDevices.size() < 1) {
- // Camera devices may not be enumerated yet.
+ // Camera devices may not be enumerated yet. This may fail if the
+ // user is not permitted to use EVS service.
getCameraList_1_1(
[](const auto cameras) {
if (cameras.size() < 1) {
@@ -567,62 +645,209 @@
}
for (auto& [id, desc] : mCameraDevices) {
- dprintf(fd, "\t%s\n", id.c_str());
+ StringAppendF(&buffer, "%s%s\n", kSingleIndent, id.c_str());
}
- dprintf(fd, "\nCamera devices currently in use:\n");
+ StringAppendF(&buffer, "%sCamera devices currently in use:\n", kSingleIndent);
for (auto& [id, ptr] : mActiveCameras) {
- dprintf(fd, "\t%s\n", id.c_str());
+ StringAppendF(&buffer, "%s%s\n", kSingleIndent, id.c_str());
}
- dprintf(fd, "\n");
+ StringAppendF(&buffer, "\n");
}
if (listDisplays) {
if (mHwEnumerator != nullptr) {
- dprintf(fd, "Display devices available to EVS service:\n");
+ StringAppendF(&buffer, "Display devices available to EVS service:\n");
// Get an internal display identifier.
mHwEnumerator->getDisplayIdList(
[&](const auto& displayPorts) {
- for (auto& port : displayPorts) {
- dprintf(fd, "\tdisplay port %u\n", (unsigned)port);
+ for (auto&& port : displayPorts) {
+ StringAppendF(&buffer, "%sdisplay port %u\n",
+ kSingleIndent,
+ static_cast<unsigned>(port));
}
}
);
+ } else {
+ LOG(WARNING) << "EVS HAL implementation is not available.";
}
}
+
+ WriteStringToFd(buffer, fd);
}
void Enumerator::cmdDumpDevice(int fd, const hidl_vec<hidl_string>& options) {
- bool dumpCameras = true;
- bool dumpDisplays = true;
- if (options.size() > 1) {
- const std::string option = options[1];
- const bool dumpAll = EqualsIgnoreCase(option, "all");
- dumpCameras = dumpAll || EqualsIgnoreCase(option, "camera");
- dumpDisplays = dumpAll || EqualsIgnoreCase(option, "display");
+ // Dumps both cameras and displays if the target device type is not given
+ bool dumpCameras = false;
+ bool dumpDisplays = false;
+ const auto numOptions = options.size();
+ if (numOptions > kOptionDumpDeviceTypeIndex) {
+ const std::string target = options[kOptionDumpDeviceTypeIndex];
+ dumpCameras = EqualsIgnoreCase(target, kDumpDeviceCamera);
+ dumpDisplays = EqualsIgnoreCase(target, kDumpDeviceDisplay);
if (!dumpCameras && !dumpDisplays) {
- dprintf(fd, "Unrecognized option, %s, is ignored.\n", option.c_str());
+ WriteStringToFd(StringPrintf("Unrecognized option, %s, is ignored.\n",
+ target.c_str()),
+ fd);
+ cmdHelp(fd);
+ return;
}
+ } else {
+ WriteStringToFd(StringPrintf("Necessary arguments are missing. "
+ "Please check the usages:\n"),
+ fd);
+ cmdHelp(fd);
+ return;
}
if (dumpCameras) {
- const bool dumpAllCameras = options.size() < 3;
- std::string deviceId = "";
- if (!dumpAllCameras) {
- deviceId = options[2];
+ // --dump camera [all|device_id] --[current|collected|custom] [args]
+ if (numOptions < kDumpCameraMinNumArgs) {
+ WriteStringToFd(StringPrintf("Necessary arguments are missing. "
+ "Please check the usages:\n"),
+ fd);
+ cmdHelp(fd);
+ return;
}
- for (auto& [id, ptr] : mActiveCameras) {
- if (!dumpAllCameras && !EqualsIgnoreCase(id, deviceId)) {
- continue;
- }
- ptr->dump(fd);
+ const std::string deviceId = options[kOptionDumpCameraTypeIndex];
+ auto target = mActiveCameras.find(deviceId);
+ const bool dumpAllCameras = EqualsIgnoreCase(deviceId,
+ kDumpOptionAll);
+ if (!dumpAllCameras && target == mActiveCameras.end()) {
+ // Unknown camera identifier
+ WriteStringToFd(StringPrintf("Given camera ID %s is unknown or not active.\n",
+ deviceId.c_str()),
+ fd);
+ return;
}
+
+ const std::string command = options[kOptionDumpCameraCommandIndex];
+ std::string cameraInfo;
+ if (EqualsIgnoreCase(command, kDumpCameraCommandCurrent)) {
+ // Active stream configuration from each active HalCamera objects
+ if (!dumpAllCameras) {
+ StringAppendF(&cameraInfo, "HalCamera: %s\n%s",
+ deviceId.c_str(),
+ target->second->toString(kSingleIndent).c_str());
+ } else {
+ for (auto&& [id, handle] : mActiveCameras) {
+ // Appends the current status
+ cameraInfo += handle->toString(kSingleIndent);
+ }
+ }
+ } else if (EqualsIgnoreCase(command, kDumpCameraCommandCollected)) {
+ // Reads the usage statistics from active HalCamera objects
+ std::unordered_map<std::string, std::string> usageStrings;
+ if (mMonitorEnabled) {
+ auto result = mClientsMonitor->toString(&usageStrings, kSingleIndent);
+ if (!result.ok()) {
+ LOG(ERROR) << "Failed to get the monitoring result";
+ return;
+ }
+
+ if (!dumpAllCameras) {
+ cameraInfo += usageStrings[deviceId];
+ } else {
+ for (auto&& [id, stats] : usageStrings) {
+ cameraInfo += stats;
+ }
+ }
+ } else {
+ WriteStringToFd(StringPrintf("Client monitor is not available.\n"),
+ fd);
+ return;
+ }
+ } else if (EqualsIgnoreCase(command, kDumpCameraCommandCustom)) {
+ // Additional arguments are expected for this command:
+ // --dump camera device_id --custom start [interval] [duration]
+ // or, --dump camera device_id --custom stop
+ if (numOptions < kDumpCameraMinNumArgs + 1) {
+ WriteStringToFd(StringPrintf("Necessary arguments are missing. "
+ "Please check the usages:\n"),
+ fd);
+ cmdHelp(fd);
+ return;
+ }
+
+ if (!mMonitorEnabled) {
+ WriteStringToFd(StringPrintf("Client monitor is not available."), fd);
+ return;
+ }
+
+ const std::string subcommand = options[kOptionDumpCameraArgsStartIndex];
+ if (EqualsIgnoreCase(subcommand, kDumpCameraCommandCustomStart)) {
+ using std::chrono::nanoseconds;
+ using std::chrono::milliseconds;
+ using std::chrono::duration_cast;
+ nanoseconds interval = 0ns;
+ nanoseconds duration = 0ns;
+ if (numOptions > kOptionDumpCameraArgsStartIndex + 2) {
+ duration = duration_cast<nanoseconds>(
+ milliseconds(
+ std::stoi(options[kOptionDumpCameraArgsStartIndex + 2])
+ ));
+ }
+
+ if (numOptions > kOptionDumpCameraArgsStartIndex + 1) {
+ interval = duration_cast<nanoseconds>(
+ milliseconds(
+ std::stoi(options[kOptionDumpCameraArgsStartIndex + 1])
+ ));
+ }
+
+ // Starts a custom collection
+ auto result = mClientsMonitor->startCustomCollection(interval, duration);
+ if (!result) {
+ LOG(ERROR) << "Failed to start a custom collection. "
+ << result.error();
+ StringAppendF(&cameraInfo, "Failed to start a custom collection. %s\n",
+ result.error().message().c_str());
+ }
+ } else if (EqualsIgnoreCase(subcommand, kDumpCameraCommandCustomStop)) {
+ if (!mMonitorEnabled) {
+ WriteStringToFd(StringPrintf("Client monitor is not available."), fd);
+ return;
+ }
+
+ auto result = mClientsMonitor->stopCustomCollection(deviceId);
+ if (!result) {
+ LOG(ERROR) << "Failed to stop a custom collection. "
+ << result.error();
+ StringAppendF(&cameraInfo, "Failed to stop a custom collection. %s\n",
+ result.error().message().c_str());
+ } else {
+ // Pull the custom collection
+ cameraInfo += *result;
+ }
+ } else {
+ WriteStringToFd(StringPrintf("Unknown argument: %s\n",
+ subcommand.c_str()),
+ fd);
+ cmdHelp(fd);
+ return;
+ }
+ } else {
+ WriteStringToFd(StringPrintf("Unknown command: %s\n"
+ "Please check the usages:\n", command.c_str()),
+ fd);
+ cmdHelp(fd);
+ return;
+ }
+
+ // Outputs the report
+ WriteStringToFd(cameraInfo, fd);
}
if (dumpDisplays) {
- dprintf(fd, "Not implemented yet\n");
+ HalDisplay* pDisplay =
+ reinterpret_cast<HalDisplay*>(mActiveDisplay.promote().get());
+ if (!pDisplay) {
+ WriteStringToFd("No active display is found.\n", fd);
+ } else {
+ WriteStringToFd(pDisplay->toString(kSingleIndent), fd);
+ }
}
}
diff --git a/evs/manager/1.1/Enumerator.h b/evs/manager/1.1/Enumerator.h
index f23871e..7708295 100644
--- a/evs/manager/1.1/Enumerator.h
+++ b/evs/manager/1.1/Enumerator.h
@@ -17,13 +17,14 @@
#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
#define ANDROID_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
+#include "HalCamera.h"
+#include "VirtualCamera.h"
+#include "stats/StatsCollector.h"
+
#include <list>
#include <unordered_map>
#include <unordered_set>
-#include "HalCamera.h"
-#include "VirtualCamera.h"
-
#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
#include <android/hardware/camera/device/3.2/ICameraDevice.h>
@@ -93,9 +94,18 @@
std::unordered_map<std::string,
CameraDesc> mCameraDevices;
+ // List of available physical display devices
+ std::list<uint8_t> mDisplayPorts;
+
// Display port the internal display is connected to.
uint8_t mInternalDisplayPort;
+ // Collecting camera usage statistics from clients
+ sp<StatsCollector> mClientsMonitor;
+
+ // Boolean flag to tell whether the camera usages are being monitored or not
+ bool mMonitorEnabled;
+
// LSHAL dump
void cmdDump(int fd, const hidl_vec<hidl_string>& options);
void cmdHelp(int fd);
diff --git a/evs/manager/1.1/HalCamera.cpp b/evs/manager/1.1/HalCamera.cpp
index 4b5feae..a90e83b 100644
--- a/evs/manager/1.1/HalCamera.cpp
+++ b/evs/manager/1.1/HalCamera.cpp
@@ -14,14 +14,13 @@
* limitations under the License.
*/
+#include "Enumerator.h"
#include "HalCamera.h"
#include "VirtualCamera.h"
-#include "Enumerator.h"
+#include <android-base/file.h>
#include <android-base/logging.h>
-#include <ui/GraphicBufferAllocator.h>
-#include <ui/GraphicBufferMapper.h>
-
+#include <android-base/strings.h>
namespace android {
namespace automotive {
@@ -33,6 +32,15 @@
// TODO(changyeon):
// We need to hook up death monitoring to detect stream death so we can attempt a reconnect
+using ::android::base::StringAppendF;
+using ::android::base::WriteStringToFd;
+
+HalCamera::~HalCamera() {
+ // Reports the usage statistics before the destruction
+ // EvsUsageStatsReported atom is defined in
+ // frameworks/base/cmds/statsd/src/atoms.proto
+ mUsageStats->writeStats();
+}
sp<VirtualCamera> HalCamera::makeVirtualCamera() {
@@ -313,6 +321,9 @@
if (mFrames[i].refCount <= 0) {
// Since all our clients are done with this buffer, return it to the device layer
mHwCamera->doneWithFrame(buffer);
+
+ // Counts a returned buffer
+ mUsageStats->framesReturned();
}
}
@@ -339,6 +350,9 @@
returnedBuffers.resize(1);
returnedBuffers[0] = buffer;
mHwCamera->doneWithFrame_1_1(returnedBuffers);
+
+ // Counts a returned buffer
+ mUsageStats->framesReturned();
}
}
@@ -355,6 +369,10 @@
LOG(INFO) << "A delivered frame from EVS v1.0 HW module is rejected.";
mHwCamera->doneWithFrame(buffer);
+ // Reports a received and returned buffer
+ mUsageStats->framesReceived();
+ mUsageStats->framesReturned();
+
return Void();
}
@@ -381,7 +399,9 @@
// Skip current frame because it arrives too soon.
LOG(DEBUG) << "Skips a frame from " << getId();
mNextRequests->push_back(req);
- ++mSyncFrames;
+
+ // Reports a skipped frame
+ mUsageStats->framesSkippedToSync();
} else if (vCam != nullptr && vCam->deliverFrame(buffer[0])) {
// Forward a frame and move a timeline.
LOG(DEBUG) << getId() << " forwarded the buffer #" << buffer[0].bufferId;
@@ -390,7 +410,9 @@
}
}
}
- ++mFramesReceived;
+
+ // Reports the number of received buffers
+ mUsageStats->framesReceived(buffer.size());
// Frames are being forwarded to active v1.0 clients and v1.1 clients if we
// failed to create a timeline.
@@ -412,8 +434,10 @@
// right away.
LOG(INFO) << "Trivially rejecting frame (" << buffer[0].bufferId
<< ") from " << getId() << " with no acceptance";
- ++mFramesNotUsed;
mHwCamera->doneWithFrame_1_1(buffer);
+
+ // Reports a returned buffer
+ mUsageStats->framesReturned();
} else {
// Add an entry for this frame in our tracking list.
unsigned i;
@@ -562,47 +586,68 @@
}
-void HalCamera::dump(int fd) const {
- dprintf(fd, "HalCamera: %s\n", mId.c_str());
- const auto timeElapsedNano = android::elapsedRealtimeNano() - mTimeCreated;
- dprintf(fd, "\tCreated: %ld (elapsed %ld ns)\n", (long)mTimeCreated, (long)timeElapsedNano);
- dprintf(fd, "\tFrames received: %lu (%f fps)\n",
- (unsigned long)mFramesReceived,
- (double)mFramesReceived / timeElapsedNano * 1e+9);
- dprintf(fd, "\tFrames not used: %lu\n", (unsigned long)mFramesNotUsed);
- dprintf(fd, "\tFrames skipped to sync: %lu\n", (unsigned long)mSyncFrames);
- dprintf(fd, "\tActive Stream Configuration:\n");
- dprintf(fd, "\t\tid: %d\n", mStreamConfig.id);
- dprintf(fd, "\t\twidth: %d\n", mStreamConfig.width);
- dprintf(fd, "\t\theight: %d\n", mStreamConfig.height);
- dprintf(fd, "\t\tformat: %d\n", mStreamConfig.width);
- dprintf(fd, "\t\tusage: 0x%lX\n", (unsigned long)mStreamConfig.usage);
- dprintf(fd, "\t\trotation: 0x%X\n", mStreamConfig.rotation);
+CameraUsageStatsRecord HalCamera::getStats() const {
+ return mUsageStats->snapshot();
+}
- dprintf(fd, "\tActive clients:\n");
+
+Stream HalCamera::getStreamConfiguration() const {
+ return mStreamConfig;
+}
+
+
+std::string HalCamera::toString(const char* indent) const {
+ std::string buffer;
+
+ const auto timeElapsedMs = android::uptimeMillis() - mTimeCreatedMs;
+ StringAppendF(&buffer, "%sCreated: @%" PRId64 " (elapsed %" PRId64 " ms)\n",
+ indent, mTimeCreatedMs, timeElapsedMs);
+
+ std::string double_indent(indent);
+ double_indent += indent;
+ buffer += CameraUsageStats::toString(getStats(), double_indent.c_str());
for (auto&& client : mClients) {
auto handle = client.promote();
if (!handle) {
continue;
}
- dprintf(fd, "\t\tClient %p\n", handle.get());
- handle->dump(fd, "\t\t\t");
- {
- std::scoped_lock<std::mutex> lock(mFrameMutex);
- dprintf(fd, "\t\t\tUse a fence-based delivery: %s\n",
- mTimelines.find((uint64_t)handle.get()) != mTimelines.end() ? "T" : "F");
- }
+ StringAppendF(&buffer, "%sClient %p\n",
+ indent, handle.get());
+ buffer += handle->toString(double_indent.c_str());
}
- dprintf(fd, "\tMaster client: %p\n", mMaster.promote().get());
- dprintf(fd, "\tSynchronization support: %s\n", mSyncSupported ? "T" : "F");
+ StringAppendF(&buffer, "%sMaster client: %p\n"
+ "%sSynchronization support: %s\n",
+ indent, mMaster.promote().get(),
+ indent, mSyncSupported ? "T":"F");
+
+ buffer += HalCamera::toString(mStreamConfig, indent);
+
+ return buffer;
}
-double HalCamera::getFramerate() const {
- const auto timeElapsed = android::elapsedRealtimeNano() - mTimeCreated;
- return static_cast<double>(mFramesReceived) / timeElapsed;
+std::string HalCamera::toString(Stream configuration, const char* indent) {
+ std::string streamInfo;
+ std::string double_indent(indent);
+ double_indent += indent;
+ StringAppendF(&streamInfo, "%sActive Stream Configuration\n"
+ "%sid: %d\n"
+ "%swidth: %d\n"
+ "%sheight: %d\n"
+ "%sformat: 0x%X\n"
+ "%susage: 0x%" PRIx64 "\n"
+ "%srotation: 0x%X\n\n",
+ indent,
+ double_indent.c_str(), configuration.id,
+ double_indent.c_str(), configuration.width,
+ double_indent.c_str(), configuration.height,
+ double_indent.c_str(), configuration.format,
+ double_indent.c_str(), configuration.usage,
+ double_indent.c_str(), configuration.rotation);
+
+ return streamInfo;
}
diff --git a/evs/manager/1.1/HalCamera.h b/evs/manager/1.1/HalCamera.h
index 88f8592..7e010a6 100644
--- a/evs/manager/1.1/HalCamera.h
+++ b/evs/manager/1.1/HalCamera.h
@@ -17,21 +17,22 @@
#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_HALCAMERA_H
#define ANDROID_AUTOMOTIVE_EVS_V1_1_HALCAMERA_H
-#include <android/hardware/automotive/evs/1.1/types.h>
-#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
-#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
-#include <ui/GraphicBuffer.h>
-#include <utils/SystemClock.h>
-
-#include <thread>
-#include <list>
-#include <deque>
-#include <unordered_map>
-
+#include "stats/CameraUsageStats.h"
#include "sync/unique_fd.h"
#include "sync/unique_fence.h"
#include "sync/unique_timeline.h"
+#include <deque>
+#include <list>
+#include <thread>
+#include <unordered_map>
+
+#include <android/hardware/automotive/evs/1.1/types.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
+#include <utils/Mutex.h>
+#include <utils/SystemClock.h>
+
using namespace ::android::hardware::automotive::evs::V1_1;
using ::android::hardware::camera::device::V3_2::Stream;
using ::android::hardware::Return;
@@ -60,19 +61,22 @@
// stream from the hardware camera and distribute it to the associated VirtualCamera objects.
class HalCamera : public IEvsCameraStream_1_1 {
public:
- HalCamera(sp<IEvsCamera_1_1> hwCamera, std::string deviceId = "", Stream cfg = {})
+ HalCamera(sp<IEvsCamera_1_1> hwCamera,
+ std::string deviceId = "",
+ int32_t recordId = 0,
+ Stream cfg = {})
: mHwCamera(hwCamera),
mId(deviceId),
mStreamConfig(cfg),
mSyncSupported(UniqueTimeline::Supported()),
- mTimeCreated(android::elapsedRealtimeNano()),
- mFramesReceived(0),
- mFramesNotUsed(0),
- mSyncFrames(0) {
+ mTimeCreatedMs(android::uptimeMillis()),
+ mUsageStats(new CameraUsageStats(recordId)) {
mCurrentRequests = &mFrameRequests[0];
mNextRequests = &mFrameRequests[1];
}
+ virtual ~HalCamera();
+
// Factory methods for client VirtualCameras
sp<VirtualCamera> makeVirtualCamera();
bool ownVirtualCamera(sp<VirtualCamera> virtualCamera);
@@ -101,8 +105,17 @@
Return<EvsResult> getParameter(CameraParam id, int32_t& value);
bool isSyncSupported() const { return mSyncSupported; }
- void dump(int fd) const;
- double getFramerate() const;
+ // Returns a snapshot of collected usage statistics
+ CameraUsageStatsRecord getStats() const;
+
+ // Returns active stream configuration
+ Stream getStreamConfiguration() const;
+
+ // Returns a string showing the current status
+ std::string toString(const char* indent = "") const;
+
+ // Returns a string showing current stream configuration
+ static std::string toString(Stream configuration, const char* indent = "");
// Methods from ::android::hardware::automotive::evs::V1_0::IEvsCameraStream follow.
Return<void> deliverFrame(const BufferDesc_1_0& buffer) override;
@@ -145,11 +158,11 @@
std::unique_ptr<UniqueTimeline>> mTimelines GUARDED_BY(mFrameMutex);
bool mSyncSupported;
- // debugging information
- int64_t mTimeCreated;
- uint64_t mFramesReceived;
- uint64_t mFramesNotUsed;
- uint64_t mSyncFrames;
+ // Time this object was created
+ int64_t mTimeCreatedMs;
+
+ // usage statistics to collect
+ android::sp<CameraUsageStats> mUsageStats;
};
} // namespace implementation
diff --git a/evs/manager/1.1/HalDisplay.cpp b/evs/manager/1.1/HalDisplay.cpp
index 4f7b4fd..b1e1aa6 100644
--- a/evs/manager/1.1/HalDisplay.cpp
+++ b/evs/manager/1.1/HalDisplay.cpp
@@ -14,17 +14,26 @@
* limitations under the License.
*/
-#include <android-base/logging.h>
#include "HalDisplay.h"
+#include <inttypes.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <ui/DisplayConfig.h>
+#include <ui/DisplayState.h>
+
+using android::base::StringAppendF;
+
namespace android {
namespace automotive {
namespace evs {
namespace V1_1 {
namespace implementation {
-HalDisplay::HalDisplay(sp<IEvsDisplay_1_0> display) :
- mHwDisplay(display) {
+HalDisplay::HalDisplay(sp<IEvsDisplay_1_0> display, int32_t id) :
+ mHwDisplay(display),
+ mId(id) {
// nothing to do.
}
@@ -114,6 +123,38 @@
return Void();
}
+
+std::string HalDisplay::toString(const char* indent) {
+ std::string buffer;
+ android::DisplayConfig displayConfig;
+ android::ui::DisplayState displayState;
+
+ if (mId == std::numeric_limits<int32_t>::min()) {
+ // Display identifier has not set
+ StringAppendF(&buffer, "HalDisplay: Display port is unknown.\n");
+ } else {
+ StringAppendF(&buffer, "HalDisplay: Display port %" PRId32 "\n", mId);
+ }
+
+ getDisplayInfo_1_1([&](auto& config, auto& state) {
+ displayConfig =
+ *(reinterpret_cast<const android::DisplayConfig*>(config.data()));
+ displayState =
+ *(reinterpret_cast<const android::ui::DisplayState*>(state.data()));
+ });
+
+ StringAppendF(&buffer, "%sWidth: %" PRId32 "\n",
+ indent, displayConfig.resolution.getWidth());
+ StringAppendF(&buffer, "%sHeight: %" PRId32 "\n",
+ indent, displayConfig.resolution.getHeight());
+ StringAppendF(&buffer, "%sRefresh rate: %f\n",
+ indent, displayConfig.refreshRate);
+ StringAppendF(&buffer, "%sRotation: %" PRId32 "\n",
+ indent, static_cast<int32_t>(displayState.orientation));
+
+ return buffer;
+}
+
} // namespace implementation
} // namespace V1_1
} // namespace evs
diff --git a/evs/manager/1.1/HalDisplay.h b/evs/manager/1.1/HalDisplay.h
index de9690b..672ad55 100644
--- a/evs/manager/1.1/HalDisplay.h
+++ b/evs/manager/1.1/HalDisplay.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_DISPLAYPROXY_H
#define ANDROID_AUTOMOTIVE_EVS_V1_1_DISPLAYPROXY_H
+#include <limits>
+
#include <android/hardware/automotive/evs/1.1/types.h>
#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
@@ -42,7 +44,8 @@
// manager directly to use the IEvsDisplay object the driver provides.
class HalDisplay : public IEvsDisplay_1_1 {
public:
- explicit HalDisplay(sp<IEvsDisplay_1_0> display);
+ explicit HalDisplay(sp<IEvsDisplay_1_0> display,
+ int32_t port = std::numeric_limits<int32_t>::min());
virtual ~HalDisplay() override;
inline void shutdown();
@@ -58,8 +61,12 @@
// Methods from ::android::hardware::automotive::evs::V1_1::IEvsDisplay follow.
Return<void> getDisplayInfo_1_1(getDisplayInfo_1_1_cb _info_cb) override;
+ // Returns a string showing the current status
+ std::string toString(const char* indent = "");
+
private:
sp<IEvsDisplay_1_0> mHwDisplay; // The low level display interface that backs this proxy
+ int32_t mId; // Display identifier
};
} // namespace implementation
diff --git a/evs/manager/1.1/VirtualCamera.cpp b/evs/manager/1.1/VirtualCamera.cpp
index cfef6ea..8b94a15 100644
--- a/evs/manager/1.1/VirtualCamera.cpp
+++ b/evs/manager/1.1/VirtualCamera.cpp
@@ -18,10 +18,14 @@
#include "HalCamera.h"
#include "Enumerator.h"
+#include <android/hardware_buffer.h>
+#include <android-base/file.h>
#include <android-base/logging.h>
-#include <ui/GraphicBufferAllocator.h>
-#include <ui/GraphicBufferMapper.h>
+#include <android-base/stringprintf.h>
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+using ::android::base::WriteStringToFd;
using ::android::hardware::automotive::evs::V1_0::DisplayState;
@@ -894,15 +898,27 @@
}
-void VirtualCamera::dump(int fd, const char* prefix) const {
- dprintf(fd, "%sLogical camera device: %s\n",
- prefix, mHalCamera.size() > 1 ? "T" : "F");
- dprintf(fd, "%sFramesAllowed: %u\n", prefix, mFramesAllowed);
- dprintf(fd, "%sFrames in use:\n", prefix);
+std::string VirtualCamera::toString(const char* indent) const {
+ std::string buffer;
+ StringAppendF(&buffer, "%sLogical camera device: %s\n"
+ "%sFramesAllowed: %u\n"
+ "%sFrames in use:\n",
+ indent, mHalCamera.size() > 1 ? "T" : "F",
+ indent, mFramesAllowed,
+ indent);
+
+ std::string next_indent(indent);
+ next_indent += "\t";
for (auto&& [id, queue] : mFramesHeld) {
- dprintf(fd, "%s\t%s, %d\n", prefix, id.c_str(), (int)queue.size());
+ StringAppendF(&buffer, "%s%s: %d\n",
+ next_indent.c_str(),
+ id.c_str(),
+ static_cast<int>(queue.size()));
}
- dprintf(fd, "%sCurrent stream state: %d\n", prefix, mStreamState);
+ StringAppendF(&buffer, "%sCurrent stream state: %d\n",
+ indent, mStreamState);
+
+ return buffer;
}
diff --git a/evs/manager/1.1/VirtualCamera.h b/evs/manager/1.1/VirtualCamera.h
index 2a8eae9..76a8648 100644
--- a/evs/manager/1.1/VirtualCamera.h
+++ b/evs/manager/1.1/VirtualCamera.h
@@ -21,7 +21,6 @@
#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
-#include <ui/GraphicBuffer.h>
#include <thread>
#include <deque>
@@ -109,7 +108,7 @@
importExternalBuffers_cb _hidl_cb) override;
// Dump current status to a given file descriptor
- void dump(int fd, const char* prefix = "") const;
+ std::string toString(const char* indent = "") const;
private:
diff --git a/evs/manager/1.1/stats/CameraUsageStats.cpp b/evs/manager/1.1/stats/CameraUsageStats.cpp
new file mode 100644
index 0000000..11f8229
--- /dev/null
+++ b/evs/manager/1.1/stats/CameraUsageStats.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright 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.
+ */
+
+#include "CameraUsageStats.h"
+
+#include <statslog.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+using ::android::base::Result;
+using ::android::base::StringAppendF;
+
+
+void CameraUsageStats::framesReceived(int n) {
+ AutoMutex lock(mMutex);
+ mStats.framesReceived += n;
+}
+
+
+void CameraUsageStats::framesReturned(int n) {
+ AutoMutex lock(mMutex);
+ mStats.framesReturned += n;
+}
+
+
+void CameraUsageStats::framesIgnored(int n) {
+ AutoMutex lock(mMutex);
+ mStats.framesIgnored += n;
+}
+
+
+void CameraUsageStats::framesSkippedToSync(int n) {
+ AutoMutex lock(mMutex);
+ mStats.framesSkippedToSync += n;
+}
+
+
+void CameraUsageStats::eventsReceived() {
+ AutoMutex lock(mMutex);
+ ++mStats.erroneousEventsCount;
+}
+
+
+int64_t CameraUsageStats::getTimeCreated() const {
+ AutoMutex lock(mMutex);
+ return mTimeCreatedMs;
+}
+
+
+int64_t CameraUsageStats::getFramesReceived() const {
+ AutoMutex lock(mMutex);
+ return mStats.framesReceived;
+}
+
+
+int64_t CameraUsageStats::getFramesReturned() const {
+ AutoMutex lock(mMutex);
+ return mStats.framesReturned;
+}
+
+
+CameraUsageStatsRecord CameraUsageStats::snapshot() const {
+ AutoMutex lock(mMutex);
+ return mStats;
+}
+
+
+Result<void> CameraUsageStats::writeStats() const {
+ AutoMutex lock(mMutex);
+ const auto duration = android::uptimeMillis() - mTimeCreatedMs;
+ // TODO(b/156131016): calculates and reports frame roundtrip latencies
+ android::util::stats_write(android::util::EVS_USAGE_STATS_REPORTED,
+ mId,
+ mStats.peakClientsCount,
+ mStats.erroneousEventsCount,
+ mStats.framesFirstRoundtripLatency,
+ mStats.framesAvgRoundtripLatency,
+ mStats.framesPeakRoundtripLatency,
+ mStats.framesReceived,
+ mStats.framesIgnored,
+ mStats.framesSkippedToSync,
+ duration);
+ return {};
+}
+
+
+std::string CameraUsageStats::toString(const CameraUsageStatsRecord& record, const char* indent) {
+ return record.toString(indent);
+}
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
diff --git a/evs/manager/1.1/stats/CameraUsageStats.h b/evs/manager/1.1/stats/CameraUsageStats.h
new file mode 100644
index 0000000..4bc4ea8
--- /dev/null
+++ b/evs/manager/1.1/stats/CameraUsageStats.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAUSAGESTATS_H
+#define ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAUSAGESTATS_H
+
+#include <inttypes.h>
+
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+#include <utils/SystemClock.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+struct CameraUsageStatsRecord {
+public:
+ // Time a snapshot is generated
+ nsecs_t timestamp;
+
+ // Total number of frames received
+ int64_t framesReceived;
+
+ // Total number of frames returned to EVS HAL
+ int64_t framesReturned;
+
+ // Number of frames ignored because no clients are listening
+ int64_t framesIgnored;
+
+ // Number of frames skipped to synchronize camera frames
+ int64_t framesSkippedToSync;
+
+ // Roundtrip latency of the very first frame after the stream started.
+ int64_t framesFirstRoundtripLatency;
+
+ // Peak mFrame roundtrip latency
+ int64_t framesPeakRoundtripLatency;
+
+ // Average mFrame roundtrip latency
+ double framesAvgRoundtripLatency;
+
+ // Number of the erroneous streaming events
+ int32_t erroneousEventsCount;
+
+ // Peak number of active clients
+ int32_t peakClientsCount;
+
+ // Calculates a delta between two records
+ CameraUsageStatsRecord& operator-=(const CameraUsageStatsRecord& rhs) {
+ // Only calculates differences in the frame statistics
+ framesReceived = framesReceived - rhs.framesReceived;
+ framesReturned = framesReturned - rhs.framesReturned;
+ framesIgnored = framesIgnored - rhs.framesIgnored;
+ framesSkippedToSync = framesSkippedToSync - rhs.framesSkippedToSync;
+ erroneousEventsCount = erroneousEventsCount - rhs.erroneousEventsCount;
+
+ return *this;
+ }
+
+ friend CameraUsageStatsRecord operator-(CameraUsageStatsRecord lhs,
+ const CameraUsageStatsRecord& rhs) noexcept {
+ lhs -= rhs; // reuse compound assignment
+ return lhs;
+ }
+
+ // Constructs a string that shows collected statistics
+ std::string toString(const char* indent = "") const {
+ std::string buffer;
+ android::base::StringAppendF(&buffer,
+ "%sTime Collected: @%" PRId64 "ms\n"
+ "%sFrames Received: %" PRId64 "\n"
+ "%sFrames Returned: %" PRId64 "\n"
+ "%sFrames Ignored : %" PRId64 "\n"
+ "%sFrames Skipped To Sync: %" PRId64 "\n\n",
+ indent, ns2ms(timestamp),
+ indent, framesReceived,
+ indent, framesReturned,
+ indent, framesIgnored,
+ indent, framesSkippedToSync);
+
+ return buffer;
+ }
+};
+
+
+class CameraUsageStats : public RefBase {
+public:
+ CameraUsageStats(int32_t id)
+ : mMutex(Mutex()),
+ mId(id),
+ mTimeCreatedMs(android::uptimeMillis()),
+ mStats({}) {}
+
+private:
+ // Mutex to protect a collection record
+ mutable Mutex mMutex;
+
+ // Unique identifier
+ int32_t mId;
+
+ // Time this object was created
+ int64_t mTimeCreatedMs;
+
+ // Usage statistics to collect
+ CameraUsageStatsRecord mStats GUARDED_BY(mMutex);
+
+public:
+ void framesReceived(int n = 1) EXCLUDES(mMutex);
+ void framesReturned(int n = 1) EXCLUDES(mMutex);
+ void framesIgnored(int n = 1) EXCLUDES(mMutex);
+ void framesSkippedToSync(int n = 1) EXCLUDES(mMutex);
+ void eventsReceived() EXCLUDES(mMutex);
+ int64_t getTimeCreated() const EXCLUDES(mMutex);
+ int64_t getFramesReceived() const EXCLUDES(mMutex);
+ int64_t getFramesReturned() const EXCLUDES(mMutex);
+
+ // Returns the statistics collected so far
+ CameraUsageStatsRecord snapshot() const EXCLUDES(mMutex);
+
+ // Reports the usage statistics
+ android::base::Result<void> writeStats() const EXCLUDES(mMutex);
+
+ // Generates a string with current statistics
+ static std::string toString(const CameraUsageStatsRecord& record, const char* indent = "");
+};
+
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif // ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAUSAGESTATS_H
diff --git a/evs/manager/1.1/stats/LooperWrapper.cpp b/evs/manager/1.1/stats/LooperWrapper.cpp
new file mode 100644
index 0000000..041a723
--- /dev/null
+++ b/evs/manager/1.1/stats/LooperWrapper.cpp
@@ -0,0 +1,82 @@
+/**
+ * Copyright 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.
+ */
+
+#include "LooperWrapper.h"
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+using android::sp;
+
+void LooperWrapper::wake() {
+ if (mLooper == nullptr) {
+ LOG(WARNING) << __FUNCTION__ << ": Looper is invalid.";
+ return;
+ }
+
+ return mLooper->wake();
+}
+
+int LooperWrapper::pollAll(int timeoutMillis) {
+ if (mLooper == nullptr) {
+ LOG(WARNING) << __FUNCTION__ << ": Looper is invalid.";
+ return 0;
+ }
+
+ return mLooper->pollAll(timeoutMillis);
+}
+
+void LooperWrapper::sendMessage(const sp<MessageHandler>& handler,
+ const Message& message) {
+ if (mLooper == nullptr) {
+ LOG(WARNING) << __FUNCTION__ << ": Looper is invalid.";
+ return;
+ }
+
+ return mLooper->sendMessage(handler, message);
+}
+
+void LooperWrapper::sendMessageAtTime(nsecs_t uptime,
+ const sp<MessageHandler>& handler,
+ const Message& message) {
+ if (mLooper == nullptr) {
+ LOG(WARNING) << __FUNCTION__ << ": Looper is invalid.";
+ return;
+ }
+
+ return mLooper->sendMessageAtTime(uptime, handler, message);
+}
+
+void LooperWrapper::removeMessages(const sp<MessageHandler>& handler) {
+ if (mLooper == nullptr) {
+ LOG(WARNING) << __FUNCTION__ << ": Looper is invalid.";
+ return;
+ }
+
+ return mLooper->removeMessages(handler);
+}
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
diff --git a/evs/manager/1.1/stats/LooperWrapper.h b/evs/manager/1.1/stats/LooperWrapper.h
new file mode 100644
index 0000000..fb9ec12
--- /dev/null
+++ b/evs/manager/1.1/stats/LooperWrapper.h
@@ -0,0 +1,57 @@
+/**
+ * Copyright 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.
+ */
+
+#ifndef ANDROID_AUTMOTIVE_EVS_V1_1_EVSLOOPERWRAPPER_H_
+#define ANDROID_AUTMOTIVE_EVS_V1_1_EVSLOOPERWRAPPER_H_
+
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+// This class wraps around android::Looper methods. Please refer to
+// utils/Looper.h for the details.
+class LooperWrapper : public RefBase {
+public:
+ LooperWrapper() : mLooper(nullptr) {}
+ virtual ~LooperWrapper() {}
+
+ void setLooper(android::sp<Looper> looper) { mLooper = looper; }
+ void wake();
+ virtual nsecs_t now() { return systemTime(SYSTEM_TIME_MONOTONIC); }
+ virtual int pollAll(int timeoutMillis);
+ virtual void sendMessage(const android::sp<MessageHandler>& handler, const Message& message);
+ virtual void sendMessageAtTime(nsecs_t uptime, const android::sp<MessageHandler>& handler,
+ const Message& message);
+ virtual void removeMessages(const android::sp<MessageHandler>& handler);
+
+protected:
+ android::sp<Looper> mLooper;
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif // ANDROID_AUTMOTIVE_EVS_V1_1_EVSLOOPERWRAPPER_H_
+
diff --git a/evs/manager/1.1/stats/StatsCollector.cpp b/evs/manager/1.1/stats/StatsCollector.cpp
new file mode 100644
index 0000000..de85e5a
--- /dev/null
+++ b/evs/manager/1.1/stats/StatsCollector.cpp
@@ -0,0 +1,468 @@
+/*
+ * Copyright 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.
+ */
+
+#include "HalCamera.h"
+#include "StatsCollector.h"
+#include "VirtualCamera.h"
+
+#include <processgroup/sched_policy.h>
+#include <pthread.h>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <android-base/stringprintf.h>
+#include <utils/SystemClock.h>
+
+namespace {
+
+ const char* kSingleIndent = "\t";
+ const char* kDoubleIndent = "\t\t";
+ const char* kDumpAllDevices = "all";
+
+}
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+using android::base::Error;
+using android::base::EqualsIgnoreCase;
+using android::base::Result;
+using android::base::StringAppendF;
+using android::base::StringPrintf;
+using android::base::WriteStringToFd;
+using android::hardware::automotive::evs::V1_1::BufferDesc;
+
+namespace {
+
+const auto kPeriodicCollectionInterval = 10s;
+const auto kPeriodicCollectionCacheSize = 180;
+const auto kMinCollectionInterval = 1s;
+const auto kCustomCollectionMaxDuration = 30min;
+const auto kMaxDumpHistory = 10;
+
+}
+
+void StatsCollector::handleMessage(const Message& message) {
+ const auto received = static_cast<CollectionEvent>(message.what);
+ Result<void> ret;
+ switch (received) {
+ case CollectionEvent::PERIODIC:
+ ret = handleCollectionEvent(received, &mPeriodicCollectionInfo);
+ break;
+
+ case CollectionEvent::CUSTOM_START:
+ ret = handleCollectionEvent(received, &mCustomCollectionInfo);
+ break;
+
+ case CollectionEvent::CUSTOM_END: {
+ AutoMutex lock(mMutex);
+ if (mCurrentCollectionEvent != CollectionEvent::CUSTOM_START) {
+ LOG(WARNING) << "Ignoring a message to end custom collection "
+ << "as current collection is " << toString(mCurrentCollectionEvent);
+ return;
+ }
+
+ // Starts a periodic collection
+ mLooper->removeMessages(this);
+ mCurrentCollectionEvent = CollectionEvent::PERIODIC;
+ mPeriodicCollectionInfo.lastCollectionTime = mLooper->now();
+ mLooper->sendMessage(this, CollectionEvent::PERIODIC);
+ return;
+ }
+
+ default:
+ LOG(WARNING) << "Unknown event is received: " << received;
+ break;
+ }
+
+ if (!ret.ok()) {
+ Mutex::Autolock lock(mMutex);
+ LOG(ERROR) << "Terminating data collection: "
+ << ret.error();
+
+ mCurrentCollectionEvent = CollectionEvent::TERMINATED;
+ mLooper->removeMessages(this);
+ mLooper->wake();
+ }
+}
+
+
+Result<void> StatsCollector::handleCollectionEvent(CollectionEvent event,
+ CollectionInfo* info) {
+ AutoMutex lock(mMutex);
+ if (mCurrentCollectionEvent != event) {
+ LOG(WARNING) << "Skipping " << toString(event) << " collection event "
+ << "on collection event " << toString(mCurrentCollectionEvent);
+ return {};
+ }
+
+ if (info->maxCacheSize < 1) {
+ return Error() << "Maximum cache size must be greater than 0";
+ }
+
+ using std::chrono::duration_cast;
+ using std::chrono::seconds;
+ if (info->interval < kMinCollectionInterval) {
+ LOG(WARNING) << "Collection interval of "
+ << duration_cast<seconds>(info->interval).count()
+ << " seconds for " << toString(event)
+ << " collection cannot be shorter than "
+ << duration_cast<seconds>(kMinCollectionInterval).count()
+ << " seconds.";
+ info->interval = kMinCollectionInterval;
+ }
+
+ auto ret = collectLocked(info);
+ if (!ret) {
+ return Error() << toString(event) << " collection failed: "
+ << ret.error();
+ }
+
+ // Arms a message for next periodic collection
+ info->lastCollectionTime += info->interval.count();
+ mLooper->sendMessageAtTime(info->lastCollectionTime, this, event);
+ return {};
+}
+
+
+Result<void> StatsCollector::collectLocked(CollectionInfo* info) REQUIRES(mMutex) {
+ for (auto&& [id, ptr] : mClientsToMonitor) {
+ auto pClient = ptr.promote();
+ if (!pClient) {
+ LOG(DEBUG) << id << " seems not alive.";
+ continue;
+ }
+
+ // Pulls a snapshot and puts a timestamp
+ auto snapshot = pClient->getStats();
+ snapshot.timestamp = mLooper->now();
+
+ // Removes the oldest record if cache is full
+ if (info->records[id].history.size() > info->maxCacheSize) {
+ info->records[id].history.pop_front();
+ }
+
+ // Stores the latest record and the deltas
+ auto delta = snapshot - info->records[id].latest;
+ info->records[id].history.emplace_back(delta);
+ info->records[id].latest = snapshot;
+ }
+
+ return {};
+}
+
+
+Result<void> StatsCollector::startCollection() {
+ {
+ AutoMutex lock(mMutex);
+ if (mCurrentCollectionEvent != CollectionEvent::INIT ||
+ mCollectionThread.joinable()) {
+ return Error(INVALID_OPERATION)
+ << "Camera usages collection is already running.";
+ }
+
+ // Create the collection info w/ the default values
+ mPeriodicCollectionInfo = {
+ .interval = kPeriodicCollectionInterval,
+ .maxCacheSize = kPeriodicCollectionCacheSize,
+ .lastCollectionTime = 0,
+ };
+
+ }
+
+ // Starts a background worker thread
+ mCollectionThread = std::thread([&]() {
+ {
+ AutoMutex lock(mMutex);
+ if (mCurrentCollectionEvent != CollectionEvent::INIT) {
+ LOG(ERROR) << "Skipping the statistics collection because "
+ << "the current collection event is "
+ << toString(mCurrentCollectionEvent);
+ return;
+ }
+
+ // Staring with a periodic collection
+ mCurrentCollectionEvent = CollectionEvent::PERIODIC;
+ }
+
+ if (set_sched_policy(0, SP_BACKGROUND) != 0) {
+ PLOG(WARNING) << "Failed to set background scheduling prioirty";
+ }
+
+ auto ret = pthread_setname_np(pthread_self(), "EvsCameraUsageCollect");
+ if (ret != 0) {
+ PLOG(WARNING) << "Failed to name a collection thread";
+ }
+
+ // Sets a looper for the communication
+ mLooper->setLooper(Looper::prepare(/*opts=*/0));
+
+ // Starts collecting the usage statistics periodically
+ mLooper->sendMessage(this, CollectionEvent::PERIODIC);
+
+ // Polls the messages until the collection is stopped.
+ bool isActive = true;
+ while (isActive) {
+ mLooper->pollAll(/*timeoutMillis=*/-1);
+ {
+ AutoMutex lock(mMutex);
+ isActive = mCurrentCollectionEvent != CollectionEvent::TERMINATED;
+ }
+ }
+ });
+
+ return {};
+}
+
+
+Result<void> StatsCollector::stopCollection() {
+ {
+ AutoMutex lock(mMutex);
+ if (mCurrentCollectionEvent == CollectionEvent::TERMINATED) {
+ LOG(WARNING) << "Camera usage data collection was stopped already.";
+ return {};
+ }
+
+ LOG(INFO) << "Stopping a camera usage data collection";
+ mCurrentCollectionEvent = CollectionEvent::TERMINATED;
+ }
+
+ // Join a background thread
+ if (mCollectionThread.joinable()) {
+ mLooper->removeMessages(this);
+ mLooper->wake();
+ mCollectionThread.join();
+ }
+
+ return {};
+}
+
+
+Result<void> StatsCollector::startCustomCollection(
+ std::chrono::nanoseconds interval,
+ std::chrono::nanoseconds maxDuration) {
+ using std::chrono::duration_cast;
+ using std::chrono::milliseconds;
+ if (interval < kMinCollectionInterval || maxDuration < kMinCollectionInterval) {
+ return Error(INVALID_OPERATION)
+ << "Collection interval and maximum maxDuration must be >= "
+ << duration_cast<milliseconds>(kMinCollectionInterval).count()
+ << " milliseconds.";
+ }
+
+ if (maxDuration > kCustomCollectionMaxDuration) {
+ return Error(INVALID_OPERATION)
+ << "Collection maximum maxDuration must be less than "
+ << duration_cast<milliseconds>(kCustomCollectionMaxDuration).count()
+ << " milliseconds.";
+ }
+
+ {
+ AutoMutex lock(mMutex);
+ if (mCurrentCollectionEvent != CollectionEvent::PERIODIC) {
+ return Error(INVALID_OPERATION)
+ << "Cannot start a custom collection when "
+ << "the current collection event " << toString(mCurrentCollectionEvent)
+ << " != " << toString(CollectionEvent::PERIODIC) << " collection event";
+ }
+
+ // Notifies the user if a preview custom collection result is
+ // not used yet.
+ if (mCustomCollectionInfo.records.size() > 0) {
+ LOG(WARNING) << "Previous custom collection result, which was done at "
+ << mCustomCollectionInfo.lastCollectionTime
+ << " has not pulled yet will be overwritten.";
+ }
+
+ // Programs custom collection configurations
+ mCustomCollectionInfo = {
+ .interval = interval,
+ .maxCacheSize = std::numeric_limits<std::size_t>::max(),
+ .lastCollectionTime = mLooper->now(),
+ .records = {},
+ };
+
+ mLooper->removeMessages(this);
+ nsecs_t uptime = mLooper->now() + maxDuration.count();
+ mLooper->sendMessageAtTime(uptime, this, CollectionEvent::CUSTOM_END);
+ mCurrentCollectionEvent = CollectionEvent::CUSTOM_START;
+ mLooper->sendMessage(this, CollectionEvent::CUSTOM_START);
+ }
+
+ return {};
+}
+
+
+Result<std::string> StatsCollector::stopCustomCollection(std::string targetId) {
+ Mutex::Autolock lock(mMutex);
+ if (mCurrentCollectionEvent == CollectionEvent::CUSTOM_START) {
+ // Stops a running custom collection
+ mLooper->removeMessages(this);
+ mLooper->sendMessage(this, CollectionEvent::CUSTOM_END);
+ }
+
+ auto ret = collectLocked(&mCustomCollectionInfo);
+ if (!ret) {
+ return Error() << toString(mCurrentCollectionEvent) << " collection failed: "
+ << ret.error();
+ }
+
+ // Prints out the all collected statistics
+ std::string buffer;
+ using std::chrono::duration_cast;
+ using std::chrono::seconds;
+ const intmax_t interval =
+ duration_cast<seconds>(mCustomCollectionInfo.interval).count();
+ if (EqualsIgnoreCase(targetId, kDumpAllDevices)) {
+ for (auto& [id, records] : mCustomCollectionInfo.records) {
+
+ StringAppendF(&buffer, "%s\n"
+ "%sNumber of collections: %zu\n"
+ "%sCollection interval: %" PRIdMAX " secs\n",
+ id.c_str(),
+ kSingleIndent, records.history.size(),
+ kSingleIndent, interval);
+ auto it = records.history.rbegin();
+ while (it != records.history.rend()) {
+ buffer += it++->toString(kDoubleIndent);
+ }
+ }
+
+ // Clears the collection
+ mCustomCollectionInfo = {};
+ } else {
+ auto it = mCustomCollectionInfo.records.find(targetId);
+ if (it != mCustomCollectionInfo.records.end()) {
+ StringAppendF(&buffer, "%s\n"
+ "%sNumber of collections: %zu\n"
+ "%sCollection interval: %" PRIdMAX " secs\n",
+ targetId.c_str(),
+ kSingleIndent, it->second.history.size(),
+ kSingleIndent, interval);
+ auto recordIter = it->second.history.rbegin();
+ while (recordIter != it->second.history.rend()) {
+ buffer += recordIter++->toString(kDoubleIndent);
+ }
+
+ // Clears the collection
+ mCustomCollectionInfo = {};
+ } else {
+ // Keeps the collection as the users may want to execute a command
+ // again with a right device id
+ StringAppendF(&buffer, "%s has not been monitored.", targetId.c_str());
+ }
+ }
+
+ return buffer;
+}
+
+
+Result<void> StatsCollector::registerClientToMonitor(android::sp<HalCamera>& camera) {
+ if (!camera) {
+ return Error(BAD_VALUE) << "Given camera client is invalid";
+ }
+
+ AutoMutex lock(mMutex);
+ const auto id = camera->getId();
+ if (mClientsToMonitor.find(id) != mClientsToMonitor.end()) {
+ LOG(WARNING) << id << " is already registered.";
+ } else {
+ mClientsToMonitor.insert_or_assign(id, camera);
+ }
+
+ return {};
+}
+
+
+Result<void> StatsCollector::unregisterClientToMonitor(const std::string& id) {
+ AutoMutex lock(mMutex);
+ auto entry = mClientsToMonitor.find(id);
+ if (entry != mClientsToMonitor.end()) {
+ mClientsToMonitor.erase(entry);
+ } else {
+ LOG(WARNING) << id << " has not been registerd.";
+ }
+
+ return {};
+}
+
+
+std::string StatsCollector::toString(const CollectionEvent& event) const {
+ switch(event) {
+ case CollectionEvent::INIT:
+ return "CollectionEvent::INIT";
+ case CollectionEvent::PERIODIC:
+ return "CollectionEvent::PERIODIC";
+ case CollectionEvent::CUSTOM_START:
+ return "CollectionEvent::CUSTOM_START";
+ case CollectionEvent::CUSTOM_END:
+ return "CollectionEvent::CUSTOM_END";
+ case CollectionEvent::TERMINATED:
+ return "CollectionEvent::TERMINATED";
+
+ default:
+ return "Unknown";
+ }
+}
+
+
+Result<void> StatsCollector::toString(std::unordered_map<std::string, std::string>* usages,
+ const char* indent) EXCLUDES(mMutex) {
+ std::string double_indent(indent);
+ double_indent += indent;
+
+ {
+ AutoMutex lock(mMutex);
+ using std::chrono::duration_cast;
+ using std::chrono::seconds;
+ const intmax_t interval =
+ duration_cast<seconds>(mPeriodicCollectionInfo.interval).count();
+
+ for (auto&& [id, records] : mPeriodicCollectionInfo.records) {
+ std::string buffer;
+ StringAppendF(&buffer, "%s\n"
+ "%sNumber of collections: %zu\n"
+ "%sCollection interval: %" PRIdMAX "secs\n",
+ id.c_str(),
+ indent, records.history.size(),
+ indent, interval);
+
+ // Adding up to kMaxDumpHistory records
+ auto it = records.history.rbegin();
+ auto count = 0;
+ while (it != records.history.rend() && count < kMaxDumpHistory) {
+ buffer += it->toString(double_indent.c_str());
+ ++it;
+ ++count;
+ }
+
+ usages->insert_or_assign(id, std::move(buffer));
+ }
+ }
+
+ return {};
+}
+
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
diff --git a/evs/manager/1.1/stats/StatsCollector.h b/evs/manager/1.1/stats/StatsCollector.h
new file mode 100644
index 0000000..031b342
--- /dev/null
+++ b/evs/manager/1.1/stats/StatsCollector.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_EVSSTATSCOLLECTOR_H
+#define ANDROID_AUTOMOTIVE_EVS_V1_1_EVSSTATSCOLLECTOR_H
+
+#include "CameraUsageStats.h"
+#include "LooperWrapper.h"
+
+#include <deque>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+#include <android/hardware/automotive/evs/1.1/types.h>
+#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <utils/Mutex.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+class HalCamera; // From VirtualCamera.h
+
+enum CollectionEvent {
+ INIT = 0,
+ PERIODIC,
+ CUSTOM_START,
+ CUSTOM_END,
+ TERMINATED,
+
+ LAST_EVENT,
+};
+
+
+struct CollectionRecord {
+ // Latest statistics collection
+ CameraUsageStatsRecord latest = {};
+
+ // History of collected statistics records
+ std::deque<CameraUsageStatsRecord> history;
+};
+
+
+struct CollectionInfo {
+ // Collection interval between two subsequent collections
+ std::chrono::nanoseconds interval = 0ns;
+
+ // The maximum number of records this collection stores
+ size_t maxCacheSize = 0;
+
+ // Time when the latest collection was done
+ nsecs_t lastCollectionTime = 0;
+
+ // Collected statistics records per instances
+ std::unordered_map<std::string, CollectionRecord> records;
+};
+
+
+class StatsCollector : public MessageHandler {
+public:
+ explicit StatsCollector() :
+ mLooper(new LooperWrapper()),
+ mCurrentCollectionEvent(CollectionEvent::INIT),
+ mPeriodicCollectionInfo({}),
+ mCustomCollectionInfo({}) {}
+
+ virtual ~StatsCollector() { stopCollection(); }
+
+ // Starts collecting CameraUsageStats
+ android::base::Result<void> startCollection();
+
+ // Stops collecting the statistics
+ android::base::Result<void> stopCollection();
+
+ // Starts collecting CameraUsageStarts during a given duration at a given
+ // interval.
+ android::base::Result<void> startCustomCollection(
+ std::chrono::nanoseconds interval,
+ std::chrono::nanoseconds duration) EXCLUDES(mMutex);
+
+ // Stops current custom collection and shows the result from the device with
+ // a given unique id. If this is "all",all results
+ // will be returned.
+ android::base::Result<std::string> stopCustomCollection(
+ std::string id = "") EXCLUDES(mMutex);
+
+ // Registers HalCamera object to monitor
+ android::base::Result<void> registerClientToMonitor(
+ android::sp<HalCamera>& camera) EXCLUDES(mMutex);
+
+ // Unregister HalCamera object
+ android::base::Result<void> unregisterClientToMonitor(
+ const std::string& id) EXCLUDES(mMutex);
+
+ // Returns a string that contains the latest statistics pulled from
+ // currently active clients
+ android::base::Result<void> toString(
+ std::unordered_map<std::string, std::string>* usages,
+ const char* indent = "") EXCLUDES(mMutex);
+
+private:
+ // Mutex to protect records
+ mutable Mutex mMutex;
+
+ // Looper to message the collection thread
+ android::sp<LooperWrapper> mLooper;
+
+ // Background thread to pull stats from the clients
+ std::thread mCollectionThread;
+
+ // Current state of the monitor
+ CollectionEvent mCurrentCollectionEvent GUARDED_BY(mMutex);
+
+ // Periodic collection information
+ CollectionInfo mPeriodicCollectionInfo GUARDED_BY(mMutex);
+
+ // A collection during the custom period the user sets
+ CollectionInfo mCustomCollectionInfo GUARDED_BY(mMutex);
+
+ // A list of HalCamera objects to monitor
+ std::unordered_map<std::string,
+ android::wp<HalCamera>> mClientsToMonitor GUARDED_BY(mMutex);
+
+ // Handles the messages from the looper
+ void handleMessage(const Message& message) override;
+
+ // Handles each CollectionEvent
+ android::base::Result<void> handleCollectionEvent(
+ CollectionEvent event, CollectionInfo* info) EXCLUDES(mMutex);
+
+ // Pulls the statistics from each active HalCamera objects and generates the
+ // records
+ android::base::Result<void> collectLocked(CollectionInfo* info) REQUIRES(mMutex);
+
+ // Returns a string corresponding to a given collection event
+ std::string toString(const CollectionEvent& event) const;
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif // ANDROID_AUTOMOTIVE_EVS_V1_1_EVSSTATSCOLLECTOR_H
diff --git a/evs/sampleDriver/Android.bp b/evs/sampleDriver/Android.bp
index 1722200..1bee713 100644
--- a/evs/sampleDriver/Android.bp
+++ b/evs/sampleDriver/Android.bp
@@ -58,10 +58,6 @@
init_rc: ["android.hardware.automotive.evs@1.1-sample.rc"],
- strip: {
- keep_symbols: true,
- },
-
cflags: ["-DLOG_TAG=\"EvsSampleDriver\""] + [
"-DGL_GLEXT_PROTOTYPES",
"-DEGL_EGLEXT_PROTOTYPES",
diff --git a/evs/sampleDriver/EvsEnumerator.cpp b/evs/sampleDriver/EvsEnumerator.cpp
index e108007..ef6009e 100644
--- a/evs/sampleDriver/EvsEnumerator.cpp
+++ b/evs/sampleDriver/EvsEnumerator.cpp
@@ -534,6 +534,12 @@
}
// Create a new display interface and return it
+ if (sDisplayPortList.find(port) == sDisplayPortList.end()) {
+ LOG(ERROR) << "No display is available on the port "
+ << static_cast<int32_t>(port);
+ return nullptr;
+ }
+
pActiveDisplay = new EvsGlDisplay(sDisplayProxy, sDisplayPortList[port]);
sActiveDisplay = pActiveDisplay;
diff --git a/evs/sepolicy/evs_manager.te b/evs/sepolicy/evs_manager.te
index d3ef6db..cf649bb 100644
--- a/evs/sepolicy/evs_manager.te
+++ b/evs/sepolicy/evs_manager.te
@@ -13,3 +13,6 @@
# allow write to fd
allow evs_manager shell:fd use;
allow evs_manager shell:fifo_file write;
+
+# allow evs_manager to send information to statsd socket
+unix_socket_send(evs_manager, statsdw, statsd)
diff --git a/evs/support_library/Android.bp b/evs/support_library/Android.bp
index 6556404..8b77a5d 100644
--- a/evs/support_library/Android.bp
+++ b/evs/support_library/Android.bp
@@ -56,10 +56,6 @@
"camera_config.json",
],
- strip: {
- keep_symbols: true,
- },
-
cflags: ["-DLOG_TAG=\"libevssupport\""] + [
"-DGL_GLEXT_PROTOTYPES",
"-DEGL_EGLEXT_PROTOTYPES",
diff --git a/service/res/values-ne/strings.xml b/service/res/values-ne/strings.xml
index a03753b..df9f269 100644
--- a/service/res/values-ne/strings.xml
+++ b/service/res/values-ne/strings.xml
@@ -46,7 +46,7 @@
<string name="car_permission_label_audio_settings" msgid="6524703796944023977">"कारका अडियो सेटिङ व्यवस्थित गर्ने"</string>
<string name="car_permission_label_mock_vehicle_hal" msgid="7198852512207405935">"सवारी साधनको HAL को अनुकरण गर्ने"</string>
<string name="car_permission_label_receive_ducking" msgid="4884538660766756573">"अडियो डकिङ कार्यक्रमहरू प्राप्त गर्नुहोस्"</string>
- <string name="car_permission_desc_receive_ducking" msgid="776376388266656512">"कारमा अन्य अडियो प्ले भइरहेका हुनाले अनुप्रयोगको भोल्युम कम भइरहेको कुराबारे एपलाई सूचित हुन दिन्छ।"</string>
+ <string name="car_permission_desc_receive_ducking" msgid="776376388266656512">"कारमा अन्य अडियो प्ले भइरहेका हुनाले एपको भोल्युम कम भइरहेको कुराबारे एपलाई सूचित हुन दिन्छ।"</string>
<string name="car_permission_desc_mock_vehicle_hal" msgid="5235596491098649155">"आन्तरिक परीक्षण गर्ने प्रयोजनका लागि तपाईंको कारको सवारी साधन HAL को अनुकरण गर्ने।"</string>
<string name="car_permission_desc_audio_volume" msgid="536626185654307889">"तपाईंको कारको अडियोको भोल्युम नियन्त्रण गर्ने।"</string>
<string name="car_permission_desc_audio_settings" msgid="7192007170677915937">"आफ्नो कारको अडियोसम्बन्धी सेटिङहरू नियन्त्रण गर्नुहोस्।"</string>
diff --git a/service/src/com/android/car/CarBugreportManagerService.java b/service/src/com/android/car/CarBugreportManagerService.java
index 2480657..39f4461 100644
--- a/service/src/com/android/car/CarBugreportManagerService.java
+++ b/service/src/com/android/car/CarBugreportManagerService.java
@@ -30,6 +30,7 @@
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.ParcelFileDescriptor;
@@ -50,6 +51,7 @@
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Bugreport service for cars.
@@ -69,7 +71,11 @@
private static final String OK_PREFIX = "OK:";
private static final String FAIL_PREFIX = "FAIL:";
+ /**
+ * The services are defined in {@code packages/services/Car/car-bugreportd/car-bugreportd.rc}.
+ */
private static final String BUGREPORTD_SERVICE = "car-bugreportd";
+ private static final String DUMPSTATEZ_SERVICE = "car-dumpstatez";
// The socket definitions must match the actual socket names defined in car_bugreportd service
// definition.
@@ -86,7 +92,7 @@
private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
getClass().getSimpleName());
private final Handler mHandler = new Handler(mHandlerThread.getLooper());
- private boolean mIsServiceRunning;
+ private final AtomicBoolean mIsServiceRunning = new AtomicBoolean(false);
/**
* Create a CarBugreportManagerService instance.
@@ -111,65 +117,104 @@
@RequiresPermission(android.Manifest.permission.DUMP)
public void requestBugreport(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
ICarBugreportCallback callback) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.DUMP, "requestBugreport");
+ ensureTheCallerIsSignedWithPlatformKeys();
+ ensureTheCallerIsDesignatedBugReportApp();
+ synchronized (mLock) {
+ if (mIsServiceRunning.getAndSet(true)) {
+ Slog.w(TAG, "Bugreport Service already running");
+ reportError(callback, CarBugreportManagerCallback.CAR_BUGREPORT_IN_PROGRESS);
+ return;
+ }
+ requestBugReportLocked(output, extraOutput, callback);
+ }
+ }
- // Check the caller has proper permission
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
- "requestZippedBugreport");
- // Check the caller is signed with platform keys
+ @Override
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void cancelBugreport() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.DUMP, "cancelBugreport");
+ ensureTheCallerIsSignedWithPlatformKeys();
+ ensureTheCallerIsDesignatedBugReportApp();
+ synchronized (mLock) {
+ if (!mIsServiceRunning.getAndSet(false)) {
+ Slog.i(TAG, "Failed to cancel. Service is not running.");
+ return;
+ }
+ Slog.i(TAG, "Cancelling the running bugreport");
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
+ // This tells init to cancel the services. Note that this is achieved through
+ // setting a system property which is not thread-safe. So the lock here offers
+ // thread-safety only among callers of the API.
+ try {
+ SystemProperties.set("ctl.stop", BUGREPORTD_SERVICE);
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Failed to stop " + BUGREPORTD_SERVICE, e);
+ }
+ try {
+ // Stop DUMPSTATEZ_SERVICE service too, because stopping BUGREPORTD_SERVICE doesn't
+ // guarantee stopping DUMPSTATEZ_SERVICE.
+ SystemProperties.set("ctl.stop", DUMPSTATEZ_SERVICE);
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Failed to stop " + DUMPSTATEZ_SERVICE, e);
+ }
+ }
+ }
+
+ private void ensureTheCallerIsSignedWithPlatformKeys() {
PackageManager pm = mContext.getPackageManager();
int callingUid = Binder.getCallingUid();
if (pm.checkSignatures(Process.myUid(), callingUid) != PackageManager.SIGNATURE_MATCH) {
throw new SecurityException("Caller " + pm.getNameForUid(callingUid)
+ " does not have the right signature");
}
- // Check if the caller is the designated bugreport app
+ }
+
+ /** Checks only on user builds. */
+ private void ensureTheCallerIsDesignatedBugReportApp() {
+ if (Build.IS_DEBUGGABLE) {
+ // Per https://source.android.com/setup/develop/new-device, user builds are debuggable=0
+ return;
+ }
String defaultAppPkgName = mContext.getString(R.string.config_car_bugreport_application);
+ int callingUid = Binder.getCallingUid();
+ PackageManager pm = mContext.getPackageManager();
String[] packageNamesForCallerUid = pm.getPackagesForUid(callingUid);
- boolean found = false;
if (packageNamesForCallerUid != null) {
for (String packageName : packageNamesForCallerUid) {
if (defaultAppPkgName.equals(packageName)) {
- found = true;
- break;
+ return;
}
}
}
- if (!found) {
- throw new SecurityException("Caller " + pm.getNameForUid(callingUid)
- + " is not a designated bugreport app");
- }
-
- synchronized (mLock) {
- requestBugReportLocked(output, extraOutput, callback);
- }
+ throw new SecurityException("Caller " + pm.getNameForUid(callingUid)
+ + " is not a designated bugreport app");
}
@GuardedBy("mLock")
private void requestBugReportLocked(ParcelFileDescriptor output,
ParcelFileDescriptor extraOutput, ICarBugreportCallback callback) {
- if (mIsServiceRunning) {
- Slog.w(TAG, "Bugreport Service already running");
- reportError(callback, CarBugreportManagerCallback.CAR_BUGREPORT_IN_PROGRESS);
- return;
- }
- mIsServiceRunning = true;
- mHandler.post(() -> startBugreportd(output, extraOutput, callback));
- }
-
- private void startBugreportd(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
- ICarBugreportCallback callback) {
Slog.i(TAG, "Starting " + BUGREPORTD_SERVICE);
try {
+ // This tells init to start the service. Note that this is achieved through
+ // setting a system property which is not thread-safe. So the lock here offers
+ // thread-safety only among callers of the API.
SystemProperties.set("ctl.start", BUGREPORTD_SERVICE);
} catch (RuntimeException e) {
+ mIsServiceRunning.set(false);
Slog.e(TAG, "Failed to start " + BUGREPORTD_SERVICE, e);
reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
return;
}
- processBugreportSockets(output, extraOutput, callback);
- synchronized (mLock) {
- mIsServiceRunning = false;
- }
+ mHandler.post(() -> {
+ try {
+ processBugreportSockets(output, extraOutput, callback);
+ } finally {
+ mIsServiceRunning.set(false);
+ }
+ });
}
private void handleProgress(String line, ICarBugreportCallback callback) {
@@ -231,11 +276,10 @@
reportError(callback, CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED);
return;
}
-
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(localSocket.getInputStream()))) {
String line;
- while ((line = reader.readLine()) != null) {
+ while (mIsServiceRunning.get() && (line = reader.readLine()) != null) {
if (line.startsWith(PROGRESS_PREFIX)) {
handleProgress(line, callback);
} else if (line.startsWith(FAIL_PREFIX)) {
@@ -284,7 +328,7 @@
try {
callback.onError(errorCode);
} catch (RemoteException e) {
- Slog.e(TAG, "onError() failed: " + e.getMessage());
+ Slog.e(TAG, "onError() failed", e);
}
}
@@ -309,7 +353,17 @@
// Therefore we are generous in setting the timeout. Most cases should not even
// come close to the timeouts, but since bugreports are taken when there is a
// system issue, it is hard to guess.
- SystemClock.sleep(SOCKET_CONNECTION_RETRY_DELAY_IN_MS);
+ // The following lines waits for SOCKET_CONNECTION_RETRY_DELAY_IN_MS or until
+ // mIsServiceRunning becomes false.
+ for (int i = 0; i < SOCKET_CONNECTION_RETRY_DELAY_IN_MS / 50; i++) {
+ if (!mIsServiceRunning.get()) {
+ Slog.i(TAG, "Failed to connect to socket " + socketName
+ + ". The service is prematurely cancelled.");
+ return null;
+ }
+ SystemClock.sleep(50); // Millis.
+ }
+
try {
socket.connect(new LocalSocketAddress(socketName,
LocalSocketAddress.Namespace.RESERVED));
@@ -320,7 +374,7 @@
+ " after " + retryCount + " retries", e);
return null;
}
- Log.i(TAG, "Failed to connect to " + socketName + ". Will try again "
+ Log.i(TAG, "Failed to connect to " + socketName + ". Will try again. "
+ e.getMessage());
}
}
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index d130c43..aa84b59 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -16,6 +16,7 @@
package com.android.car;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.car.Car;
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
@@ -23,6 +24,7 @@
import android.car.hardware.power.ICarPowerStateListener;
import android.car.userlib.HalCallback;
import android.car.userlib.InitialUserSetter;
+import android.car.userlib.InitialUserSetter.InitialUserInfoType;
import android.car.userlib.UserHalHelper;
import android.car.userlib.UserHelper;
import android.content.ComponentName;
@@ -164,14 +166,13 @@
this(context, context.getResources(), powerHal, systemInterface, UserManager.get(context),
carUserService, new InitialUserSetter(context,
(u) -> carUserService.setInitialUser(u),
- context.getString(R.string.default_guest_name),
- !carUserService.isUserHalSupported()));
+ context.getString(R.string.default_guest_name)));
}
@VisibleForTesting
- CarPowerManagementService(Context context, Resources resources, PowerHalService powerHal,
- SystemInterface systemInterface, UserManager userManager,
- CarUserService carUserService, InitialUserSetter initialUserSetter) {
+ public CarPowerManagementService(Context context, Resources resources, PowerHalService powerHal,
+ SystemInterface systemInterface, UserManager userManager, CarUserService carUserService,
+ InitialUserSetter initialUserSetter) {
mContext = context;
mHal = powerHal;
mSystemInterface = systemInterface;
@@ -194,7 +195,7 @@
}
@VisibleForTesting
- protected void setShutdownTimersForTest(int pollingIntervalMs, int shutdownTimeoutMs) {
+ public void setShutdownTimersForTest(int pollingIntervalMs, int shutdownTimeoutMs) {
// Override timers to keep testing time short
// Passing in '0' resets the value to the default
synchronized (mLock) {
@@ -431,7 +432,13 @@
return;
}
- mInitialUserSetter.executeDefaultBehavior(/* replaceGuest= */ !mSwitchGuestUserBeforeSleep);
+ executeDefaultInitialUserBehavior(!mSwitchGuestUserBeforeSleep);
+ }
+
+ private void executeDefaultInitialUserBehavior(boolean replaceGuest) {
+ mInitialUserSetter.set(newInitialUserInfoBuilder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ .setReplaceGuest(replaceGuest)
+ .build());
}
/**
@@ -447,10 +454,20 @@
boolean replaceGuest = !mSwitchGuestUserBeforeSleep;
if (newUser == null) {
Log.w(TAG, "Failed to replace guest; falling back to default behavior");
- mInitialUserSetter.executeDefaultBehavior(replaceGuest);
+ executeDefaultInitialUserBehavior(replaceGuest);
return;
}
- mInitialUserSetter.switchUser(newUser.id, replaceGuest);
+ switchUser(newUser.id, replaceGuest);
+ }
+
+ private void switchUser(@UserIdInt int userId, boolean replaceGuest) {
+ mInitialUserSetter.set(newInitialUserInfoBuilder(InitialUserSetter.TYPE_SWITCH)
+ .setSwitchUserId(userId).setReplaceGuest(replaceGuest).build());
+ }
+
+ private InitialUserSetter.Builder newInitialUserInfoBuilder(@InitialUserInfoType int type) {
+ return new InitialUserSetter.Builder(type)
+ .setSupportsOverrideUserIdProperty(!mUserService.isUserHalSupported());
}
/**
@@ -479,14 +496,14 @@
switch (response.action) {
case InitialUserInfoResponseAction.DEFAULT:
Log.i(TAG, "HAL requested default initial user behavior");
- mInitialUserSetter.executeDefaultBehavior(replaceGuest);
+ executeDefaultInitialUserBehavior(replaceGuest);
return;
case InitialUserInfoResponseAction.SWITCH:
int userId = response.userToSwitchOrCreate.userId;
Log.i(TAG, "HAL requested switch to user " + userId);
// If guest was replaced on shutdown, it doesn't need to be replaced
// again
- mInitialUserSetter.switchUser(userId, replaceGuest);
+ switchUser(userId, replaceGuest);
return;
case InitialUserInfoResponseAction.CREATE:
int halFlags = response.userToSwitchOrCreate.flags;
@@ -494,7 +511,11 @@
Log.i(TAG, "HAL requested new user (name="
+ UserHelper.safeName(name) + ", flags="
+ UserHalHelper.userFlagsToString(halFlags) + ")");
- mInitialUserSetter.createUser(name, halFlags);
+ mInitialUserSetter
+ .set(newInitialUserInfoBuilder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName(name)
+ .setNewUserFlags(halFlags)
+ .build());
return;
default:
switchUserOnResumeUserHalFallback(
@@ -513,7 +534,7 @@
private void switchUserOnResumeUserHalFallback(String reason) {
Log.w(TAG, "Failed to set initial user based on User Hal (" + reason
+ "); falling back to default behavior");
- mInitialUserSetter.executeDefaultBehavior(/* replaceGuest= */ !mSwitchGuestUserBeforeSleep);
+ executeDefaultInitialUserBehavior(!mSwitchGuestUserBeforeSleep);
}
private void handleShutdownPrepare(CpmsState newState) {
diff --git a/service/src/com/android/car/CarPropertyService.java b/service/src/com/android/car/CarPropertyService.java
index 39a01e7..9f7bd2b 100644
--- a/service/src/com/android/car/CarPropertyService.java
+++ b/service/src/com/android/car/CarPropertyService.java
@@ -25,6 +25,8 @@
import android.car.hardware.property.ICarProperty;
import android.car.hardware.property.ICarPropertyEventListener;
import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -61,6 +63,10 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
private final SparseArray<SparseArray<Client>> mSetOperationClientMap = new SparseArray<>();
+ private final HandlerThread mHandlerThread =
+ CarServiceUtils.getHandlerThread(getClass().getSimpleName());
+ private final Handler mHandler = new Handler(mHandlerThread.getLooper());
+
public CarPropertyService(Context context, PropertyHalService hal) {
if (DBG) {
Log.d(TAG, "CarPropertyService started!");
@@ -169,21 +175,24 @@
if (DBG) {
Log.d(TAG, "registerListener: propId=0x" + toHexString(propId) + " rate=" + rate);
}
- if (mConfigs.get(propId) == null) {
- // Do not attempt to register an invalid propId
- Log.e(TAG, "registerListener: propId is not in config list: 0x" + toHexString(propId));
- return;
- }
- ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId));
if (listener == null) {
Log.e(TAG, "registerListener: Listener is null.");
throw new IllegalArgumentException("listener cannot be null.");
}
IBinder listenerBinder = listener.asBinder();
-
+ CarPropertyConfig propertyConfig;
+ Client finalClient;
synchronized (mLock) {
- // Get the client for this listener
+ propertyConfig = mConfigs.get(propId);
+ if (propertyConfig == null) {
+ // Do not attempt to register an invalid propId
+ Log.e(TAG, "registerListener: propId is not in config list: 0x" + toHexString(
+ propId));
+ return;
+ }
+ ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId));
+ // Get or create the client for this listener
Client client = mClientMap.get(listenerBinder);
if (client == null) {
client = new Client(listener);
@@ -206,29 +215,36 @@
if (rate > mHal.getSampleRate(propId)) {
mHal.subscribeProperty(propId, rate);
}
+ finalClient = client;
}
- // Send the latest value(s) to the registering listener only
- List<CarPropertyEvent> events = new LinkedList<CarPropertyEvent>();
- if (mConfigs.get(propId).isGlobalProperty()) {
+
+ // propertyConfig and client are NonNull.
+ mHandler.post(() ->
+ getAndDispatchPropertyInitValue(propertyConfig, finalClient));
+ }
+
+ private void getAndDispatchPropertyInitValue(CarPropertyConfig config, Client client) {
+ List<CarPropertyEvent> events = new LinkedList<>();
+ int propId = config.getPropertyId();
+ if (config.isGlobalProperty()) {
CarPropertyValue value = mHal.getProperty(propId, 0);
- // CarPropertyEvent without a CarPropertyValue can not be used by any listeners.
if (value != null) {
CarPropertyEvent event = new CarPropertyEvent(
- CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
+ CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
events.add(event);
}
} else {
- for (int areaId : mConfigs.get(propId).getAreaIds()) {
+ for (int areaId : config.getAreaIds()) {
CarPropertyValue value = mHal.getProperty(propId, areaId);
if (value != null) {
CarPropertyEvent event = new CarPropertyEvent(
- CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
+ CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
events.add(event);
}
}
}
try {
- listener.onEvent(events);
+ client.getListener().onEvent(events);
} catch (RemoteException ex) {
// If we cannot send a record, its likely the connection snapped. Let the binder
// death handle the situation.
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 871e18c..3a91aec 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.UiModeManager;
@@ -33,16 +34,18 @@
import android.car.input.CarInputManager;
import android.car.input.RotaryEvent;
import android.car.user.CarUserManager;
-import android.car.user.GetUserIdentificationAssociationResponse;
+import android.car.user.UserIdentificationAssociationResponse;
import android.car.user.UserSwitchResult;
import android.car.userlib.HalCallback;
import android.car.userlib.UserHalHelper;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
+import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
@@ -286,8 +289,11 @@
pw.println("\t Print this help text.");
pw.println("\tday-night-mode [day|night|sensor]");
pw.println("\t Force into day/night mode or restore to auto.");
- pw.println("\tinject-vhal-event property [zone] data(can be comma separated list)");
+ pw.println("\tinject-vhal-event property [zone] data(can be comma separated list) "
+ + "[-t delay_time_seconds]");
pw.println("\t Inject a vehicle property for testing.");
+ pw.println("\t delay_time_seconds: the event timestamp is increased by certain second.");
+ pw.println("\t If not specified, it will be 0.");
pw.println("\tinject-error-event property zone errorCode");
pw.println("\t Inject an error event from VHAL for testing.");
pw.println("\tenable-uxr true|false");
@@ -345,12 +351,14 @@
pw.println("\t delta_times_ms: a list of delta time (current time minus event time)");
pw.println("\t in descending order. If not specified, it will be 0.");
- pw.printf("\t%s <REQ_TYPE> [--timeout TIMEOUT_MS]\n", COMMAND_GET_INITIAL_USER_INFO);
+ pw.printf("\t%s <REQ_TYPE> [--hal-only] [--timeout TIMEOUT_MS]\n",
+ COMMAND_GET_INITIAL_USER_INFO);
pw.println("\t Calls the Vehicle HAL to get the initial boot info, passing the given");
pw.println("\t REQ_TYPE (which could be either FIRST_BOOT, FIRST_BOOT_AFTER_OTA, ");
pw.println("\t COLD_BOOT, RESUME, or any numeric value that would be passed 'as-is')");
pw.println("\t and an optional TIMEOUT_MS to wait for the HAL response (if not set,");
pw.println("\t it will use a default value).");
+ pw.println("\t The --hal-only option only calls HAL, without using CarUserService.");
pw.printf("\t%s <USER_ID> [--hal-only] [--timeout TIMEOUT_MS]\n", COMMAND_SWITCH_USER);
pw.println("\t Switches to user USER_ID using the HAL integration.");
@@ -439,9 +447,12 @@
case COMMAND_INJECT_VHAL_EVENT:
String zone = PARAM_VEHICLE_PROPERTY_AREA_GLOBAL;
String data;
- if (args.length != 3 && args.length != 4) {
+ int argNum = args.length;
+ if (argNum < 3 || argNum > 6) {
return showInvalidArguments(writer);
- } else if (args.length == 4) {
+ }
+ String delayTime = args[argNum - 2].equals("-t") ? args[argNum - 1] : "0";
+ if (argNum == 4 || argNum == 6) {
// Zoned
zone = args[2];
data = args[3];
@@ -449,7 +460,7 @@
// Global
data = args[2];
}
- injectVhalEvent(args[1], zone, data, false, writer);
+ injectVhalEvent(args[1], zone, data, false, delayTime, writer);
break;
case COMMAND_INJECT_ERROR_EVENT:
if (args.length != 4) {
@@ -457,7 +468,7 @@
}
String errorAreaId = args[2];
String errorCode = args[3];
- injectVhalEvent(args[1], errorAreaId, errorCode, true, writer);
+ injectVhalEvent(args[1], errorAreaId, errorCode, true, "0", writer);
break;
case COMMAND_ENABLE_UXR:
if (args.length != 2) {
@@ -814,6 +825,7 @@
// Gets the request type
String typeArg = args[1];
int requestType = UserHalHelper.parseInitialUserInfoRequestType(typeArg);
+ boolean halOnly = false;
int timeout = DEFAULT_HAL_TIMEOUT_MS;
for (int i = 2; i < args.length; i++) {
@@ -822,6 +834,9 @@
case "--timeout":
timeout = Integer.parseInt(args[++i]);
break;
+ case "--hal-only":
+ halOnly = true;
+ break;
default:
writer.println("Invalid option at index " + i + ": " + arg);
return;
@@ -832,12 +847,8 @@
Log.d(TAG, "handleGetInitialUserInfo(): type=" + requestType + " (" + typeArg
+ "), timeout=" + timeout);
- UserHalService userHal = mHal.getUserHal();
- // TODO(b/150413515): use UserHalHelper to populate it with current users
- UsersInfo usersInfo = new UsersInfo();
CountDownLatch latch = new CountDownLatch(1);
-
- userHal.getInitialUserInfo(requestType, timeout, usersInfo, (status, resp) -> {
+ HalCallback<InitialUserInfoResponse> callback = (status, resp) -> {
try {
Log.d(TAG, "GetUserInfoResponse: status=" + status + ", resp=" + resp);
writer.printf("Call status: %s\n",
@@ -848,10 +859,30 @@
writer.printf("Request id: %d\n", resp.requestId);
writer.printf("Action: %s\n",
InitialUserInfoResponseAction.toString(resp.action));
+ if (!TextUtils.isEmpty(resp.userNameToCreate)) {
+ writer.printf("User name: %s\n", resp.userNameToCreate);
+ }
+ if (resp.userToSwitchOrCreate.userId != UserHandle.USER_NULL) {
+ writer.printf("User id: %d\n", resp.userToSwitchOrCreate.userId);
+ }
+ if (resp.userToSwitchOrCreate.flags != UserFlags.NONE) {
+ writer.printf("User flags: %s\n",
+ UserHalHelper.userFlagsToString(resp.userToSwitchOrCreate.flags));
+ }
+ if (!TextUtils.isEmpty(resp.userLocales)) {
+ writer.printf("User locales: %s\n", resp.userLocales);
+ }
} finally {
latch.countDown();
}
- });
+ };
+ if (halOnly) {
+ // TODO(b/150413515): use UserHalHelper to populate it with current users
+ UsersInfo usersInfo = new UsersInfo();
+ mHal.getUserHal().getInitialUserInfo(requestType, timeout, usersInfo, callback);
+ } else {
+ mCarUserService.getInitialUserInfo(requestType, callback);
+ }
waitForHal(writer, latch, timeout);
}
@@ -929,20 +960,10 @@
waitForHal(writer, latch, timeout);
return;
}
- Car car = Car.createCar(mContext);
- CarUserManager carUserManager =
- (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
+ CarUserManager carUserManager = getCarUserManager(mContext);
AndroidFuture<UserSwitchResult> future = carUserManager.switchUser(targetUserId);
- UserSwitchResult result = null;
- try {
- result = future.get(timeout, TimeUnit.MILLISECONDS);
- } catch (Exception e) {
- Log.e(TAG, "exception calling CarUserManager.switchUser(" + targetUserId + ")", e);
- }
- if (result == null) {
- writer.printf("Service didn't respond in %d ms", timeout);
- return;
- }
+ UserSwitchResult result = waitForFuture(writer, future, timeout);
+ if (result == null) return;
writer.printf("UserSwitchResult: status = %s\n",
UserSwitchResult.statusToString(result.getStatus()));
String msg = result.getErrorMessage();
@@ -951,6 +972,20 @@
}
}
+ private static <T> T waitForFuture(@NonNull PrintWriter writer,
+ @NonNull AndroidFuture<T> future, int timeoutMs) {
+ T result = null;
+ try {
+ result = future.get(timeoutMs, TimeUnit.MILLISECONDS);
+ if (result == null) {
+ writer.printf("Service didn't respond in %d ms", timeoutMs);
+ }
+ } catch (Exception e) {
+ writer.printf("Exception getting future: %s", e);
+ }
+ return result;
+ }
+
private void getInitialUser(PrintWriter writer) {
android.content.pm.UserInfo user = mCarUserService.getInitialUser();
writer.println(user == null ? NO_INITIAL_USER : user.id);
@@ -1004,24 +1039,21 @@
+ ", request=" + request);
UserIdentificationResponse response = mHal.getUserHal().getUserAssociation(request);
Log.d(TAG, "getUserAuthAssociation(): response=" + response);
-
- if (response == null) {
- writer.println("null response");
- return;
- }
-
- if (!TextUtils.isEmpty(response.errorMessage)) {
- writer.printf("Error message: %s\n", response.errorMessage);
- }
- int numberAssociations = response.associations.size();
- writer.printf("%d associations:\n", numberAssociations);
- for (int i = 0; i < numberAssociations; i++) {
- UserIdentificationAssociation association = response.associations.get(i);
- writer.printf(" %s\n", association);
- }
+ showResponse(writer, response);
return;
}
+ CarUserManager carUserManager = getCarUserManager(writer, userId);
+ int[] types = new int[requestSize];
+ for (int i = 0; i < requestSize; i++) {
+ types[i] = request.associationTypes.get(i);
+ }
+ UserIdentificationAssociationResponse response = carUserManager
+ .getUserIdentificationAssociation(types);
+ showResponse(writer, response);
+ }
+
+ private CarUserManager getCarUserManager(@NonNull PrintWriter writer, @UserIdInt int userId) {
Context context;
if (userId == mContext.getUserId()) {
context = mContext;
@@ -1034,14 +1066,35 @@
+ "what CarUserService will use when calling HAL.\n", userId, actualUserId);
}
+ return getCarUserManager(context);
+ }
+
+ private CarUserManager getCarUserManager(@NonNull Context context) {
Car car = Car.createCar(context);
CarUserManager carUserManager = (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
- int[] types = new int[requestSize];
- for (int i = 0; i < types.length; i++) {
- types[i] = request.associationTypes.get(i);
+ return carUserManager;
+ }
+
+ private void showResponse(@NonNull PrintWriter writer,
+ @NonNull UserIdentificationResponse response) {
+ if (response == null) {
+ writer.println("null response");
+ return;
}
- GetUserIdentificationAssociationResponse response = carUserManager
- .getUserIdentificationAssociation(types);
+
+ if (!TextUtils.isEmpty(response.errorMessage)) {
+ writer.printf("Error message: %s\n", response.errorMessage);
+ }
+ int numberAssociations = response.associations.size();
+ writer.printf("%d associations:\n", numberAssociations);
+ for (int i = 0; i < numberAssociations; i++) {
+ UserIdentificationAssociation association = response.associations.get(i);
+ writer.printf(" %s\n", association);
+ }
+ }
+
+ private void showResponse(@NonNull PrintWriter writer,
+ @NonNull UserIdentificationAssociationResponse response) {
if (response == null) {
writer.println("null response");
return;
@@ -1058,7 +1111,7 @@
}
private void setUserAuthAssociation(String[] args, PrintWriter writer) {
- if (args.length < 4) {
+ if (args.length < 3) {
writer.println("invalid usage, must pass at least 4 arguments");
return;
}
@@ -1119,20 +1172,7 @@
mHal.getUserHal().setUserAssociation(timeout, request, (status, response) -> {
Log.d(TAG, "setUserAuthAssociation(): response=" + response);
try {
- if (response == null) {
- writer.println("null response");
- return;
- }
-
- if (!TextUtils.isEmpty(response.errorMessage)) {
- writer.printf("Error message: %s\n", response.errorMessage);
- }
- int numberAssociations = response.associations.size();
- writer.printf("%d associations:\n", numberAssociations);
- for (int i = 0; i < numberAssociations; i++) {
- UserIdentificationAssociation association = response.associations.get(i);
- writer.printf(" %s\n", association);
- }
+ showResponse(writer, response);
} finally {
latch.countDown();
}
@@ -1140,8 +1180,20 @@
waitForHal(writer, latch, timeout);
return;
}
- // TODO(b/150409351): implement it...
- throw new UnsupportedOperationException("must set --hal-only");
+ CarUserManager carUserManager = getCarUserManager(writer, userId);
+ int[] types = new int[requestSize];
+ int[] values = new int[requestSize];
+ for (int i = 0; i < requestSize; i++) {
+ UserIdentificationSetAssociation association = request.associations.get(i);
+ types[i] = association.type;
+ values[i] = association.value;
+ }
+ AndroidFuture<UserIdentificationAssociationResponse> future = carUserManager
+ .setUserIdentificationAssociation(types, values);
+ UserIdentificationAssociationResponse response = waitForFuture(writer, future, timeout);
+ if (response != null) {
+ showResponse(writer, response);
+ }
}
private static int parseAuthArg(@NonNull SparseArray<String> types, @NonNull String type) {
@@ -1218,10 +1270,11 @@
* @param zone Zone that this event services
* @param isErrorEvent indicates the type of event
* @param value Data value of the event
+ * @param delayTime the event timestamp is increased by delayTime
* @param writer PrintWriter
*/
private void injectVhalEvent(String property, String zone, String value,
- boolean isErrorEvent, PrintWriter writer) {
+ boolean isErrorEvent, String delayTime, PrintWriter writer) {
if (zone != null && (zone.equalsIgnoreCase(PARAM_VEHICLE_PROPERTY_AREA_GLOBAL))) {
if (!isPropertyAreaTypeGlobal(property)) {
writer.println("Property area type inconsistent with given zone");
@@ -1232,7 +1285,7 @@
if (isErrorEvent) {
mHal.injectOnPropertySetError(property, zone, value);
} else {
- mHal.injectVhalEvent(property, zone, value);
+ mHal.injectVhalEvent(property, zone, value, delayTime);
}
} catch (NumberFormatException e) {
writer.println("Invalid property Id zone Id or value" + e);
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 972f0a7..53e76fa 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -268,6 +268,7 @@
CarLocalServices.addService(FixedActivityService.class, mFixedActivityService);
CarLocalServices.addService(VmsBrokerService.class, mVmsBrokerService);
CarLocalServices.addService(CarOccupantZoneService.class, mCarOccupantZoneService);
+ CarLocalServices.addService(AppFocusService.class, mAppFocusService);
// Be careful with order. Service depending on other service should be inited later.
List<CarServiceBase> allServices = new ArrayList<>();
diff --git a/service/src/com/android/car/VmsPublishersInfo.java b/service/src/com/android/car/VmsPublishersInfo.java
deleted file mode 100644
index b99fc1e..0000000
--- a/service/src/com/android/car/VmsPublishersInfo.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import android.annotation.Nullable;
-import android.util.ArrayMap;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-public class VmsPublishersInfo {
- private static final byte[] EMPTY_RESPONSE = new byte[0];
-
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- private final ArrayMap<InfoWrapper, Integer> mPublishersIds = new ArrayMap<>();
- @GuardedBy("mLock")
- private final ArrayList<InfoWrapper> mPublishersInfo = new ArrayList<>();
-
- private static class InfoWrapper {
- private final byte[] mInfo;
-
- InfoWrapper(byte[] info) {
- mInfo = info;
- }
-
- public byte[] getInfo() {
- return mInfo.clone();
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof InfoWrapper)) {
- return false;
- }
- InfoWrapper p = (InfoWrapper) o;
- return Arrays.equals(this.mInfo, p.mInfo);
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mInfo);
- }
- }
-
- /**
- * Retrieves the publisher ID for the given publisher information. If the publisher information
- * has not previously been seen, it will be assigned a new publisher ID.
- *
- * @param publisherInfo Publisher information to query or register.
- * @return Publisher ID for the given publisher information.
- */
- public int getIdForInfo(byte[] publisherInfo) {
- Integer publisherId;
- InfoWrapper wrappedPublisherInfo = new InfoWrapper(publisherInfo);
- synchronized (mLock) {
- // Check if publisher is already registered
- publisherId = mPublishersIds.get(wrappedPublisherInfo);
- if (publisherId == null) {
- // Add the new publisher and assign it the next ID
- mPublishersInfo.add(wrappedPublisherInfo);
- publisherId = mPublishersInfo.size();
- mPublishersIds.put(wrappedPublisherInfo, publisherId);
- }
- }
- return publisherId;
- }
-
- /**
- * Returns the publisher info associated with the given publisher ID.
- * @param publisherId Publisher ID to query.
- * @return Publisher info associated with the ID, or an empty array if publisher ID is unknown.
- */
- public byte[] getPublisherInfo(int publisherId) {
- synchronized (mLock) {
- byte[] result = getPublisherInfoOrNull(publisherId);
- return result != null ? result : EMPTY_RESPONSE;
- }
- }
-
- /**
- * Returns the publisher info associated with the given publisher ID.
- * @param publisherId Publisher ID to query.
- * @return Publisher info associated with the ID, or null if publisher ID is unknown.
- */
- @Nullable
- public byte[] getPublisherInfoOrNull(int publisherId) {
- synchronized (mLock) {
- return publisherId < 1 || publisherId > mPublishersInfo.size()
- ? null
- : mPublishersInfo.get(publisherId - 1).getInfo();
- }
- }
-}
-
diff --git a/service/src/com/android/car/am/FixedActivityService.java b/service/src/com/android/car/am/FixedActivityService.java
index f381702..cd16463 100644
--- a/service/src/com/android/car/am/FixedActivityService.java
+++ b/service/src/com/android/car/am/FixedActivityService.java
@@ -138,7 +138,7 @@
}
if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
synchronized (FixedActivityService.this.mLock) {
- FixedActivityService.this.mRunningActivities.clear();
+ clearRunningActivitiesLocked();
}
}
};
@@ -290,6 +290,25 @@
synchronized (mLock) {
writer.println("mRunningActivities:" + mRunningActivities
+ " ,mEventMonitoringActive:" + mEventMonitoringActive);
+ writer.println("mBlockingPresentations:");
+ for (int i = 0; i < mBlockingPresentations.size(); i++) {
+ Presentation p = mBlockingPresentations.valueAt(i);
+ if (p == null) {
+ continue;
+ }
+ writer.println("display:" + mBlockingPresentations.keyAt(i)
+ + " showing:" + p.isShowing());
+ }
+ }
+ }
+
+ private void clearRunningActivitiesLocked() {
+ int currentUser = ActivityManager.getCurrentUser();
+ for (int i = mRunningActivities.size() - 1; i >= 0; i--) {
+ RunningActivityInfo info = mRunningActivities.valueAt(i);
+ if (info == null || info.userId != currentUser) {
+ mRunningActivities.removeAt(i);
+ }
}
}
@@ -411,10 +430,15 @@
Presentation p = new Presentation(mContext, display,
android.R.style.Theme_Black_NoTitleBar_Fullscreen);
p.setContentView(R.layout.activity_continuous_blank);
- p.show();
synchronized (mLock) {
+ RunningActivityInfo info = mRunningActivities.get(displayIdForActivity);
+ if (info != null && info.userId == ActivityManager.getCurrentUser()) {
+ Log.i(TAG_AM, "Do not show Presentation, new req already made");
+ return;
+ }
mBlockingPresentations.append(displayIdForActivity, p);
}
+ p.show();
});
}
mRunningActivities.removeAt(i);
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index d8ac6ac..94f31d2 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -101,8 +101,6 @@
private final String mRenderingServiceConfig;
-
-
@GuardedBy("mLock")
private IInstrumentClusterNavigation mIInstrumentClusterNavigationFromRenderer;
@@ -111,6 +109,7 @@
@Override
public void onNavigationStateChanged(Bundle bundle) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_NAVIGATION_MANAGER);
+ assertNavigationFocus();
// No retry here as new events will be sent later.
IInstrumentClusterNavigation navigationBinder = getNavigationBinder(
/* retryOnFail= */ false);
@@ -323,6 +322,23 @@
changeNavContextOwner(appType, uid, pid, false);
}
+ private void assertNavigationFocus() {
+ int uid = Binder.getCallingUid();
+ int pid = Binder.getCallingPid();
+ synchronized (mLock) {
+ if (uid == mNavContextOwner.uid && pid == mNavContextOwner.pid) {
+ return;
+ }
+ }
+ // Stored one failed. There can be a delay, so check with real one again.
+ AppFocusService afs = CarLocalServices.getService(AppFocusService.class);
+ if (afs != null && afs.isFocusOwner(uid, pid,
+ CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)) {
+ return;
+ }
+ throw new IllegalStateException("Client not owning APP_FOCUS_TYPE_NAVIGATION");
+ }
+
private void changeNavContextOwner(int appType, int uid, int pid, boolean acquire) {
if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
return;
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
index f995047..9abe92b 100644
--- a/service/src/com/android/car/hal/UserHalService.java
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -29,11 +29,9 @@
import android.car.userlib.HalCallback;
import android.car.userlib.UserHalHelper;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
-import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
-import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
@@ -45,7 +43,6 @@
import android.os.Handler;
import android.os.Looper;
import android.os.ServiceSpecificException;
-import android.os.UserHandle;
import android.sysprop.CarProperties;
import android.text.TextUtils;
import android.util.EventLog;
@@ -53,6 +50,8 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import com.android.car.CarLocalServices;
+import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.car.EventLogTags;
@@ -95,7 +94,7 @@
// This handler handles 2 types of messages:
// - "Anonymous" messages (what=0) containing runnables.
// - "Identifiable" messages used to check for timeouts (whose 'what' is the request id).
- private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler;
/**
* Value used on the next request.
@@ -110,7 +109,13 @@
private final SparseArray<PendingRequest<?, ?>> mPendingRequests = new SparseArray<>();
public UserHalService(VehicleHal hal) {
+ this(hal, new Handler(Looper.getMainLooper()));
+ }
+
+ @VisibleForTesting
+ UserHalService(VehicleHal hal, Handler handler) {
mHal = hal;
+ mHandler = handler;
}
@Override
@@ -336,7 +341,7 @@
VehiclePropValue propRequest;
synchronized (mLock) {
checkSupportedLocked();
- int requestId = mNextRequestId++;
+ int requestId = getNextRequestId();
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_LEGACY_SWITCH_USER_REQ, requestId,
targetInfo.userId, usersInfo.currentUser.userId);
propRequest = getPropRequestForSwitchUserLocked(requestId,
@@ -604,7 +609,7 @@
}
private void handleCheckIfRequestTimedOut(int requestId) {
- PendingRequest<?, ?> pendingRequest = getPendingResponse(requestId);
+ PendingRequest<?, ?> pendingRequest = getPendingRequest(requestId);
if (pendingRequest == null) return;
Log.w(TAG, "Request #" + requestId + " timed out");
@@ -613,7 +618,7 @@
}
@Nullable
- private PendingRequest<?, ?> getPendingResponse(int requestId) {
+ private PendingRequest<?, ?> getPendingRequest(int requestId) {
synchronized (mLock) {
return mPendingRequests.get(requestId);
}
@@ -631,41 +636,73 @@
return;
}
handleRemovePendingRequest(requestId);
- InitialUserInfoResponse response = new InitialUserInfoResponse();
- // TODO(b/150413515): use helper method to convert prop value to proper response
- response.requestId = requestId;
- response.action = value.value.int32Values.get(1);
- switch (response.action) {
- case InitialUserInfoResponseAction.DEFAULT:
- response.userToSwitchOrCreate.userId = UserHandle.USER_NULL;
- response.userToSwitchOrCreate.flags = UserFlags.NONE;
- break;
- case InitialUserInfoResponseAction.SWITCH:
- response.userToSwitchOrCreate.userId = value.value.int32Values.get(2);
- response.userToSwitchOrCreate.flags = UserFlags.NONE;
- break;
- case InitialUserInfoResponseAction.CREATE:
- response.userToSwitchOrCreate.userId = UserHandle.USER_NULL;
- response.userToSwitchOrCreate.flags = value.value.int32Values.get(2);
- response.userNameToCreate = value.value.stringValue;
- break;
- default:
- Log.e(TAG, "invalid action (" + response.action + ") from HAL: " + value);
- EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_RESP, requestId,
- HalCallback.STATUS_WRONG_HAL_RESPONSE);
- callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
- return;
+
+ InitialUserInfoResponse response;
+ try {
+ response = UserHalHelper.toInitialUserInfoResponse(value);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "invalid response (" + value + ") from HAL", e);
+ EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_RESP, requestId,
+ HalCallback.STATUS_WRONG_HAL_RESPONSE);
+ callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
+ return;
}
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_RESP, requestId,
HalCallback.STATUS_OK, response.action,
response.userToSwitchOrCreate.userId, response.userToSwitchOrCreate.flags,
- response.userNameToCreate);
+ response.userNameToCreate, response.userLocales);
if (DBG) Log.d(TAG, "replying to request " + requestId + " with " + response);
callback.onResponse(HalCallback.STATUS_OK, response);
}
private void handleOnSwitchUserResponse(VehiclePropValue value) {
int requestId = value.value.int32Values.get(0);
+ int messageType = value.value.int32Values.get(1);
+
+ if (messageType == SwitchUserMessageType.VEHICLE_RESPONSE) {
+ handleVehicleResponse(value);
+ return;
+ }
+
+ if (messageType == SwitchUserMessageType.VEHICLE_REQUEST) {
+ handleVehicleRequest(value);
+ return;
+ }
+
+ Log.e(TAG, "handleOnSwitchUserResponse invalid message type (" + messageType
+ + ") from HAL: " + value);
+
+ // check if a callback exists for the request ID
+ HalCallback<SwitchUserResponse> callback =
+ handleGetPendingCallback(requestId, SwitchUserResponse.class);
+ if (callback != null) {
+ handleRemovePendingRequest(requestId);
+ EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
+ HalCallback.STATUS_WRONG_HAL_RESPONSE, SwitchUserStatus.FAILURE);
+ callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
+ return;
+ }
+ }
+
+ private void handleVehicleRequest(VehiclePropValue value) {
+ int requestId = value.value.int32Values.get(0);
+ // Index 1 is message type, which is not required in this call.
+ int targetUserId = value.value.int32Values.get(2);
+ EventLog.writeEvent(EventLogTags.CAR_USER_HAL_OEM_SWITCH_USER_REQ, requestId, targetUserId);
+
+ // HAL vehicle request should have negative request ID
+ if (requestId >= 0) {
+ Log.e(TAG, "handleVehicleRequest invalid requestId (" + requestId + ") from HAL: "
+ + value);
+ return;
+ }
+
+ CarUserService userService = CarLocalServices.getService(CarUserService.class);
+ userService.switchAndroidUserFromHal(requestId, targetUserId);
+ }
+
+ private void handleVehicleResponse(VehiclePropValue value) {
+ int requestId = value.value.int32Values.get(0);
HalCallback<SwitchUserResponse> callback =
handleGetPendingCallback(requestId, SwitchUserResponse.class);
if (callback == null) {
@@ -678,13 +715,6 @@
SwitchUserResponse response = new SwitchUserResponse();
response.requestId = requestId;
response.messageType = value.value.int32Values.get(1);
- if (response.messageType != SwitchUserMessageType.VEHICLE_RESPONSE) {
- EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
- HalCallback.STATUS_WRONG_HAL_RESPONSE);
- Log.e(TAG, "invalid message type (" + response.messageType + ") from HAL: " + value);
- callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
- return;
- }
response.status = value.value.int32Values.get(2);
if (response.status == SwitchUserStatus.SUCCESS
|| response.status == SwitchUserStatus.FAILURE) {
@@ -703,7 +733,7 @@
}
private <T> HalCallback<T> handleGetPendingCallback(int requestId, Class<T> clazz) {
- PendingRequest<?, ?> pendingRequest = getPendingResponse(requestId);
+ PendingRequest<?, ?> pendingRequest = getPendingRequest(requestId);
if (pendingRequest == null) return null;
if (pendingRequest.responseClass != clazz) {
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 7e80e74..8706c00 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -60,6 +60,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -712,14 +713,16 @@
* @param property the Vehicle property Id as defined in the HAL
* @param zone Zone that this event services
* @param value Data value of the event
+ * @param delayTime Add a certain duration to event timestamp
*/
- public void injectVhalEvent(String property, String zone, String value)
+ public void injectVhalEvent(String property, String zone, String value, String delayTime)
throws NumberFormatException {
if (value == null || zone == null || property == null) {
return;
}
int propId = Integer.decode(property);
int zoneId = Integer.decode(zone);
+ int duration = Integer.decode(delayTime);
VehiclePropValue v = createPropValue(propId, zoneId);
int propertyType = propId & VehiclePropertyType.MASK;
// Values can be comma separated list
@@ -745,7 +748,7 @@
Log.e(CarLog.TAG_HAL, "Property type unsupported:" + propertyType);
return;
}
- v.timestamp = SystemClock.elapsedRealtimeNanos();
+ v.timestamp = SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(duration);
onPropertyEvent(Lists.newArrayList(v));
}
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 2ae56cb..d7de501 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -33,13 +33,17 @@
import android.car.user.CarUserManager.UserLifecycleEvent;
import android.car.user.CarUserManager.UserLifecycleEventType;
import android.car.user.CarUserManager.UserLifecycleListener;
-import android.car.user.GetUserIdentificationAssociationResponse;
+import android.car.user.UserIdentificationAssociationResponse;
import android.car.user.UserSwitchResult;
import android.car.userlib.CarUserManagerHelper;
import android.car.userlib.CommonConstants.CarUserServiceConstants;
import android.car.userlib.HalCallback;
import android.car.userlib.UserHalHelper;
+import android.car.userlib.UserHelper;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -48,6 +52,8 @@
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetAssociation;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest;
import android.hardware.automotive.vehicle.V2_0.UsersInfo;
import android.location.LocationManager;
import android.os.Binder;
@@ -60,6 +66,7 @@
import android.os.UserManager;
import android.provider.Settings;
import android.sysprop.CarProperties;
+import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
@@ -109,7 +116,14 @@
public static final String BUNDLE_USER_FLAGS = CarUserServiceConstants.BUNDLE_USER_FLAGS;
/** {@code String} extra used to represent a user name in a {@link IResultReceiver} response. */
public static final String BUNDLE_USER_NAME = CarUserServiceConstants.BUNDLE_USER_NAME;
- /** {@code int} extra used to represent the info action {@link IResultReceiver} response. */
+ /**
+ * {@code int} extra used to represent the user locales in a {@link IResultReceiver} response.
+ */
+ public static final String BUNDLE_USER_LOCALES =
+ CarUserServiceConstants.BUNDLE_USER_LOCALES;
+ /**
+ * {@code int} extra used to represent the info action in a {@link IResultReceiver} response.
+ */
public static final String BUNDLE_INITIAL_INFO_ACTION =
CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION;
@@ -292,6 +306,7 @@
writer.printf("User switch in process=%d\n", mUserIdForUserSwitchInProcess);
writer.printf("Request Id for the user switch in process=%d\n ",
mRequestIdForUserSwitchInProcess);
+ writer.printf("System UI package name=%s\n", getSystemUiPackageName());
dumpUserMetrics(writer);
}
@@ -412,29 +427,21 @@
* @see CarUserManager.switchDriver
*/
@Override
- public boolean switchDriver(@UserIdInt int driverId) {
+ public void switchDriver(@UserIdInt int driverId, AndroidFuture<UserSwitchResult> receiver) {
checkManageUsersPermission("switchDriver");
- if (driverId == UserHandle.USER_SYSTEM && UserManager.isHeadlessSystemUserMode()) {
+ if (UserHelper.isHeadlessSystemUser(driverId)) {
// System user doesn't associate with real person, can not be switched to.
Log.w(TAG_USER, "switching to system user in headless system user mode is not allowed");
- return false;
+ sendResult(receiver, UserSwitchResult.STATUS_INVALID_REQUEST);
+ return;
}
int userSwitchable = mUserManager.getUserSwitchability();
if (userSwitchable != UserManager.SWITCHABILITY_STATUS_OK) {
Log.w(TAG_USER, "current process is not allowed to switch user");
- return false;
+ sendResult(receiver, UserSwitchResult.STATUS_INVALID_REQUEST);
+ return;
}
- if (driverId == ActivityManager.getCurrentUser()) {
- // The current user is already the given user.
- return true;
- }
- try {
- return mAm.switchUser(driverId);
- } catch (RemoteException e) {
- // ignore
- Log.w(TAG_USER, "error while switching user", e);
- }
- return false;
+ switchUser(driverId, mHalTimeoutMs, receiver);
}
/**
@@ -446,7 +453,7 @@
@NonNull
public List<UserInfo> getAllDrivers() {
checkManageUsersOrDumpPermission("getAllDrivers");
- return getUsers((user) -> !isSystemUser(user.id) && user.isEnabled()
+ return getUsers((user) -> !UserHelper.isHeadlessSystemUser(user.id) && user.isEnabled()
&& !user.isManagedProfile() && !user.isEphemeral());
}
@@ -461,8 +468,8 @@
public List<UserInfo> getPassengers(@UserIdInt int driverId) {
checkManageUsersOrDumpPermission("getPassengers");
return getUsers((user) -> {
- return !isSystemUser(user.id) && user.isEnabled() && user.isManagedProfile()
- && user.profileGroupId == driverId;
+ return !UserHelper.isHeadlessSystemUser(user.id) && user.isEnabled()
+ && user.isManagedProfile() && user.profileGroupId == driverId;
});
}
@@ -596,7 +603,7 @@
if (resp != null) {
EventLog.writeEvent(EventLogTags.CAR_USER_SVC_INITIAL_USER_INFO_RESP,
status, resp.action, resp.userToSwitchOrCreate.userId,
- resp.userToSwitchOrCreate.flags, resp.userNameToCreate);
+ resp.userToSwitchOrCreate.flags, resp.userNameToCreate, resp.userLocales);
switch (resp.action) {
case InitialUserInfoResponseAction.SWITCH:
resultData = new Bundle();
@@ -620,6 +627,10 @@
} else {
EventLog.writeEvent(EventLogTags.CAR_USER_SVC_INITIAL_USER_INFO_RESP, status);
}
+ if (resultData != null && !TextUtils.isEmpty(resp.userLocales)) {
+ resultData.putString(BUNDLE_USER_LOCALES, resp.userLocales);
+ }
+
sendResult(receiver, status, resultData);
});
}
@@ -867,7 +878,7 @@
}
@Override
- public GetUserIdentificationAssociationResponse getUserIdentificationAssociation(int[] types) {
+ public UserIdentificationAssociationResponse getUserIdentificationAssociation(int[] types) {
Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
checkManageUsersPermission("getUserIdentificationAssociation");
@@ -888,7 +899,7 @@
if (halResponse == null) {
Log.w(TAG, "getUserIdentificationAssociation(): HAL returned null for "
+ Arrays.toString(types));
- return null;
+ return UserIdentificationAssociationResponse.forFailure();
}
int[] values = new int[halResponse.associations.size()];
@@ -897,7 +908,68 @@
}
EventLog.writeEvent(EventLogTags.CAR_USER_MGR_GET_USER_AUTH_RESP, values.length);
- return new GetUserIdentificationAssociationResponse(halResponse.errorMessage, values);
+ return UserIdentificationAssociationResponse.forSuccess(values, halResponse.errorMessage);
+ }
+
+ @Override
+ public void setUserIdentificationAssociation(int timeoutMs, int[] types, int[] values,
+ AndroidFuture<UserIdentificationAssociationResponse> result) {
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(values), "must have at least one value");
+ if (types.length != values.length) {
+ throw new IllegalArgumentException("types (" + Arrays.toString(types) + ") and values ("
+ + Arrays.toString(values) + ") should have the same length");
+ }
+ checkManageUsersPermission("setUserIdentificationAssociation");
+
+ int uid = getCallingUid();
+ int userId = UserHandle.getUserId(uid);
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SET_USER_AUTH_REQ, uid, userId, types.length);
+
+ UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+ request.userInfo.userId = userId;
+ request.userInfo.flags = getHalUserInfoFlags(userId);
+
+ request.numberAssociations = types.length;
+ for (int i = 0; i < types.length; i++) {
+ UserIdentificationSetAssociation association = new UserIdentificationSetAssociation();
+ association.type = types[i];
+ association.value = values[i];
+ request.associations.add(association);
+ }
+
+ mHal.setUserAssociation(timeoutMs, request, (status, resp) -> {
+ if (status != HalCallback.STATUS_OK) {
+ Log.w(TAG, "setUserIdentificationAssociation(): invalid callback status ("
+ + UserHalHelper.halCallbackStatusToString(status) + ") for response "
+ + resp);
+ if (resp == null || TextUtils.isEmpty(resp.errorMessage)) {
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SET_USER_AUTH_RESP, 0);
+ result.complete(UserIdentificationAssociationResponse.forFailure());
+ return;
+ }
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SET_USER_AUTH_RESP, 0,
+ resp.errorMessage);
+ result.complete(
+ UserIdentificationAssociationResponse.forFailure(resp.errorMessage));
+ return;
+ }
+ int respSize = resp.associations.size();
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SET_USER_AUTH_RESP, respSize,
+ resp.errorMessage);
+
+ int[] responseTypes = new int[respSize];
+ for (int i = 0; i < respSize; i++) {
+ responseTypes[i] = resp.associations.get(i).value;
+ }
+ UserIdentificationAssociationResponse response = UserIdentificationAssociationResponse
+ .forSuccess(responseTypes, resp.errorMessage);
+ if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+ Log.d(TAG, "setUserIdentificationAssociation(): resp= " + resp
+ + ", converted=" + response);
+ }
+ result.complete(response);
+ });
}
/**
@@ -931,6 +1003,49 @@
receiver.complete(new UserSwitchResult(status, errorMessage));
}
+ /**
+ * Calls activity manager for user switch.
+ *
+ * <p><b>NOTE</b> This method is meant to be called just by UserHalService.
+ *
+ * @param requestId for the user switch request
+ * @param targetUserId of the target user
+ *
+ * @hide
+ */
+ public void switchAndroidUserFromHal(int requestId, @UserIdInt int targetUserId) {
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_SWITCH_USER_FROM_HAL_REQ, requestId,
+ targetUserId);
+ Log.i(TAG_USER, "User hal requested a user switch. Target user id " + targetUserId);
+
+ try {
+ boolean result = mAm.switchUser(targetUserId);
+ if (result) {
+ updateUserSwitchInProcess(requestId, targetUserId);
+ } else {
+ postSwitchHalResponse(requestId, targetUserId);
+ }
+ } catch (RemoteException e) {
+ // ignore
+ Log.w(TAG_USER, "error while switching user " + targetUserId, e);
+ }
+ }
+
+ private void updateUserSwitchInProcess(int requestId, @UserIdInt int targetUserId) {
+ synchronized (mLockUser) {
+ if (mUserIdForUserSwitchInProcess != UserHandle.USER_NULL) {
+ // Some other user switch is in process.
+ if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+ Log.d(TAG_USER, "User switch for user: " + mUserIdForUserSwitchInProcess
+ + " is in process. Abandoning it as a new user switch is requested"
+ + " for the target user: " + targetUserId);
+ }
+ }
+ mUserIdForUserSwitchInProcess = targetUserId;
+ mRequestIdForUserSwitchInProcess = requestId;
+ }
+ }
+
private void postSwitchHalResponse(int requestId, @UserIdInt int targetUserId) {
UserInfo targetUser = mUserManager.getUserInfo(targetUserId);
UsersInfo usersInfo = getUsersInfo();
@@ -959,11 +1074,45 @@
*/
@Override
public void setUserSwitchUiCallback(@NonNull IResultReceiver receiver) {
- // TODO(b/154958003): check UID, only carSysUI should be allowed to set it.
+ checkManageUsersPermission("setUserSwitchUiCallback");
+
+ // Confirm that caller is system UI.
+ String systemUiPackageName = getSystemUiPackageName();
+ if (systemUiPackageName == null) {
+ throw new IllegalStateException("System UI package not found.");
+ }
+
+ try {
+ int systemUiUid = mContext
+ .createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0).getPackageManager()
+ .getPackageUid(systemUiPackageName, PackageManager.MATCH_SYSTEM_ONLY);
+ int callerUid = Binder.getCallingUid();
+ if (systemUiUid != callerUid) {
+ throw new SecurityException("Invalid caller. Only" + systemUiPackageName
+ + " is allowed to make this call");
+ }
+ } catch (NameNotFoundException e) {
+ throw new IllegalStateException("Package " + systemUiPackageName + " not found.");
+ }
+
mUserSwitchUiReceiver = receiver;
}
- // TODO(b/144120654): use helper to generate UsersInfo
+ // TODO(157082995): This information can be taken from
+ // PackageManageInternalImpl.getSystemUiServiceComponent
+ @Nullable
+ private String getSystemUiPackageName() {
+ try {
+ ComponentName componentName = ComponentName.unflattenFromString(mContext.getResources()
+ .getString(com.android.internal.R.string.config_systemUIServiceComponent));
+ return componentName.getPackageName();
+ } catch (RuntimeException e) {
+ Log.w(TAG_USER, "error while getting system UI package name.", e);
+ return null;
+ }
+ }
+
+ // TODO(b/150413515): use helper to generate UsersInfo
private UsersInfo getUsersInfo() {
UserInfo currentUser;
try {
@@ -975,7 +1124,7 @@
return getUsersInfo(currentUser);
}
- // TODO(b/144120654): use helper to generate UsersInfo
+ // TODO(b/150413515): use helper to generate UsersInfo
private UsersInfo getUsersInfo(@NonNull UserInfo currentUser) {
List<UserInfo> existingUsers = mUserManager.getUsers();
int size = existingUsers.size();
@@ -997,11 +1146,6 @@
return usersInfo;
}
- /** Returns whether the given user is a system user. */
- private static boolean isSystemUser(@UserIdInt int userId) {
- return userId == UserHandle.USER_SYSTEM;
- }
-
private void updateDefaultUserRestriction() {
// We want to set restrictions on system and guest users only once. These are persisted
// onto disk, so it's sufficient to do it once + we minimize the number of disk writes.
@@ -1296,14 +1440,14 @@
}
private void onUserSwitching(@UserIdInt int fromUserId, @UserIdInt int toUserId) {
- Log.i(TAG_USER, "onSwitchUser() callback for user " + toUserId);
+ Log.i(TAG_USER, "onUserSwitching() callback for user " + toUserId);
TimingsTraceLog t = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
t.traceBegin("onUserSwitching-" + toUserId);
// Switch HAL users if user switch is not requested by CarUserService
notifyHalLegacySwitch(fromUserId, toUserId);
- if (!isSystemUser(toUserId)) {
+ if (!UserHelper.isHeadlessSystemUser(toUserId)) {
mCarUserManagerHelper.setLastActiveUser(toUserId);
}
if (mLastPassengerId != UserHandle.USER_NULL) {
diff --git a/service/src/com/android/car/user/UserMetrics.java b/service/src/com/android/car/user/UserMetrics.java
index adba813..62b9755 100644
--- a/service/src/com/android/car/user/UserMetrics.java
+++ b/service/src/com/android/car/user/UserMetrics.java
@@ -65,7 +65,7 @@
// garage mode
private static final int INITIAL_CAPACITY = 2;
- // TODO(b/144120654): read from resources
+ // TODO(b/150413515): read from resources
private static final int LOG_SIZE = 10;
private final Object mLock = new Object();
diff --git a/service/src/com/android/car/vms/VmsBrokerService.java b/service/src/com/android/car/vms/VmsBrokerService.java
index 9ecfc3e..31c60bd 100644
--- a/service/src/com/android/car/vms/VmsBrokerService.java
+++ b/service/src/com/android/car/vms/VmsBrokerService.java
@@ -41,8 +41,6 @@
import android.util.Log;
import com.android.car.CarServiceBase;
-import com.android.car.VmsLayersAvailability;
-import com.android.car.VmsPublishersInfo;
import com.android.car.stats.CarStatsService;
import com.android.car.stats.VmsClientLogger;
import com.android.internal.annotations.GuardedBy;
@@ -53,6 +51,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -74,8 +73,8 @@
private final CarStatsService mStatsService;
private final IntSupplier mGetCallingUid;
- private final VmsPublishersInfo mPublishersInfo = new VmsPublishersInfo();
- private final VmsLayersAvailability mAvailableLayers = new VmsLayersAvailability();
+ private final VmsProviderInfoStore mProviderInfoStore = new VmsProviderInfoStore();
+ private final VmsLayerAvailability mAvailableLayers = new VmsLayerAvailability();
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -111,7 +110,17 @@
@Override
public void dump(PrintWriter writer) {
- // TODO(b/149125079): Implement dumpsys
+ writer.println("*" + TAG + "*");
+ synchronized (mLock) {
+ writer.println("mAvailableLayers: " + mAvailableLayers.getAvailableLayers());
+ writer.println();
+ writer.println("mSubscriptionState: " + mSubscriptionState);
+ writer.println();
+ writer.println("mClientMap:");
+ mClientMap.values().stream()
+ .sorted(Comparator.comparingInt(VmsClientInfo::getUid))
+ .forEach(client -> client.dump(writer, " "));
+ }
}
@Override
@@ -125,18 +134,20 @@
mStatsService.getVmsClientLogger(clientUid)
.logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
+ IBinder.DeathRecipient deathRecipient;
+ try {
+ deathRecipient = () -> unregisterClient(clientToken,
+ VmsClientLogger.ConnectionState.DISCONNECTED);
+ callback.asBinder().linkToDeath(deathRecipient, 0);
+ } catch (RemoteException e) {
+ mStatsService.getVmsClientLogger(clientUid)
+ .logConnectionState(VmsClientLogger.ConnectionState.DISCONNECTED);
+ throw new IllegalStateException("Client callback is already dead");
+ }
+
synchronized (mLock) {
- try {
- callback.asBinder().linkToDeath(
- () -> unregisterClient(clientToken,
- VmsClientLogger.ConnectionState.DISCONNECTED), 0);
- mClientMap.put(clientToken, new VmsClientInfo(clientUid, clientPackage, callback,
- legacyClient));
- } catch (RemoteException e) {
- Log.w(TAG, "Client process is already dead", e);
- mStatsService.getVmsClientLogger(clientUid)
- .logConnectionState(VmsClientLogger.ConnectionState.DISCONNECTED);
- }
+ mClientMap.put(clientToken, new VmsClientInfo(clientUid, clientPackage, callback,
+ legacyClient, deathRecipient));
return new VmsRegistrationInfo(
mAvailableLayers.getAvailableLayers(),
mSubscriptionState);
@@ -153,7 +164,7 @@
public VmsProviderInfo getProviderInfo(IBinder clientToken, int providerId) {
assertAnyVmsPermission(mContext);
getClient(clientToken); // Assert that the client is registered
- return new VmsProviderInfo(mPublishersInfo.getPublisherInfoOrNull(providerId));
+ return new VmsProviderInfo(mProviderInfoStore.getProviderInfo(providerId));
}
@Override
@@ -172,12 +183,13 @@
@Override
public int registerProvider(IBinder clientToken, VmsProviderInfo providerInfo) {
assertVmsPublisherPermission(mContext);
+ VmsClientInfo client = getClient(clientToken);
+ int providerId;
synchronized (mLock) {
- VmsClientInfo client = getClient(clientToken);
- int publisherId = mPublishersInfo.getIdForInfo(providerInfo.getDescription());
- client.addProviderId(publisherId);
- return publisherId;
+ providerId = mProviderInfoStore.getProviderId(providerInfo.getDescription());
}
+ client.addProviderId(providerId);
+ return providerId;
}
@Override
@@ -252,15 +264,17 @@
}
private void unregisterClient(IBinder clientToken, int connectionState) {
+ VmsClientInfo client;
synchronized (mLock) {
- VmsClientInfo client = mClientMap.remove(clientToken);
- if (client != null) {
- mStatsService.getVmsClientLogger(client.getUid())
- .logConnectionState(connectionState);
- }
+ client = mClientMap.remove(clientToken);
}
- updateAvailableLayers();
- updateSubscriptionState();
+ if (client != null) {
+ client.getCallback().asBinder().unlinkToDeath(client.getDeathRecipient(), 0);
+ mStatsService.getVmsClientLogger(client.getUid())
+ .logConnectionState(connectionState);
+ updateAvailableLayers();
+ updateSubscriptionState();
+ }
}
private VmsClientInfo getClient(IBinder clientToken) {
diff --git a/service/src/com/android/car/vms/VmsClientInfo.java b/service/src/com/android/car/vms/VmsClientInfo.java
index 05a11dd..d64b6b7 100644
--- a/service/src/com/android/car/vms/VmsClientInfo.java
+++ b/service/src/com/android/car/vms/VmsClientInfo.java
@@ -21,12 +21,14 @@
import android.car.vms.VmsLayer;
import android.car.vms.VmsLayerDependency;
import android.car.vms.VmsLayersOffering;
+import android.os.IBinder;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -43,6 +45,7 @@
private final String mPackageName;
private final IVmsClientCallback mCallback;
private final boolean mLegacyClient;
+ private final IBinder.DeathRecipient mDeathRecipient;
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -58,11 +61,13 @@
@GuardedBy("mLock")
private boolean mMonitoringEnabled;
- VmsClientInfo(int uid, String packageName, IVmsClientCallback callback, boolean legacyClient) {
+ VmsClientInfo(int uid, String packageName, IVmsClientCallback callback, boolean legacyClient,
+ IBinder.DeathRecipient deathRecipient) {
mUid = uid;
mPackageName = packageName;
mCallback = callback;
mLegacyClient = legacyClient;
+ mDeathRecipient = deathRecipient;
}
int getUid() {
@@ -77,6 +82,14 @@
return mCallback;
}
+ boolean isLegacyClient() {
+ return mLegacyClient;
+ }
+
+ IBinder.DeathRecipient getDeathRecipient() {
+ return mDeathRecipient;
+ }
+
void addProviderId(int providerId) {
synchronized (mLock) {
mProviderIds.put(providerId, true);
@@ -168,8 +181,50 @@
}
}
- boolean isLegacyClient() {
- return mLegacyClient;
+ void dump(PrintWriter writer, String indent) {
+ synchronized (mLock) {
+ String prefix = indent;
+ writer.println(prefix + "VmsClient [" + mPackageName + "]");
+
+ prefix = indent + " ";
+ writer.println(prefix + "UID: " + mUid);
+ writer.println(prefix + "Legacy Client: " + mLegacyClient);
+ writer.println(prefix + "Monitoring: " + mMonitoringEnabled);
+
+ if (mProviderIds.size() > 0) {
+ writer.println(prefix + "Offerings:");
+ for (int i = 0; i < mProviderIds.size(); i++) {
+ prefix = indent + " ";
+ int providerId = mProviderIds.keyAt(i);
+ writer.println(prefix + "Provider [" + providerId + "]");
+
+ for (VmsLayerDependency layerOffering : mOfferings.get(
+ providerId, Collections.emptySet())) {
+ prefix = indent + " ";
+ writer.println(prefix + layerOffering.getLayer());
+ if (!layerOffering.getDependencies().isEmpty()) {
+ prefix = indent + " ";
+ writer.println(prefix + "Dependencies: "
+ + layerOffering.getDependencies());
+ }
+ }
+ }
+ }
+
+ if (!mLayerSubscriptions.isEmpty() || !mLayerAndProviderSubscriptions.isEmpty()) {
+ prefix = indent + " ";
+ writer.println(prefix + "Subscriptions:");
+
+ prefix = indent + " ";
+ for (VmsLayer layer : mLayerSubscriptions) {
+ writer.println(prefix + layer);
+ }
+ for (Map.Entry<VmsLayer, Set<Integer>> layerEntry :
+ mLayerAndProviderSubscriptions.entrySet()) {
+ writer.println(prefix + layerEntry.getKey() + ": " + layerEntry.getValue());
+ }
+ }
+ }
}
private static <K, V> Map<K, Set<V>> deepCopy(Map<K, Set<V>> original) {
diff --git a/service/src/com/android/car/VmsLayersAvailability.java b/service/src/com/android/car/vms/VmsLayerAvailability.java
similarity index 72%
rename from service/src/com/android/car/VmsLayersAvailability.java
rename to service/src/com/android/car/vms/VmsLayerAvailability.java
index a9bbc7a..3521336 100644
--- a/service/src/com/android/car/VmsLayersAvailability.java
+++ b/service/src/com/android/car/vms/VmsLayerAvailability.java
@@ -1,7 +1,7 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
+ * 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
*
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.car;
+package com.android.car.vms;
import android.car.vms.VmsAssociatedLayer;
import android.car.vms.VmsAvailableLayers;
@@ -35,26 +35,22 @@
/**
* Manages VMS availability for layers.
- * <p>
+ *
* Each VMS publisher sets its layers offering which are a list of layers the publisher claims
* it might publish. VmsLayersAvailability calculates from all the offering what are the
* available layers.
- *
- * @hide
*/
-public class VmsLayersAvailability {
+class VmsLayerAvailability {
private static final boolean DBG = false;
- private static final String TAG = "VmsLayersAvailability";
+ private static final String TAG = VmsLayerAvailability.class.getSimpleName();
private final Object mLock = new Object();
@GuardedBy("mLock")
private final Map<VmsLayer, Set<Set<VmsLayer>>> mPotentialLayersAndDependencies =
new HashMap<>();
@GuardedBy("mLock")
- private Set<VmsAssociatedLayer> mAvailableAssociatedLayers = Collections.EMPTY_SET;
- @GuardedBy("mLock")
- private Set<VmsAssociatedLayer> mUnavailableAssociatedLayers = Collections.EMPTY_SET;
+ private Set<VmsAssociatedLayer> mAvailableAssociatedLayers = Collections.emptySet();
@GuardedBy("mLock")
private Map<VmsLayer, Set<Integer>> mPotentialLayersAndPublishers = new HashMap<>();
@GuardedBy("mLock")
@@ -63,7 +59,7 @@
/**
* Setting the current layers offerings as reported by publishers.
*/
- public void setPublishersOffering(Collection<VmsLayersOffering> publishersLayersOfferings) {
+ void setPublishersOffering(Collection<VmsLayersOffering> publishersLayersOfferings) {
synchronized (mLock) {
reset();
@@ -72,22 +68,12 @@
VmsLayer layer = dependency.getLayer();
// Associate publishers with layers.
- Set<Integer> curPotentialLayerAndPublishers =
- mPotentialLayersAndPublishers.get(layer);
- if (curPotentialLayerAndPublishers == null) {
- curPotentialLayerAndPublishers = new HashSet<>();
- mPotentialLayersAndPublishers.put(layer, curPotentialLayerAndPublishers);
- }
- curPotentialLayerAndPublishers.add(offering.getPublisherId());
+ mPotentialLayersAndPublishers.computeIfAbsent(layer, k -> new HashSet<>())
+ .add(offering.getPublisherId());
// Add dependencies for availability calculation.
- Set<Set<VmsLayer>> curDependencies =
- mPotentialLayersAndDependencies.get(layer);
- if (curDependencies == null) {
- curDependencies = new HashSet<>();
- mPotentialLayersAndDependencies.put(layer, curDependencies);
- }
- curDependencies.add(dependency.getDependencies());
+ mPotentialLayersAndDependencies.computeIfAbsent(layer, k -> new HashSet<>())
+ .add(dependency.getDependencies());
}
}
calculateLayers();
@@ -97,7 +83,7 @@
/**
* Returns a collection of all the layers which may be published.
*/
- public VmsAvailableLayers getAvailableLayers() {
+ VmsAvailableLayers getAvailableLayers() {
synchronized (mLock) {
return new VmsAvailableLayers(mAvailableAssociatedLayers, mSeq);
}
@@ -107,8 +93,7 @@
synchronized (mLock) {
mPotentialLayersAndDependencies.clear();
mPotentialLayersAndPublishers.clear();
- mAvailableAssociatedLayers = Collections.EMPTY_SET;
- mUnavailableAssociatedLayers = Collections.EMPTY_SET;
+ mAvailableAssociatedLayers = Collections.emptySet();
mSeq += 1;
}
}
@@ -127,14 +112,8 @@
mAvailableAssociatedLayers = Collections.unmodifiableSet(
availableLayersSet
.stream()
- .map(l -> new VmsAssociatedLayer(l, mPotentialLayersAndPublishers.get(l)))
- .collect(Collectors.toSet()));
-
- mUnavailableAssociatedLayers = Collections.unmodifiableSet(
- mPotentialLayersAndDependencies.keySet()
- .stream()
- .filter(l -> !availableLayersSet.contains(l))
- .map(l -> new VmsAssociatedLayer(l, mPotentialLayersAndPublishers.get(l)))
+ .map(l -> new VmsAssociatedLayer(l,
+ mPotentialLayersAndPublishers.get(l)))
.collect(Collectors.toSet()));
}
}
@@ -188,6 +167,5 @@
return;
}
}
- return;
}
}
diff --git a/service/src/com/android/car/vms/VmsProviderInfoStore.java b/service/src/com/android/car/vms/VmsProviderInfoStore.java
new file mode 100644
index 0000000..5a3894c
--- /dev/null
+++ b/service/src/com/android/car/vms/VmsProviderInfoStore.java
@@ -0,0 +1,96 @@
+/*
+ * 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.vms;
+
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+class VmsProviderInfoStore {
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final ArrayMap<InfoWrapper, Integer> mProvidersIds = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private final ArrayList<InfoWrapper> mProvidersInfo = new ArrayList<>();
+
+ private static class InfoWrapper {
+ private final byte[] mInfo;
+
+ InfoWrapper(byte[] info) {
+ mInfo = info;
+ }
+
+ public byte[] getInfo() {
+ return mInfo.clone();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof InfoWrapper)) {
+ return false;
+ }
+ InfoWrapper p = (InfoWrapper) o;
+ return Arrays.equals(this.mInfo, p.mInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mInfo);
+ }
+ }
+
+ /**
+ * Retrieves the provider ID for the given provider information. If the provider information
+ * has not previously been seen, it will be assigned a new provider ID.
+ *
+ * @param providerInfo Provider information to query or register.
+ * @return Provider ID for the given provider information.
+ */
+ int getProviderId(byte[] providerInfo) {
+ Integer providerId;
+ InfoWrapper wrappedProviderInfo = new InfoWrapper(providerInfo);
+ synchronized (mLock) {
+ // Check if provider is already registered
+ providerId = mProvidersIds.get(wrappedProviderInfo);
+ if (providerId == null) {
+ // Add the new provider and assign it the next ID
+ mProvidersInfo.add(wrappedProviderInfo);
+ providerId = mProvidersInfo.size();
+ mProvidersIds.put(wrappedProviderInfo, providerId);
+ }
+ }
+ return providerId;
+ }
+
+ /**
+ * Returns the provider info associated with the given provider ID.
+ * @param providerId Provider ID to query.
+ * @return Provider info associated with the ID, or null if provider ID is unknown.
+ */
+ @Nullable
+ byte[] getProviderInfo(int providerId) {
+ synchronized (mLock) {
+ return providerId < 1 || providerId > mProvidersInfo.size()
+ ? null
+ : mProvidersInfo.get(providerId - 1).getInfo();
+ }
+ }
+}
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 041ba29..8a1ebc7 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -68,7 +68,7 @@
*/
public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase {
- private static final boolean DEBUG = true; // STOPSHIP if true (b/151474489)
+ private static final boolean DEBUG = false; // STOPSHIP if true
private static final String TAG = TAG_WATCHDOG;
private static final int[] ALL_TIMEOUTS =
{ TIMEOUT_CRITICAL, TIMEOUT_MODERATE, TIMEOUT_NORMAL };
diff --git a/surround_view/service-impl/Android.bp b/surround_view/service-impl/Android.bp
index b4dac9f..8ae7bba 100644
--- a/surround_view/service-impl/Android.bp
+++ b/surround_view/service-impl/Android.bp
@@ -70,14 +70,17 @@
srcs: ["lib/arm64/libcore_lib_shared.so"]
},
x86: {
- srcs: ["lib/x86/libcore_lib.so"]
+ srcs: ["lib/x86/libcore_lib_shared.so"]
},
x86_64: {
- srcs: ["lib/x86-64/libcore_lib.so"]
+ srcs: ["lib/x86-64/libcore_lib_shared.so"]
},
},
shared_libs: [
- "libEGL",
+ "libutils",
+ "libcutils",
+ "libbase",
+ "libEGL",
"libGLESv2",
"libGLESv3",
"libc",
diff --git a/surround_view/service-impl/SurroundView2dSession.cpp b/surround_view/service-impl/SurroundView2dSession.cpp
index f1a789b..c94400a 100644
--- a/surround_view/service-impl/SurroundView2dSession.cpp
+++ b/surround_view/service-impl/SurroundView2dSession.cpp
@@ -345,8 +345,15 @@
// description.
mSurroundView = unique_ptr<SurroundView>(Create());
- mSurroundView->SetStaticData(GetCameras(), Get2dParams(), Get3dParams(),
- GetUndistortionScales(), GetBoundingBox());
+ SurroundViewStaticDataParams params =
+ SurroundViewStaticDataParams(GetCameras(),
+ Get2dParams(),
+ Get3dParams(),
+ GetUndistortionScales(),
+ GetBoundingBox(),
+ map<string, CarTexture>(),
+ map<string, CarPart>());
+ mSurroundView->SetStaticData(params);
// TODO(b/150412555): remove after EVS camera is used
mInputPointers = mSurroundView->ReadImages(
diff --git a/surround_view/service-impl/SurroundView3dSession.cpp b/surround_view/service-impl/SurroundView3dSession.cpp
index 0204b55..88913af 100644
--- a/surround_view/service-impl/SurroundView3dSession.cpp
+++ b/surround_view/service-impl/SurroundView3dSession.cpp
@@ -19,9 +19,11 @@
#include <android/hardware_buffer.h>
#include <android/hidl/memory/1.0/IMemory.h>
#include <hidlmemory/mapping.h>
-#include <set>
#include <utils/SystemClock.h>
+#include <array>
+#include <set>
+
#include "SurroundView3dSession.h"
#include "sv_3d_params.h"
@@ -37,6 +39,7 @@
static const uint8_t kGrayColor = 128;
static const int kNumChannels = 4;
+static const int kFrameDelayInMilliseconds = 30;
SurroundView3dSession::SurroundView3dSession() :
mStreamState(STOPPED){
@@ -285,15 +288,18 @@
}
void SurroundView3dSession::generateFrames() {
+ if (mSurroundView->Start3dPipeline()) {
+ LOG(INFO) << "Start3dPipeline succeeded";
+ } else {
+ LOG(ERROR) << "Start3dPipeline failed";
+ return;
+ }
+
int sequenceId = 0;
// TODO(b/150412555): do not use the setViews for frames generation
// since there is a discrepancy between the HIDL APIs and core lib APIs.
- vector<vector<float>> matrix;
- matrix.resize(4);
- for (int i=0; i<4; i++) {
- matrix[i].resize(4);
- }
+ array<array<float, 4>, 4> matrix;
while(true) {
{
@@ -349,9 +355,9 @@
}
}
- // TODO(b/150412555): use hard-coded views for now. Change view every 10
- // frames.
- int recViewId = sequenceId / 10 % 16;
+ // TODO(b/150412555): use hard-coded views for now. Change view every
+ // frame.
+ int recViewId = sequenceId % 16;
for (int i=0; i<4; i++)
for (int j=0; j<4; j++) {
matrix[i][j] = kRecViews[recViewId][i*4+j];
@@ -426,6 +432,11 @@
mStream->receiveFrames(framesRecord.frames);
}
}
+
+ // TODO(b/150412555): adding delays explicitly. This delay should be
+ // removed when EVS camera is used.
+ this_thread::sleep_for(chrono::milliseconds(
+ kFrameDelayInMilliseconds));
}
// If we've been asked to stop, send an event to signal the actual end of stream
@@ -442,8 +453,15 @@
// description.
mSurroundView = unique_ptr<SurroundView>(Create());
- mSurroundView->SetStaticData(GetCameras(), Get2dParams(), Get3dParams(),
- GetUndistortionScales(), GetBoundingBox());
+ SurroundViewStaticDataParams params =
+ SurroundViewStaticDataParams(GetCameras(),
+ Get2dParams(),
+ Get3dParams(),
+ GetUndistortionScales(),
+ GetBoundingBox(),
+ map<string, CarTexture>(),
+ map<string, CarPart>());
+ mSurroundView->SetStaticData(params);
// TODO(b/150412555): remove after EVS camera is used
mInputPointers = mSurroundView->ReadImages(
@@ -491,13 +509,6 @@
return false;
}
- if (mSurroundView->Start3dPipeline()) {
- LOG(INFO) << "Start3dPipeline succeeded";
- } else {
- LOG(ERROR) << "Start3dPipeline failed";
- return false;
- }
-
mIsInitialized = true;
return true;
}
diff --git a/surround_view/service-impl/core_lib.h b/surround_view/service-impl/core_lib.h
index 04ff43a..c2d31af 100644
--- a/surround_view/service-impl/core_lib.h
+++ b/surround_view/service-impl/core_lib.h
@@ -1,7 +1,10 @@
#ifndef WIRELESS_ANDROID_AUTOMOTIVE_CAML_SURROUND_VIEW_CORE_LIB_H_
#define WIRELESS_ANDROID_AUTOMOTIVE_CAML_SURROUND_VIEW_CORE_LIB_H_
+#include <array>
#include <cstdint>
+#include <map>
+#include <string>
#include <vector>
namespace android_auto {
@@ -341,25 +344,14 @@
// RGBA values, A is used for transparency.
uint8_t rgba[4];
- // normalized texture coordinates, in width and height direction. Range [0,
- // 1].
- float tex[2];
-
- // normalized vertex normal.
- float nor[3];
-
bool operator==(const OverlayVertex& rhs) const {
return (0 == std::memcmp(pos, rhs.pos, 3 * sizeof(float))) &&
- (0 == std::memcmp(rgba, rhs.rgba, 4 * sizeof(uint8_t))) &&
- (0 == std::memcmp(tex, rhs.tex, 2 * sizeof(float))) &&
- (0 == std::memcmp(nor, rhs.nor, 3 * sizeof(float)));
+ (0 == std::memcmp(rgba, rhs.rgba, 4 * sizeof(uint8_t)));
}
OverlayVertex& operator=(const OverlayVertex& rhs) {
std::memcpy(pos, rhs.pos, 3 * sizeof(float));
std::memcpy(rgba, rhs.rgba, 4 * sizeof(uint8_t));
- std::memcpy(tex, rhs.tex, 2 * sizeof(float));
- std::memcpy(nor, rhs.nor, 3 * sizeof(float));
return *this;
}
};
@@ -386,12 +378,253 @@
}
};
+// ----------- Structs related to car model ---------------
+
+// 3D Vertex of a car model with normal and optionally texture coordinates.
+struct CarVertex {
+ // 3d position in (x, y, z).
+ std::array<float, 3> pos;
+
+ // unit normal at vertex, used for diffuse shading.
+ std::array<float, 3> normal;
+
+ // texture coordinates, valid in range [0, 1]. (-1, -1) implies no
+ // texture sampling. Note: only a single texture coordinate is currently
+ // supported per vertex. This struct will need to be extended with another
+ // tex_coord if multiple textures are needed per vertex.
+ std::array<float, 2> tex_coord;
+
+ // Default constructor.
+ CarVertex() {
+ pos = {0, 0, 0};
+ normal = {1, 0, 0};
+ tex_coord = {-1.0f, -1.0f};
+ }
+
+ CarVertex(const std::array<float, 3>& _pos,
+ const std::array<float, 3>& _normal,
+ const std::array<float, 2> _tex_coord)
+ : pos(_pos), normal(_normal), tex_coord(_tex_coord) {}
+};
+
+// Type of texture (color, bump, procedural etc.)
+// Currently only color is supported.
+enum CarTextureType : uint32_t {
+ // Texture map is applied to all color parameters: Ka, Kd and Ks.
+ // Data type of texture is RGB with each channel a uint8_t.
+ kKa = 0,
+ kKd,
+ kKs,
+
+ // Texture for bump maps. Data type is 3 channel float.
+ kBumpMap
+};
+
+// Textures to be used for rendering car model.
+struct CarTexture {
+ // Type and number of channels are dependant on each car texture type.
+ int width;
+ int height;
+ int channels;
+ int bytes_per_channel;
+ uint8_t* data;
+
+ CarTexture() {
+ width = 0;
+ height = 0;
+ channels = 0;
+ bytes_per_channel = 0;
+ data = nullptr;
+ }
+};
+
+// Material parameters for a car part.
+// Refer to MTL properties: http://paulbourke.net/dataformats/mtl/
+struct CarMaterial {
+ // Illumination model - 0, 1, 2 currently supported
+ // 0 = Color on and Ambient off
+ // 1 = Color on and Ambient on
+ // 2 = Highlight on
+ // 3 = Reflection on and Ray trace on
+ // 4 - 10 = Reflection/Transparency options not supported,
+ // Will default to option 3.
+ uint8_t illum;
+
+ std::array<float, 3> ka; // Ambient RGB [0, 1]
+ std::array<float, 3> kd; // Diffuse RGB [0, 1]
+ std::array<float, 3> ks; // Specular RGB [0, 1]
+
+ // Dissolve factor [0, 1], 0 = full transparent, 1 = full opaque.
+ float d;
+
+ // Specular exponent typically range from 0 to 1000.
+ // A high exponent results in a tight, concentrated highlight.
+ float ns;
+
+ // Set default values of material.
+ CarMaterial() {
+ illum = 0; // Color on, ambient off
+ ka = {0.0f, 0.0f, 0.0f}; // No ambient.
+ kd = {0.0f, 0.0f, 0.0f}; // No dissolve.
+ ks = {0.0f, 0.0f, 0.0f}; // No specular.
+ d = 1.0f; // Fully opaque.
+ ns = 0; // No specular exponent.
+ }
+
+ // Map for texture type to a string id of a texture.
+ std::map<CarTextureType, std::string> textures;
+};
+
+// Type alias for 4x4 homogenous matrix, in row-major order.
+using Mat4x4 = std::array<float, 16>;
+
+// Represents a part of a car model.
+// Each car part is a object in the car that is individually animated and
+// has the same illumination properties. A car part may contain sub parts.
+struct CarPart {
+ // Car part vertices.
+ std::vector<CarVertex> vertices;
+
+ // Properties/attributes describing car material.
+ CarMaterial material;
+
+ // Model matrix to transform the car part from object space to its parent's
+ // coordinate space.
+ // The car's vertices are transformed by performing:
+ // parent_model_mat * model_mat * car_part_vertices to transform them to the
+ // global coordinate space.
+ // Model matrix must be a homogenous matrix with orthogonal rotation matrix.
+ Mat4x4 model_mat;
+
+ // Id of parent part. Parent part's model matrix is used to animate this part.
+ // empty string implies the part has no parent.
+ std::string parent_part_id;
+
+ // Ids of child parts. If current part is animated all its child parts
+ // are animated as well. Empty vector implies part has not children.
+ std::vector<std::string> child_part_ids;
+
+ CarPart(const std::vector<CarVertex>& car_vertices,
+ const CarMaterial& car_material, const Mat4x4& car_model_mat,
+ std::string car_parent_part_id,
+ const std::vector<std::string>& car_child_part_ids)
+ : vertices(car_vertices),
+ material(car_material),
+ model_mat(car_model_mat),
+ parent_part_id(car_parent_part_id),
+ child_part_ids(car_child_part_ids) {}
+
+ CarPart& operator=(const CarPart& car_part) {
+ this->vertices = car_part.vertices;
+ this->material = car_part.material;
+ this->model_mat = car_part.model_mat;
+ this->parent_part_id = car_part.parent_part_id;
+ this->child_part_ids = car_part.child_part_ids;
+ return *this;
+ }
+};
+
+struct AnimationParam {
+ // part id
+ std::string part_id;
+
+ // model matrix.
+ Mat4x4 model_matrix;
+
+ // bool flag indicating if the model matrix is updated from last
+ // SetAnimations() call.
+ bool is_model_update;
+
+ // gamma.
+ float gamma;
+
+ // bool flag indicating if gamma is updated from last
+ // SetAnimations() call.
+ bool is_gamma_update;
+
+ // texture id.
+ std::string texture_id;
+
+ // bool flag indicating if texture is updated from last
+ // SetAnimations() call.
+ bool is_texture_update;
+
+ // Default constructor, no animations are updated.
+ AnimationParam() {
+ is_model_update = false;
+ is_gamma_update = false;
+ is_texture_update = false;
+ }
+
+ // Constructor with car part name.
+ explicit AnimationParam(const std::string& _part_id)
+ : part_id(_part_id),
+ is_model_update(false),
+ is_gamma_update(false),
+ is_texture_update(false) {}
+
+ void SetModelMatrix(const Mat4x4& model_mat) {
+ is_model_update = true;
+ model_matrix = model_mat;
+ }
+
+ void SetGamma(float gamma_value) {
+ is_gamma_update = true;
+ gamma = gamma_value;
+ }
+
+ void SetTexture(const std::string& tex_id) {
+ is_texture_update = true;
+ texture_id = tex_id;
+ }
+};
+
enum Format {
GRAY = 0,
RGB = 1,
RGBA = 2,
};
+// collection of surround view static data params.
+struct SurroundViewStaticDataParams {
+ std::vector<SurroundViewCameraParams> cameras_params;
+
+ // surround view 2d parameters.
+ SurroundView2dParams surround_view_2d_params;
+
+ // surround view 3d parameters.
+ SurroundView3dParams surround_view_3d_params;
+
+ // undistortion focal length scales.
+ std::vector<float> undistortion_focal_length_scales;
+
+ // car model bounding box for 2d surround view.
+ BoundingBox car_model_bb;
+
+ // map of texture name to a car texture. Lists all textures to be
+ // used for car model rendering.
+ std::map<std::string, CarTexture> car_textures;
+
+ // map of car id to a car part. Lists all car parts to be used
+ // for car model rendering.
+ std::map<std::string, CarPart> car_parts;
+
+ SurroundViewStaticDataParams(
+ const std::vector<SurroundViewCameraParams>& sv_cameras_params,
+ const SurroundView2dParams& sv_2d_params,
+ const SurroundView3dParams& sv_3d_params,
+ const std::vector<float>& scales, const BoundingBox& bb,
+ const std::map<std::string, CarTexture>& textures,
+ const std::map<std::string, CarPart>& parts)
+ : cameras_params(sv_cameras_params),
+ surround_view_2d_params(sv_2d_params),
+ surround_view_3d_params(sv_3d_params),
+ undistortion_focal_length_scales(scales),
+ car_model_bb(bb),
+ car_textures(textures),
+ car_parts(parts) {}
+};
+
struct SurroundViewInputBufferPointers {
void* gpu_data_pointer;
void* cpu_data_pointer;
@@ -418,17 +651,41 @@
Format format;
int width;
int height;
- SurroundViewResultPointer() : data_pointer(nullptr), width(0), height(0) {}
+ bool is_data_preallocated;
+ SurroundViewResultPointer()
+ : data_pointer(nullptr),
+ width(0),
+ height(0),
+ is_data_preallocated(false) {}
+
+ // Constructor with result data pointer being allocated within core lib.
+ // Use for cases when no already existing buffer is available.
SurroundViewResultPointer(Format format_, int width_, int height_)
: format(format_), width(width_), height(height_) {
// default formate is gray.
const int byte_per_pixel = format_ == RGB ? 3 : format_ == RGBA ? 4 : 1;
data_pointer =
static_cast<void*>(new char[width * height * byte_per_pixel]);
+ is_data_preallocated = false;
}
+
+ // Constructor with pre-allocated data.
+ // Use for cases when results must be added to an existing allocated buffer.
+ // Example, pre-allocated buffer of a display.
+ SurroundViewResultPointer(void* data_pointer_, Format format_, int width_,
+ int height_)
+ : data_pointer(data_pointer_),
+ format(format_),
+ width(width_),
+ height(height_),
+ is_data_preallocated(true) {}
+
~SurroundViewResultPointer() {
if (data_pointer) {
- // delete[] static_cast<char*>(data_pointer);
+ // TODO(b/154365307): Fix freeing up of pre-allocated memory.
+ // if (!is_data_preallocated) {
+ // delete[] static_cast<char*>(data_pointer);
+ // }
data_pointer = nullptr;
}
}
@@ -439,13 +696,10 @@
virtual ~SurroundView() = default;
// Sets SurroundView static data.
- // For each input, please refer to the definition.
+ // For details of SurroundViewStaticDataParams, please refer to the
+ // definition.
virtual bool SetStaticData(
- const std::vector<SurroundViewCameraParams>& cameras_params,
- const SurroundView2dParams& surround_view_2d_params,
- const SurroundView3dParams& surround_view_3d_params,
- const std::vector<float>& undistortion_focal_length_scales,
- const BoundingBox& car_model_bb) = 0;
+ const SurroundViewStaticDataParams& static_data_params) = 0;
// Starts 2d pipeline. Returns false if error occurs.
virtual bool Start2dPipeline() = 0;
@@ -461,29 +715,29 @@
virtual void Stop3dPipeline() = 0;
// Updates 2d output resolution on-the-fly. Starts2dPipeline() must be called
- // before this can be called. For quality assurance, the resolution should not
- // be larger than the original one. This call is not thread safe and there is
- // no sync between Get2dSurroundView() and this call.
+ // before this can be called. For quality assurance, the |resolution| should
+ // not be larger than the original one. This call is not thread safe and there
+ // is no sync between Get2dSurroundView() and this call.
virtual bool Update2dOutputResolution(const Size2dInteger& resolution) = 0;
// Updates 3d output resolution on-the-fly. Starts3dPipeline() must be called
- // before this can be called. For quality assurance, the resolution should not
- // be larger than the original one. This call is not thread safe and there is
- // no sync between Get3dSurroundView() and this call.
+ // before this can be called. For quality assurance, the |resolution| should
+ // not be larger than the original one. This call is not thread safe and there
+ // is no sync between Get3dSurroundView() and this call.
virtual bool Update3dOutputResolution(const Size2dInteger& resolution) = 0;
// Projects camera's pixel location to surround view 2d image location.
- // camera_point is the pixel location in raw camera's space.
- // camera_index is the camera's index.
- // surround_view_2d_point is the surround view 2d image pixel location.
+ // |camera_point| is the pixel location in raw camera's space.
+ // |camera_index| is the camera's index.
+ // |surround_view_2d_point| is the surround view 2d image pixel location.
virtual bool GetProjectionPointFromRawCameraToSurroundView2d(
const Coordinate2dInteger& camera_point, int camera_index,
Coordinate2dFloat* surround_view_2d_point) = 0;
// Projects camera's pixel location to surround view 3d bowl coordinate.
- // camera_point is the pixel location in raw camera's space.
- // camera_index is the camera's index.
- // surround_view_3d_point is the surround view 3d vertex.
+ // |camera_point| is the pixel location in raw camera's space.
+ // |camera_index| is the camera's index.
+ // |surround_view_3d_point| is the surround view 3d vertex.
virtual bool GetProjectionPointFromRawCameraToSurroundView3d(
const Coordinate2dInteger& camera_point, int camera_index,
Coordinate3dFloat* surround_view_3d_point) = 0;
@@ -497,19 +751,41 @@
SurroundViewResultPointer* result_pointer) = 0;
// Gets 3d surround view image.
- // It takes input_pointers and view_matrix as input, and output is
- // result_pointer. view_matrix is 4 x 4 matrix.
+ // It takes |input_pointers| and |view_matrix| as input, and output is
+ // |result_pointer|. |view_matrix| is 4 x 4 matrix.
// Please refer to the definition of
// SurroundViewInputBufferPointers and
// SurroundViewResultPointer.
virtual bool Get3dSurroundView(
const std::vector<SurroundViewInputBufferPointers>& input_pointers,
- const std::vector<std::vector<float>> view_matrix,
+ const std::array<std::array<float, 4>, 4>& view_matrix,
+ SurroundViewResultPointer* result_pointer) = 0;
+
+ // Gets 3d surround view image overload.
+ // It takes |input_pointers|, |quaternion| and |translation| as input,
+ // and output is |result_pointer|.
+ // |quaternion| is 4 x 1 array (X, Y, Z, W).
+ // It is required to be unit quaternion as rotation quaternion.
+ // |translation| is 3 X 1 array (x, y, z).
+ // Please refer to the definition of
+ // SurroundViewInputBufferPointers and
+ // SurroundViewResultPointer.
+ virtual bool Get3dSurroundView(
+ const std::vector<SurroundViewInputBufferPointers>& input_pointers,
+ const std::array<float, 4>& quaternion,
+ const std::array<float, 3>& translation,
SurroundViewResultPointer* result_pointer) = 0;
// Sets 3d overlays.
virtual bool Set3dOverlay(const std::vector<Overlay>& overlays) = 0;
+ // Animates a set of car parts.
+ // Only updated car parts are included.
+ // |car_animations| is a vector of AnimationParam specifying updated
+ // car parts with updated animation parameters.
+ virtual bool SetAnimations(
+ const std::vector<AnimationParam>& car_animations) = 0;
+
// for test only.
// TODO(xxqian): remove thest two fns.
virtual std::vector<SurroundViewInputBufferPointers> ReadImages(
diff --git a/surround_view/service-impl/lib/arm64/libcore_lib_shared.so b/surround_view/service-impl/lib/arm64/libcore_lib_shared.so
old mode 100644
new mode 100755
index 0175c16..2421d41
--- a/surround_view/service-impl/lib/arm64/libcore_lib_shared.so
+++ b/surround_view/service-impl/lib/arm64/libcore_lib_shared.so
Binary files differ
diff --git a/surround_view/service-impl/lib/x86-64/libcore_lib.so b/surround_view/service-impl/lib/x86-64/libcore_lib.so
deleted file mode 100755
index 96479c6..0000000
--- a/surround_view/service-impl/lib/x86-64/libcore_lib.so
+++ /dev/null
Binary files differ
diff --git a/surround_view/service-impl/lib/x86-64/libcore_lib_shared.so b/surround_view/service-impl/lib/x86-64/libcore_lib_shared.so
new file mode 100755
index 0000000..34d1f6a
--- /dev/null
+++ b/surround_view/service-impl/lib/x86-64/libcore_lib_shared.so
Binary files differ
diff --git a/surround_view/service-impl/lib/x86/libcore_lib.so b/surround_view/service-impl/lib/x86/libcore_lib.so
deleted file mode 100755
index 34e3bcb..0000000
--- a/surround_view/service-impl/lib/x86/libcore_lib.so
+++ /dev/null
Binary files differ
diff --git a/surround_view/service-impl/lib/x86/libcore_lib_shared.so b/surround_view/service-impl/lib/x86/libcore_lib_shared.so
new file mode 100755
index 0000000..c6ba2b6
--- /dev/null
+++ b/surround_view/service-impl/lib/x86/libcore_lib_shared.so
Binary files differ
diff --git a/tests/CarSecurityPermissionTest/Android.bp b/tests/CarSecurityPermissionTest/Android.bp
index 330568d..066ecc8 100644
--- a/tests/CarSecurityPermissionTest/Android.bp
+++ b/tests/CarSecurityPermissionTest/Android.bp
@@ -31,6 +31,7 @@
"androidx.test.ext.junit",
"androidx.test.rules",
"car-frameworks-service",
+ "compatibility-device-util-axt",
"mockito-target-minus-junit4",
"testng",
"truth-prebuilt",
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
new file mode 100644
index 0000000..86a2853
--- /dev/null
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.user;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_USERS;
+import static android.car.Car.CAR_USER_SERVICE;
+import static android.car.Car.createCar;
+import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_1;
+
+import static com.android.compatibility.common.util.ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.Instrumentation;
+import android.car.Car;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleListener;
+import android.content.Context;
+import android.os.Handler;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Objects;
+
+/**
+ * This class contains security permission tests for the {@link CarUserManager}'s system APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class CarUserManagerPermissionTest {
+ private static final int USRE_TYPE = 1;
+
+ private CarUserManager mCarUserManager;
+ private Context mContext;
+ private Instrumentation mInstrumentation;
+
+ @Before
+ public void setUp() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getTargetContext();
+ Car car = Objects.requireNonNull(createCar(mContext, (Handler) null));
+ mCarUserManager = (CarUserManager) car.getCarManager(CAR_USER_SERVICE);
+
+ }
+
+ @Test
+ public void testSwitchUserPermission() throws Exception {
+ int target_uid = 100;
+ Exception e = expectThrows(SecurityException.class,
+ () -> mCarUserManager.switchUser(target_uid));
+ assertThat(e.getMessage()).contains(MANAGE_USERS);
+ }
+
+ @Test
+ public void testAddListenerPermission() {
+ UserLifecycleListener listener = (e) -> { };
+
+ Exception e = expectThrows(SecurityException.class,
+ () -> mCarUserManager.addListener(Runnable::run, listener));
+
+ assertThat(e.getMessage()).contains(INTERACT_ACROSS_USERS);
+ assertThat(e.getMessage()).contains(INTERACT_ACROSS_USERS_FULL);
+ }
+
+ @Test
+ public void testRemoveListenerPermission() throws Exception {
+ UserLifecycleListener listener = (e) -> { };
+ invokeMethodWithShellPermissionsNoReturn(mCarUserManager,
+ (um) -> um.addListener(Runnable::run, listener));
+
+ Exception e = expectThrows(SecurityException.class,
+ () -> mCarUserManager.removeListener(listener));
+
+ assertThat(e.getMessage()).contains(INTERACT_ACROSS_USERS);
+ assertThat(e.getMessage()).contains(INTERACT_ACROSS_USERS_FULL);
+ }
+
+ @Test
+ public void testGetUserIdentificationAssociationPermission() {
+ Exception e = expectThrows(SecurityException.class,
+ () -> mCarUserManager.getUserIdentificationAssociation(CUSTOM_1));
+ assertThat(e.getMessage()).contains(MANAGE_USERS);
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociationPermission() {
+ Exception e = expectThrows(SecurityException.class,
+ () -> mCarUserManager.setUserIdentificationAssociation(
+ new int[] {CUSTOM_1}, new int[] {42}));
+ assertThat(e.getMessage()).contains(MANAGE_USERS);
+ }
+
+ @Test
+ public void testSetUserSwitchUiCallback() {
+ Exception e = expectThrows(SecurityException.class,
+ () -> mCarUserManager.getUserIdentificationAssociation(CUSTOM_1));
+ assertThat(e.getMessage()).contains(MANAGE_USERS);
+ }
+}
diff --git a/tests/SecondaryHomeApp/Android.bp b/tests/SecondaryHomeApp/Android.bp
index ce8e763..04da06b 100644
--- a/tests/SecondaryHomeApp/Android.bp
+++ b/tests/SecondaryHomeApp/Android.bp
@@ -22,14 +22,10 @@
],
static_libs: [
- "android.car.userlib",
"androidx.appcompat_appcompat",
- "androidx.recyclerview_recyclerview",
- "androidx.legacy_legacy-support-v4",
"androidx.lifecycle_lifecycle-extensions",
"com.google.android.material_material",
"CarNotificationLib",
- "car-ui-lib"
],
libs: [
diff --git a/tests/SecondaryHomeApp/AndroidManifest.xml b/tests/SecondaryHomeApp/AndroidManifest.xml
index 0a84531..5d22b70 100644
--- a/tests/SecondaryHomeApp/AndroidManifest.xml
+++ b/tests/SecondaryHomeApp/AndroidManifest.xml
@@ -45,6 +45,7 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.SECONDARY_HOME"/>
</intent-filter>
</activity>
<service android:name=".launcher.NotificationListener"
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java b/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java
index 93f2530..ee6dbbb 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java
@@ -24,7 +24,6 @@
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import org.junit.Test;
@@ -42,7 +41,6 @@
private static final int NONEXISTENT_DISPLAY_ID = Integer.MAX_VALUE;
@Test
- @FlakyTest
public void testSingleActivityView() throws Exception {
ActivityViewTestActivity activity = startActivityViewTestActivity(DEFAULT_DISPLAY);
activity.waitForActivityViewReady();
@@ -65,7 +63,6 @@
}
@Test
- @FlakyTest
public void testDoubleActivityView() throws Exception {
ActivityViewTestActivity activity1 = startActivityViewTestActivity(DEFAULT_DISPLAY);
activity1.waitForActivityViewReady();
@@ -100,7 +97,6 @@
}
@Test
- @FlakyTest
public void testThrowsExceptionOnReportingNonExistingDisplay() throws Exception {
ActivityViewTestActivity activity = startActivityViewTestActivity(DEFAULT_DISPLAY);
activity.waitForActivityViewReady();
@@ -125,7 +121,6 @@
// TODO(b/143353546): Make the following tests not to rely on CarLauncher.
@Test
- @FlakyTest
public void testThrowsExceptionOnReportingNonOwningDisplay() throws Exception {
int displayIdOfCarLauncher = waitForActivityViewDisplayReady(CAR_LAUNCHER_PKG_NAME);
assumeTrue(INVALID_DISPLAY != displayIdOfCarLauncher);
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarBugreportManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarBugreportManagerTest.java
new file mode 100644
index 0000000..42406ca
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/CarBugreportManagerTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.apitest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.testng.Assert.expectThrows;
+
+import android.Manifest;
+import android.annotation.FloatRange;
+import android.car.Car;
+import android.car.CarBugreportManager;
+import android.car.CarBugreportManager.CarBugreportManagerCallback;
+import android.os.ParcelFileDescriptor;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class CarBugreportManagerTest extends CarApiTestBase {
+ // TODO: Use "dumpstate.dry_run" to make dumpstate faster.
+ // dumpstate runs around 3 minutes on emulator on a pretty fast computer.
+ private static final int BUGREPORT_TIMEOUT_MILLIS = 360_000;
+ private static final int NO_ERROR = -1;
+
+ @Rule public TestName testName = new TestName();
+
+ private CarBugreportManager mManager;
+ private FakeCarBugreportCallback mFakeCallback;
+ private ParcelFileDescriptor mOutput;
+ private ParcelFileDescriptor mExtraOutput;
+
+ @Before
+ public void setUp() throws Exception {
+ mManager = (CarBugreportManager) getCar().getCarManager(Car.CAR_BUGREPORT_SERVICE);
+ mFakeCallback = new FakeCarBugreportCallback();
+ mOutput = createParcelFdInCache("bugreport", "zip");
+ mExtraOutput = createParcelFdInCache("screenshot", "png");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ getPermissions(); // For cancelBugreport()
+ mManager.cancelBugreport();
+ dropPermissions();
+ }
+
+ @Test
+ public void test_requestBugreport_failsWhenNoPermission() throws Exception {
+ dropPermissions();
+
+ SecurityException expected =
+ expectThrows(SecurityException.class,
+ () -> mManager.requestBugreport(mOutput, mExtraOutput, mFakeCallback));
+ assertThat(expected).hasMessageThat().contains(
+ "nor current process has android.permission.DUMP.");
+ }
+
+ @Test
+ @LargeTest
+ public void test_requestBugreport_works() throws Exception {
+ getPermissions();
+
+ mManager.requestBugreport(mOutput, mExtraOutput, mFakeCallback);
+
+ // The FDs are duped and closed in requestBugreport().
+ assertFdIsClosed(mOutput);
+ assertFdIsClosed(mExtraOutput);
+
+ mFakeCallback.waitTillDoneOrTimeout(BUGREPORT_TIMEOUT_MILLIS);
+ assertThat(mFakeCallback.isFinishedSuccessfully()).isEqualTo(true);
+ assertThat(mFakeCallback.getReceivedProgress()).isTrue();
+ // TODO: Check the contents of the zip file and the extra output.
+ }
+
+ @Test
+ public void test_requestBugreport_cannotRunMultipleBugreports() throws Exception {
+ getPermissions();
+ FakeCarBugreportCallback callback2 = new FakeCarBugreportCallback();
+ ParcelFileDescriptor output2 = createParcelFdInCache("bugreport2", "zip");
+ ParcelFileDescriptor extraOutput2 = createParcelFdInCache("screenshot2", "png");
+
+ // 1st bugreport.
+ mManager.requestBugreport(mOutput, mExtraOutput, mFakeCallback);
+
+ // 2nd bugreport.
+ mManager.requestBugreport(output2, extraOutput2, callback2);
+
+ callback2.waitTillDoneOrTimeout(BUGREPORT_TIMEOUT_MILLIS);
+ assertThat(callback2.getErrorCode()).isEqualTo(
+ CarBugreportManagerCallback.CAR_BUGREPORT_IN_PROGRESS);
+ }
+
+ @Test
+ @LargeTest
+ public void test_cancelBugreport_works() throws Exception {
+ getPermissions();
+ FakeCarBugreportCallback callback2 = new FakeCarBugreportCallback();
+ ParcelFileDescriptor output2 = createParcelFdInCache("bugreport2", "zip");
+ ParcelFileDescriptor extraOutput2 = createParcelFdInCache("screenshot2", "png");
+
+ // 1st bugreport.
+ mManager.requestBugreport(mOutput, mExtraOutput, mFakeCallback);
+ mManager.cancelBugreport();
+
+ // Allow the system to finish the bugreport cancellation, 0.5 seconds is enough.
+ Thread.sleep(500);
+
+ // 2nd bugreport must work, because 1st bugreport was cancelled.
+ mManager.requestBugreport(output2, extraOutput2, callback2);
+
+ callback2.waitTillProgressOrTimeout(BUGREPORT_TIMEOUT_MILLIS);
+ assertThat(callback2.getErrorCode()).isEqualTo(NO_ERROR);
+ assertThat(callback2.getReceivedProgress()).isEqualTo(true);
+ }
+
+ private static void getPermissions() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.DUMP);
+ }
+
+ private static void dropPermissions() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ /** Creates a temp file in cache dir and returns file descriptor. */
+ private ParcelFileDescriptor createParcelFdInCache(String prefix, String extension)
+ throws Exception {
+ File f = File.createTempFile(
+ prefix + "_" + testName.getMethodName(), extension, getContext().getCacheDir());
+ f.setReadable(true, true);
+ f.setWritable(true, true);
+
+ return ParcelFileDescriptor.open(f,
+ ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
+ }
+
+ private static void assertFdIsClosed(ParcelFileDescriptor pfd) {
+ try {
+ int fd = pfd.getFd();
+ fail("Expected ParcelFileDescriptor argument to be closed, but got: " + fd);
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ private static class FakeCarBugreportCallback extends CarBugreportManagerCallback {
+ private final Object mLock = new Object();
+ private final CountDownLatch mEndedLatch = new CountDownLatch(1);
+ private final CountDownLatch mProgressLatch = new CountDownLatch(1);
+ private boolean mReceivedProgress = false;
+ private int mErrorCode = NO_ERROR;
+
+ @Override
+ public void onProgress(@FloatRange(from = 0f, to = 100f) float progress) {
+ synchronized (mLock) {
+ mReceivedProgress = true;
+ }
+ mProgressLatch.countDown();
+ }
+
+ @Override
+ public void onError(
+ @CarBugreportManagerCallback.CarBugreportErrorCode int errorCode) {
+ synchronized (mLock) {
+ mErrorCode = errorCode;
+ }
+ mEndedLatch.countDown();
+ mProgressLatch.countDown();
+ }
+
+ @Override
+ public void onFinished() {
+ mEndedLatch.countDown();
+ mProgressLatch.countDown();
+ }
+
+ int getErrorCode() {
+ synchronized (mLock) {
+ return mErrorCode;
+ }
+ }
+
+ boolean getReceivedProgress() {
+ synchronized (mLock) {
+ return mReceivedProgress;
+ }
+ }
+
+ boolean isFinishedSuccessfully() {
+ return mEndedLatch.getCount() == 0 && getErrorCode() == NO_ERROR;
+ }
+
+ void waitTillDoneOrTimeout(long timeoutMillis) throws InterruptedException {
+ mEndedLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
+ }
+
+ void waitTillProgressOrTimeout(long timeoutMillis) throws InterruptedException {
+ mProgressLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
+ }
+ }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarNavigationManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarNavigationManagerTest.java
index 0d92400..6732c3d 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarNavigationManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarNavigationManagerTest.java
@@ -43,7 +43,6 @@
import com.google.android.collect.Lists;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
/**
@@ -112,7 +111,6 @@
assertThat(NavigationStateProto.parseFrom(navigationStateProto.toByteArray())).isNotNull();
}
- @Ignore("TODO(b/15534360)")
@Test
public void testSendEvent() throws Exception {
if (mCarNavigationManager == null) {
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
index e18a200..ab13c5f 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
@@ -58,14 +58,14 @@
private static final String TAG = CarUserManagerTest.class.getSimpleName();
- private static final int SWITCH_TIMEOUT_MS = 40_000;
+ private static final int SWITCH_TIMEOUT_MS = 70_000;
private static final int STOP_TIMEOUT_MS = 300_000;
/**
* Stopping the user takes a while, even when calling force stop - change it to false if this
* test becomes flaky.
*/
- private static final boolean TEST_STOP = true;
+ private static final boolean TEST_STOP = false;
private static final UserManager sUserManager = UserManager.get(sContext);
@@ -75,7 +75,7 @@
private CarUserManager mCarUserManager;
@BeforeClass
- public static void createUserFixture() {
+ public static void setupUsers() {
sInitialUserId = ActivityManager.getCurrentUser();
Log.i(TAG, "Running test as user " + sInitialUserId);
@@ -83,7 +83,9 @@
}
@AfterClass
- public static void removeUserFixture() {
+ public static void cleanupUsers() {
+ switchUserDirectly(sInitialUserId);
+
if (sNewUserId == UserHandle.USER_NULL) {
Log.w(TAG, "No need to remove user" + sNewUserId);
return;
@@ -108,7 +110,8 @@
int oldUserId = sInitialUserId;
int newUserId = sNewUserId;
- BlockingUserLifecycleListener startListener = new BlockingUserLifecycleListener.Builder()
+ BlockingUserLifecycleListener startListener = BlockingUserLifecycleListener
+ .forSpecificEvents()
.forUser(newUserId)
.setTimeout(SWITCH_TIMEOUT_MS)
.addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
@@ -152,7 +155,8 @@
Log.d(TAG, "unregistering start listener: " + startListener);
mCarUserManager.removeListener(startListener);
- BlockingUserLifecycleListener stopListener = new BlockingUserLifecycleListener.Builder()
+ BlockingUserLifecycleListener stopListener = BlockingUserLifecycleListener
+ .forSpecificEvents()
.forUser(newUserId)
.setTimeout(STOP_TIMEOUT_MS)
.addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING)
@@ -165,16 +169,13 @@
// Switch back to the previous user
switchUser(oldUserId);
- // Must force stop the user, otherwise it can take minutes for its process to finish
- forceStopUser(newUserId);
-
if (TEST_STOP) {
- // waitForEvents() will also return events for previous user...
- List<UserLifecycleEvent> allEvents = stopListener.waitForEvents();
- Log.d(TAG, "All received events on stopListener: " + allEvents);
- //... so we need to check for just the epected events
- List<UserLifecycleEvent> stopEvents = stopListener.getExpectedEventsReceived();
- Log.d(TAG, "Relevant stop events: " + stopEvents);
+ // Must force stop the user, otherwise it can take minutes for its process to finish
+ forceStopUser(newUserId);
+
+ List<UserLifecycleEvent> stopEvents = stopListener.waitForEvents();
+ Log.d(TAG, "stopEvents: " + stopEvents + "; all events on stop listener: "
+ + stopListener.getAllReceivedEvents());
// Assert user ids
for (UserLifecycleEvent event : stopEvents) {
@@ -183,13 +184,15 @@
assertWithMessage("wrong userHandle on %s", event)
.that(event.getUserHandle().getIdentifier()).isEqualTo(newUserId);
}
+ } else {
+ Log.w(TAG, "NOT testing user stop events");
}
// Make sure unregistered listener din't receive any more events
List<UserLifecycleEvent> allStartEvents = startListener.getAllReceivedEvents();
Log.d(TAG, "All start events: " + startEvents);
- assertThat(allStartEvents).isSameAs(startEvents);
+ assertThat(allStartEvents).containsAllIn(startEvents).inOrder();
Log.d(TAG, "unregistering stop listener: " + stopListener);
mCarUserManager.removeListener(stopListener);
diff --git a/tests/android_car_api_test/src/android/car/apitest/PreInstalledPackagesTest.java b/tests/android_car_api_test/src/android/car/apitest/PreInstalledPackagesTest.java
new file mode 100644
index 0000000..5693800
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/PreInstalledPackagesTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.apitest;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static org.junit.Assert.fail;
+
+import android.text.TextUtils;
+
+import org.junit.Test;
+
+public final class PreInstalledPackagesTest {
+
+ @Test
+ public void testNoCriticalErrors_currentMode() {
+ assertNoCriticalErrors(/* enforceMode= */ false);
+ }
+
+ @Test
+ public void testNoCriticalErrors_enforceMode() {
+ assertNoCriticalErrors(/* enforceMode= */ true);
+ }
+
+ private static void assertNoCriticalErrors(boolean enforceMode) {
+ String cmd = "cmd user report-system-user-package-whitelist-problems --critical-only%s";
+ String mode = enforceMode ? " --mode 1" : "";
+ String result = runShellCommand(cmd, mode);
+ if (!TextUtils.isEmpty(result)) {
+ fail("Command '" + cmd + " reported errors:\n" + result);
+ }
+ }
+}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioFocusTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioFocusTest.java
index fcbaefe..f855f2d 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioFocusTest.java
@@ -26,6 +26,7 @@
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Looper;
+import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.FlakyTest;
@@ -34,15 +35,22 @@
import com.android.car.R;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class CarAudioFocusTest {
+
+ private static final String TAG = CarAudioFocusTest.class.getSimpleName();
+ private static final boolean DEBUG = false;
private static final long TEST_TIMING_TOLERANCE_MS = 100;
private static final int TEST_TORELANCE_MAX_ITERATIONS = 5;
private static final int INTERACTION_REJECT = 0; // Focus not granted
@@ -115,6 +123,8 @@
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
+ private final Set<AudioFocusRequest> mAudioFocusRequestsSet = new HashSet<>();
+
private AudioManager mAudioManager;
@Before
@@ -128,8 +138,21 @@
isDynamicRoutingEnabled);
}
+ @After
+ public void cleanUp() {
+ Iterator<AudioFocusRequest> iterator = mAudioFocusRequestsSet.iterator();
+ while (iterator.hasNext()) {
+ AudioFocusRequest request = iterator.next();
+ mAudioManager.abandonAudioFocusRequest(request);
+ if (DEBUG) {
+ Log.d(TAG, "cleanUp Removing: "
+ + request.getAudioAttributes().usageToString());
+ }
+ }
+ }
+
@Test
- public void individualAttributeFocusRequest_focusRequestGranted() throws Exception {
+ public void individualAttributeFocusRequest_focusRequestGranted() {
// Make sure each usage is able to request and release audio focus individually
requestAndLoseFocusForAttribute(ATTR_INVALID);
requestAndLoseFocusForAttribute(ATTR_MEDIA);
@@ -148,8 +171,7 @@
@Test
@FlakyTest
- public void exclusiveInteractionsForFocusGain_requestGrantedAndFocusLossSent()
- throws Exception {
+ public void exclusiveInteractionsForFocusGain_requestGrantedAndFocusLossSent() {
// For each interaction the focus request is granted and on the second request
// focus lost is dispatched to the first focus listener
@@ -161,8 +183,7 @@
}
@Test
- public void exclusiveInteractionsTransient_requestGrantedAndFocusLossSent()
- throws Exception {
+ public void exclusiveInteractionsTransient_requestGrantedAndFocusLossSent() {
// For each interaction the focus request is granted and on the second request
// focus lost transient is dispatched to the first focus listener
@@ -176,8 +197,7 @@
@RequiresDevice
@Test
- public void exclusiveInteractionsTransientMayDuck_requestGrantedAndFocusLossSent()
- throws Exception {
+ public void exclusiveInteractionsTransientMayDuck_requestGrantedAndFocusLossSent() {
// For each interaction the focus request is granted and on the second request
// focus lost transient is dispatched to the first focus listener
@@ -191,7 +211,7 @@
@RequiresDevice
@Test
- public void rejectedInteractions_focusRequestRejected() throws Exception {
+ public void rejectedInteractions_focusRequestRejected() {
// Test different paired interaction between different usages
// for each interaction pair the first focus request will be granted but the second
// will be rejected
@@ -257,7 +277,7 @@
}
@Test
- public void concurrentInteractionsFocusGain_requestGrantedAndFocusLossSent() throws Exception {
+ public void concurrentInteractionsFocusGain_requestGrantedAndFocusLossSent() {
// Test concurrent interactions i.e. interactions that can
// potentially gain focus at the same time.
// For this test permanent focus gain is requested by two usages.
@@ -283,8 +303,7 @@
@RequiresDevice
@Test
- public void concurrentInteractionsTransientGainMayDuck_requestGrantedAndNoFocusLossSent()
- throws Exception {
+ public void concurrentInteractionsTransientGainMayDuck_requestGrantedAndNoFocusLossSent() {
// Test concurrent interactions i.e. interactions that can
// potentially gain focus at the same time.
// For this test permanent focus gain is requested by first usage and focus gain transient
@@ -296,8 +315,7 @@
testConcurrentInteractions(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, true);
}
- private void testConcurrentInteractions(int gain, boolean pauseForDucking)
- throws Exception {
+ private void testConcurrentInteractions(int gain, boolean pauseForDucking) {
// Test paired concurrent interactions i.e. interactions that can
// potentially gain focus at the same time.
int interaction = INTERACTION_CONCURRENT;
@@ -400,8 +418,7 @@
testInteraction(ATTR_ANNOUNCEMENT, ATTR_VEHICLE_STATUS, interaction, gain, pauseForDucking);
}
- private void testExclusiveInteractions(int gain, boolean pauseForDucking)
- throws Exception {
+ private void testExclusiveInteractions(int gain, boolean pauseForDucking) {
// Test exclusive interaction, interaction where each usage will not share focus with other
// another usage. As a result once focus is gained any current focus listener
@@ -476,132 +493,125 @@
AudioAttributes attributes2,
int interaction,
int gainType,
- boolean pauseForDucking) throws Exception {
- final AudioFocusRequest[] focusRequests = new AudioFocusRequest[2];
- final FocusChangeListener[] focusListeners = new FocusChangeListener[2];
- try {
- final FocusChangeListener focusChangeListener1 = new FocusChangeListener();
- final AudioFocusRequest audioFocusRequest1 = new AudioFocusRequest
- .Builder(AudioManager.AUDIOFOCUS_GAIN)
- .setAudioAttributes(attributes1)
- .setOnAudioFocusChangeListener(focusChangeListener1)
- .setForceDucking(false)
- .setWillPauseWhenDucked(pauseForDucking)
- .build();
- focusRequests[0] = audioFocusRequest1;
- focusListeners[0] = focusChangeListener1;
- final FocusChangeListener focusChangeListener2 = new FocusChangeListener();
- final AudioFocusRequest audioFocusRequest2 = new AudioFocusRequest
- .Builder(gainType)
- .setAudioAttributes(attributes2)
- .setOnAudioFocusChangeListener(focusChangeListener2)
- .setForceDucking(false)
- .build();
- focusRequests[1] = audioFocusRequest2;
- focusListeners[1] = focusChangeListener2;
+ boolean pauseForDucking) {
+ final FocusChangeListener focusChangeListener1 = new FocusChangeListener();
+ final AudioFocusRequest audioFocusRequest1 = new AudioFocusRequest
+ .Builder(AudioManager.AUDIOFOCUS_GAIN)
+ .setAudioAttributes(attributes1)
+ .setOnAudioFocusChangeListener(focusChangeListener1)
+ .setForceDucking(false)
+ .setWillPauseWhenDucked(pauseForDucking)
+ .build();
- int expectedLoss = 0;
+ final FocusChangeListener focusChangeListener2 = new FocusChangeListener();
+ final AudioFocusRequest audioFocusRequest2 = new AudioFocusRequest
+ .Builder(gainType)
+ .setAudioAttributes(attributes2)
+ .setOnAudioFocusChangeListener(focusChangeListener2)
+ .setForceDucking(false)
+ .build();
- // Each focus gain type will return a different focus lost type
- switch (gainType) {
- case AudioManager.AUDIOFOCUS_GAIN:
- expectedLoss = AudioManager.AUDIOFOCUS_LOSS;
- break;
- case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
- expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
- break;
- case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
- expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
- // Note loss or gain will not be sent as both can live concurrently
- if (interaction == INTERACTION_CONCURRENT && !pauseForDucking) {
- expectedLoss = AudioManager.AUDIOFOCUS_NONE;
- }
- break;
- }
+ int expectedLoss = 0;
- int secondRequestResultsExpected = AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+ // Each focus gain type will return a different focus lost type
+ switch (gainType) {
+ case AudioManager.AUDIOFOCUS_GAIN:
+ expectedLoss = AudioManager.AUDIOFOCUS_LOSS;
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+ expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+ expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+ // Note loss or gain will not be sent as both can live concurrently
+ if (interaction == INTERACTION_CONCURRENT && !pauseForDucking) {
+ expectedLoss = AudioManager.AUDIOFOCUS_NONE;
+ }
+ break;
+ }
- if (interaction == INTERACTION_REJECT) {
- secondRequestResultsExpected = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- }
+ int secondRequestResultsExpected = AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
- int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest1);
- String message = "Focus gain request failed for 1st "
+ if (interaction == INTERACTION_REJECT) {
+ secondRequestResultsExpected = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+
+ int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest1);
+ String message = "Focus gain request failed for 1st "
+ + AudioAttributes.usageToString(attributes1.getSystemUsage());
+ assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
+ mAudioFocusRequestsSet.add(audioFocusRequest1);
+
+ requestResult = mAudioManager.requestAudioFocus(audioFocusRequest2);
+ message = "Focus gain request failed for 2nd "
+ + AudioAttributes.usageToString(attributes2.getSystemUsage());
+ assertEquals(message, secondRequestResultsExpected, requestResult);
+ mAudioFocusRequestsSet.add(audioFocusRequest2);
+
+ // If the results is rejected for second one we only have to clean up first
+ // as the second focus request is rejected
+ if (interaction == INTERACTION_REJECT) {
+ requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest1);
+ mAudioFocusRequestsSet.clear();
+ message = "Focus loss request failed for 1st "
+ AudioAttributes.usageToString(attributes1.getSystemUsage());
assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
+ }
- requestResult = mAudioManager.requestAudioFocus(audioFocusRequest2);
- message = "Focus gain request failed for 2nd "
+ // If exclusive we expect to lose focus on 1st one
+ // unless we have a concurrent interaction
+ if (interaction == INTERACTION_EXCLUSIVE || interaction == INTERACTION_CONCURRENT) {
+ message = "Focus change was not dispatched for 1st "
+ + AudioAttributes.usageToString(attributes1.getSystemUsage());
+ boolean shouldStop = false;
+ int counter = 0;
+ while (!shouldStop && counter++ < TEST_TORELANCE_MAX_ITERATIONS) {
+ boolean gainedFocusLoss = focusChangeListener1.waitForFocusChangeAndAssertFocus(
+ TEST_TIMING_TOLERANCE_MS, expectedLoss, message);
+ shouldStop = gainedFocusLoss
+ || (expectedLoss == AudioManager.AUDIOFOCUS_NONE);
+ }
+ assertThat(shouldStop).isTrue();
+ focusChangeListener1.resetFocusChangeAndWait();
+
+ if (expectedLoss == AudioManager.AUDIOFOCUS_LOSS) {
+ mAudioFocusRequestsSet.remove(audioFocusRequest1);
+ }
+ requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest2);
+ mAudioFocusRequestsSet.remove(audioFocusRequest2);
+ message = "Focus loss request failed for 2nd "
+ AudioAttributes.usageToString(attributes2.getSystemUsage());
- assertEquals(message, secondRequestResultsExpected, requestResult);
+ assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
- // If the results is rejected for second one we only have to clean up first
- // as the second focus request is rejected
- if (interaction == INTERACTION_REJECT) {
- requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest1);
- focusRequests[0] = null;
- message = "Focus loss request failed for 1st "
- + AudioAttributes.usageToString(attributes1.getSystemUsage());
- assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
- }
- // If exclusive we expect to lose focus on 1st one
- // unless we have a concurrent interaction
- if (interaction == INTERACTION_EXCLUSIVE || interaction == INTERACTION_CONCURRENT) {
- message = "Focus change was not dispatched for 1st "
- + AudioAttributes.usageToString(attributes1.getSystemUsage());
- boolean shouldStop = false;
- int counter = 0;
- while (!shouldStop && counter++ < TEST_TORELANCE_MAX_ITERATIONS) {
- boolean gainedFocusLoss = focusChangeListener1.waitForFocusChangeAndAssertFocus(
- TEST_TIMING_TOLERANCE_MS, expectedLoss, message);
- shouldStop = gainedFocusLoss
- || (expectedLoss == AudioManager.AUDIOFOCUS_NONE);
- }
- assertThat(shouldStop).isTrue();
- focusChangeListener1.resetFocusChangeAndWait();
+ // If the loss was transient then we should have received back on 1st
+ if ((gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+ || gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) {
- requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest2);
- focusRequests[1] = null;
- message = "Focus loss request failed for 2nd "
- + AudioAttributes.usageToString(attributes2.getSystemUsage());
- assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
-
- // If the loss was transient then we should have received back on 1st
- if ((gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
- || gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) {
-
- // Since ducking and concurrent can exist together
- // this needs to be skipped as the focus lost is not sent
- if (!(interaction == INTERACTION_CONCURRENT
- && gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) {
- message = "Focus change was not dispatched for 1st "
- + AudioAttributes.usageToString(attributes1.getSystemUsage());
-
- boolean focusGained = false;
- int count = 0;
- while (!focusGained && count++ < TEST_TORELANCE_MAX_ITERATIONS) {
- focusGained = focusChangeListener1.waitForFocusChangeAndAssertFocus(
- TEST_TIMING_TOLERANCE_MS,
- AudioManager.AUDIOFOCUS_GAIN, message);
- }
- assertThat(focusGained).isTrue();
- focusChangeListener1.resetFocusChangeAndWait();
- }
- // For concurrent focus interactions still needs to be released
- message = "Focus loss request failed for 1st "
+ // Since ducking and concurrent can exist together
+ // this needs to be skipped as the focus lost is not sent
+ if (!(interaction == INTERACTION_CONCURRENT
+ && gainType == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) {
+ message = "Focus change was not dispatched for 1st "
+ AudioAttributes.usageToString(attributes1.getSystemUsage());
- requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest1);
- focusRequests[0] = null;
- assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED,
- requestResult);
+
+ boolean focusGained = false;
+ int count = 0;
+ while (!focusGained && count++ < TEST_TORELANCE_MAX_ITERATIONS) {
+ focusGained = focusChangeListener1.waitForFocusChangeAndAssertFocus(
+ TEST_TIMING_TOLERANCE_MS,
+ AudioManager.AUDIOFOCUS_GAIN, message);
+ }
+ assertThat(focusGained).isTrue();
+ focusChangeListener1.resetFocusChangeAndWait();
}
- }
- } finally {
- for (AudioFocusRequest focusRequest: focusRequests) {
- if (focusRequest != null) {
- mAudioManager.abandonAudioFocusRequest(focusRequest);
- }
+ // For concurrent focus interactions still needs to be released
+ message = "Focus loss request failed for 1st "
+ + AudioAttributes.usageToString(attributes1.getSystemUsage());
+ requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest1);
+ mAudioFocusRequestsSet.remove(audioFocusRequest1);
+ assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED,
+ requestResult);
}
}
}
@@ -610,10 +620,9 @@
* Verifies usage can request audio focus and release it
*
* @param attribute usage attribute to request focus
- * @throws Exception
*/
- private void requestAndLoseFocusForAttribute(AudioAttributes attribute) throws Exception {
- final FocusChangeListener focusChangeListener = new FocusChangeListener();
+ private void requestAndLoseFocusForAttribute(AudioAttributes attribute) {
+ FocusChangeListener focusChangeListener = new FocusChangeListener();
AudioFocusRequest audioFocusRequest = new AudioFocusRequest
.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(attribute)
@@ -622,30 +631,25 @@
.build();
- try {
- int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest);
- String message = "Focus gain request failed for "
- + AudioAttributes.usageToString(attribute.getSystemUsage());
- assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
+ int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest);
+ String message = "Focus gain request failed for "
+ + AudioAttributes.usageToString(attribute.getSystemUsage());
+ assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
+ mAudioFocusRequestsSet.add(audioFocusRequest);
- // Verify no focus changed dispatched
- message = "Focus change was dispatched for "
- + AudioAttributes.usageToString(attribute.getSystemUsage());
+ // Verify no focus changed dispatched
+ message = "Focus change was dispatched for "
+ + AudioAttributes.usageToString(attribute.getSystemUsage());
- assertThat(focusChangeListener.waitForFocusChangeAndAssertFocus(
- TEST_TIMING_TOLERANCE_MS, AudioManager.AUDIOFOCUS_NONE, message)).isFalse();
- focusChangeListener.resetFocusChangeAndWait();
+ assertThat(focusChangeListener.waitForFocusChangeAndAssertFocus(
+ TEST_TIMING_TOLERANCE_MS, AudioManager.AUDIOFOCUS_NONE, message)).isFalse();
+ focusChangeListener.resetFocusChangeAndWait();
- requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest);
- audioFocusRequest = null;
- message = "Focus loss request failed for "
- + AudioAttributes.usageToString(attribute.getSystemUsage());
- assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
- } finally {
- if (audioFocusRequest != null) {
- mAudioManager.abandonAudioFocusRequest(audioFocusRequest);
- }
- }
+ requestResult = mAudioManager.abandonAudioFocusRequest(audioFocusRequest);
+ message = "Focus loss request failed for "
+ + AudioAttributes.usageToString(attribute.getSystemUsage());
+ assertEquals(message, AudioManager.AUDIOFOCUS_REQUEST_GRANTED, requestResult);
+ mAudioFocusRequestsSet.remove(audioFocusRequest);
}
private static class FocusChangeListener implements AudioManager.OnAudioFocusChangeListener {
diff --git a/tests/carservice_unit_test/src/android/car/AbstractExtendedMockitoCarServiceTestCase.java b/tests/carservice_unit_test/src/android/car/AbstractExtendedMockitoCarServiceTestCase.java
new file mode 100644
index 0000000..258e49e
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/AbstractExtendedMockitoCarServiceTestCase.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import android.annotation.NonNull;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.util.Log;
+
+import com.android.car.CarLocalServices;
+
+/**
+ * Specialized {@link AbstractExtendedMockitoTestCase} implementation that provides
+ * CarService-related helpers.
+ */
+public abstract class AbstractExtendedMockitoCarServiceTestCase
+ extends AbstractExtendedMockitoTestCase {
+
+ private static final String TAG = AbstractExtendedMockitoCarServiceTestCase.class
+ .getSimpleName();
+
+ private static final boolean VERBOSE = false;
+
+ /**
+ * Mocks a call to {@link CarLocalServices#getService(Class)}.
+ *
+ * @throws IllegalStateException if class didn't override {@link #newSessionBuilder()} and
+ * called {@code spyStatic(CarLocalServices.class)} on the session passed to it.
+ */
+ protected final <T> void mockGetCarLocalService(@NonNull Class<T> type, @NonNull T service) {
+ if (VERBOSE) Log.v(TAG, getLogPrefix() + "mockGetLocalService(" + type.getName() + ")");
+ assertSpied(CarLocalServices.class);
+
+ beginTrace("mockGetLocalService");
+ doReturn(service).when(() -> CarLocalServices.getService(type));
+ endTrace();
+ }
+}
diff --git a/tests/carservice_unit_test/src/android/car/userlib/InitialUserSetterTest.java b/tests/carservice_unit_test/src/android/car/userlib/InitialUserSetterTest.java
index f61b19a..785cbc4 100644
--- a/tests/carservice_unit_test/src/android/car/userlib/InitialUserSetterTest.java
+++ b/tests/carservice_unit_test/src/android/car/userlib/InitialUserSetterTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -41,12 +42,16 @@
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.userlib.InitialUserSetter.Builder;
+import android.car.userlib.InitialUserSetter.InitialUserInfo;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import com.android.internal.widget.LockPatternUtils;
@@ -69,6 +74,9 @@
private static final int CURRENT_USER_ID = 12;
@Mock
+ private Context mContext;
+
+ @Mock
private CarUserManagerHelper mHelper;
@Mock
@@ -94,20 +102,53 @@
@Before
public void setFixtures() {
- mSetter = spy(new InitialUserSetter(mHelper, mUm, mListener,
- mLockPatternUtils, OWNER_NAME, GUEST_NAME,
- /* supportsOverrideUserIdProperty= */ false));
+ mSetter = spy(new InitialUserSetter(mContext, mHelper, mUm, mListener,
+ mLockPatternUtils, OWNER_NAME, GUEST_NAME));
doReturn(mIActivityManager).when(() -> ActivityManager.getService());
mockGetCurrentUser(CURRENT_USER_ID);
}
@Test
+ public void testSet_null() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> mSetter.set(null));
+ }
+
+ @Test
+ public void testInitialUserInfoBuilder_invalidType() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> new InitialUserSetter.Builder(-1));
+ }
+
+ @Test
+ public void testInitialUserInfoBuilder_invalidSetSwitchUserId() throws Exception {
+ InitialUserSetter.Builder builder = new InitialUserSetter.Builder(
+ InitialUserSetter.TYPE_CREATE);
+ assertThrows(IllegalArgumentException.class, () -> builder.setSwitchUserId(USER_ID));
+ }
+
+ @Test
+ public void testInitialUserInfoBuilder_invalidSetNewUserName() throws Exception {
+ InitialUserSetter.Builder builder = new InitialUserSetter.Builder(
+ InitialUserSetter.TYPE_SWITCH);
+ assertThrows(IllegalArgumentException.class, () -> builder.setNewUserName(OWNER_NAME));
+ }
+
+ @Test
+ public void testInitialUserInfoBuilder_invalidSetNewUserFlags() throws Exception {
+ InitialUserSetter.Builder builder = new InitialUserSetter.Builder(
+ InitialUserSetter.TYPE_SWITCH);
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setNewUserFlags(UserFlags.ADMIN));
+ }
+
+ @Test
public void testSwitchUser_ok_nonGuest() throws Exception {
UserInfo user = expectUserExists(USER_ID);
expectSwitchUser(USER_ID);
- mSetter.switchUser(USER_ID, /* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ .setSwitchUserId(USER_ID)
+ .build());
verifyUserSwitched(USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -120,7 +161,9 @@
UserInfo user = expectUserExists(UserHandle.USER_SYSTEM);
expectSwitchUser(UserHandle.USER_SYSTEM);
- mSetter.switchUser(UserHandle.USER_SYSTEM, true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ .setSwitchUserId(UserHandle.USER_SYSTEM)
+ .build());
verifyUserSwitched(UserHandle.USER_SYSTEM);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -137,7 +180,10 @@
expectGuestReplaced(USER_ID, newGuest);
expectSwitchUser(NEW_USER_ID);
- mSetter.switchUser(USER_ID, /* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ .setSwitchUserId(USER_ID)
+ .setReplaceGuest(true)
+ .build());
verifyUserSwitched(NEW_USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -152,7 +198,10 @@
UserInfo existingGuest = expectGuestExists(USER_ID, ephemeral);
expectSwitchUser(USER_ID);
- mSetter.switchUser(USER_ID, /* replaceGuest= */ false);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ .setSwitchUserId(USER_ID)
+ .setReplaceGuest(false)
+ .build());
verifyUserSwitched(USER_ID);
verifyGuestNeverMarkedForDeletion();
@@ -161,12 +210,16 @@
assertInitialUserSet(existingGuest);
}
+
@Test
public void testSwitchUser_fail_guestReplacementFailed() throws Exception {
expectGuestExists(USER_ID, /* isEphemeral= */ true); // ephemeral doesn't matter
expectGuestReplaced(USER_ID, /* newGuest= */ null);
- mSetter.switchUser(USER_ID, /* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ .setSwitchUserId(USER_ID)
+ .setReplaceGuest(true)
+ .build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch();
@@ -178,7 +231,9 @@
expectUserExists(USER_ID);
expectSwitchUserFails(USER_ID);
- mSetter.switchUser(USER_ID, /* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ .setSwitchUserId(USER_ID)
+ .build());
verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch();
verifySystemUserUnlocked();
@@ -189,7 +244,9 @@
public void testSwitchUser_fail_userDoesntExist() throws Exception {
// No need to set user exists expectation / will return null by default
- mSetter.switchUser(USER_ID, /* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ .setSwitchUserId(USER_ID)
+ .build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch();
@@ -201,7 +258,9 @@
expectUserExists(USER_ID);
expectSwitchUserThrowsException(USER_ID);
- mSetter.switchUser(USER_ID, /* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ .setSwitchUserId(USER_ID)
+ .build());
verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch();
verifySystemUserUnlocked();
@@ -213,7 +272,9 @@
mockGetCurrentUser(CURRENT_USER_ID);
UserInfo currentUser = expectUserExists(CURRENT_USER_ID);
- mSetter.switchUser(CURRENT_USER_ID, true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ .setSwitchUserId(CURRENT_USER_ID)
+ .build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorNeverCalled();
@@ -281,7 +342,10 @@
UserInfo newUser = expectCreateFullUser(USER_ID, "TheDude", NO_FLAGS);
expectSwitchUser(USER_ID);
- mSetter.createUser("TheDude", UserFlags.NONE);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName("TheDude")
+ .setNewUserFlags(UserFlags.NONE)
+ .build());
verifyUserSwitched(USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -294,7 +358,10 @@
UserInfo newUser = expectCreateFullUser(USER_ID, "TheDude", UserInfo.FLAG_ADMIN);
expectSwitchUser(USER_ID);
- mSetter.createUser("TheDude", UserFlags.ADMIN);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName("TheDude")
+ .setNewUserFlags(UserFlags.ADMIN)
+ .build());
verifyUserSwitched(USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -303,11 +370,33 @@
}
@Test
+ public void testCreateUser_ok_admin_setLocale() throws Exception {
+ UserInfo newUser = expectCreateFullUser(USER_ID, "TheDude", UserInfo.FLAG_ADMIN);
+ expectSwitchUser(USER_ID);
+
+ mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName("TheDude")
+ .setNewUserFlags(UserFlags.ADMIN)
+ .setUserLocales("LOL")
+ .build());
+
+ verifyUserSwitched(USER_ID);
+ verifyFallbackDefaultBehaviorNeverCalled();
+ verifySystemUserUnlocked();
+ assertInitialUserSet(newUser);
+ assertSystemLocales("LOL");
+ }
+
+
+ @Test
public void testCreateUser_ok_ephemeralGuest() throws Exception {
UserInfo newGuest = expectCreateGuestUser(USER_ID, "TheDude", UserInfo.FLAG_EPHEMERAL);
expectSwitchUser(USER_ID);
- mSetter.createUser("TheDude", UserFlags.EPHEMERAL | UserFlags.GUEST);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName("TheDude")
+ .setNewUserFlags(UserFlags.EPHEMERAL | UserFlags.GUEST)
+ .build());
verifyUserSwitched(USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -319,7 +408,10 @@
public void testCreateUser_fail_systemUser() throws Exception {
// No need to set mUm.createUser() expectation - it shouldn't be called
- mSetter.createUser("TheDude", UserFlags.SYSTEM);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName("TheDude")
+ .setNewUserFlags(UserFlags.SYSTEM)
+ .build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch();
@@ -330,7 +422,10 @@
public void testCreateUser_fail_guestAdmin() throws Exception {
// No need to set mUm.createUser() expectation - it shouldn't be called
- mSetter.createUser("TheDude", UserFlags.GUEST | UserFlags.ADMIN);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName("TheDude")
+ .setNewUserFlags(UserFlags.GUEST | UserFlags.ADMIN)
+ .build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch();
@@ -340,7 +435,10 @@
public void testCreateUser_fail_ephemeralAdmin() throws Exception {
// No need to set mUm.createUser() expectation - it shouldn't be called
- mSetter.createUser("TheDude", UserFlags.EPHEMERAL | UserFlags.ADMIN);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName("TheDude")
+ .setNewUserFlags(UserFlags.EPHEMERAL | UserFlags.ADMIN)
+ .build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch();
@@ -350,7 +448,10 @@
public void testCreateUser_fail_createFail() throws Exception {
// No need to set mUm.createUser() expectation - it will return false by default
- mSetter.createUser("TheDude", UserFlags.NONE);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName("TheDude")
+ .setNewUserFlags(UserFlags.NONE)
+ .build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch();
@@ -360,7 +461,10 @@
public void testCreateUser_fail_createThrowsException() throws Exception {
expectCreateUserThrowsException("TheDude", UserFlags.NONE);
- mSetter.createUser("TheDude", UserFlags.NONE);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName("TheDude")
+ .setNewUserFlags(UserFlags.NONE)
+ .build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch();
@@ -371,7 +475,10 @@
expectCreateFullUser(USER_ID, "TheDude", NO_FLAGS);
expectSwitchUserFails(USER_ID);
- mSetter.createUser("TheDude", UserFlags.NONE);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ .setNewUserName("TheDude")
+ .setNewUserFlags(UserFlags.NONE)
+ .build());
verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch();
verifySystemUserUnlocked();
@@ -384,7 +491,7 @@
UserInfo newUser = expectCreateFullUser(USER_ID, OWNER_NAME, UserInfo.FLAG_ADMIN);
expectSwitchUser(USER_ID);
- mSetter.executeDefaultBehavior(/* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyUserSwitched(USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -393,11 +500,28 @@
}
@Test
+ public void testDefaultBehavior_firstBoot_ok_setLocale() throws Exception {
+ // no need to mock hasInitialUser(), it will return false by default
+ UserInfo newUser = expectCreateFullUser(USER_ID, OWNER_NAME, UserInfo.FLAG_ADMIN);
+ expectSwitchUser(USER_ID);
+
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ .setUserLocales("LOL")
+ .build());
+
+ verifyUserSwitched(USER_ID);
+ verifyFallbackDefaultBehaviorNeverCalled();
+ verifySystemUserUnlocked();
+ assertInitialUserSet(newUser);
+ assertSystemLocales("LOL");
+ }
+
+ @Test
public void testDefaultBehavior_firstBoot_fail_createUserFailed() throws Exception {
// no need to mock hasInitialUser(), it will return false by default
// no need to mock createUser(), it will return null by default
- mSetter.executeDefaultBehavior(/* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorCalledFromDefaultBehavior();
@@ -410,7 +534,7 @@
expectCreateFullUser(USER_ID, OWNER_NAME, UserInfo.FLAG_ADMIN);
expectSwitchUserFails(USER_ID);
- mSetter.executeDefaultBehavior(/* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyFallbackDefaultBehaviorCalledFromDefaultBehavior();
verifySystemUserUnlocked();
@@ -418,11 +542,27 @@
}
@Test
+ public void testDefaultBehavior_firstBoot_fail_switchFailed_setLocale() throws Exception {
+ // no need to mock hasInitialUser(), it will return false by default
+ expectCreateFullUser(USER_ID, OWNER_NAME, UserInfo.FLAG_ADMIN);
+ expectSwitchUserFails(USER_ID);
+
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ .setUserLocales("LOL")
+ .build());
+
+ verifyFallbackDefaultBehaviorCalledFromDefaultBehavior();
+ verifySystemUserUnlocked();
+ verifyLastActiveUserNeverSet();
+ assertSystemLocales("LOL");
+ }
+
+ @Test
public void testDefaultBehavior_nonFirstBoot_ok() throws Exception {
UserInfo existingUser = expectHasInitialUser(USER_ID);
expectSwitchUser(USER_ID);
- mSetter.executeDefaultBehavior(/* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyUserSwitched(USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -436,7 +576,7 @@
UserInfo currentUser = expectHasInitialUser(CURRENT_USER_ID);
expectSwitchUser(CURRENT_USER_ID);
- mSetter.executeDefaultBehavior(/* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorNeverCalled();
@@ -450,7 +590,7 @@
expectHasInitialUser(USER_ID);
expectSwitchUserFails(USER_ID);
- mSetter.executeDefaultBehavior(/* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyFallbackDefaultBehaviorCalledFromDefaultBehavior();
verifyUserNeverCreated();
@@ -467,7 +607,9 @@
expectGuestReplaced(USER_ID, newGuest);
expectSwitchUser(NEW_USER_ID);
- mSetter.executeDefaultBehavior(/* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ .setReplaceGuest(true)
+ .build());
verifyUserSwitched(NEW_USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -483,7 +625,9 @@
expectGuestExists(USER_ID, /* isEphemeral= */ true); // ephemeral doesn't matter
expectGuestReplaced(USER_ID, /* newGuest= */ null);
- mSetter.executeDefaultBehavior(/* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ .setReplaceGuest(true)
+ .build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorCalledFromDefaultBehavior();
@@ -494,18 +638,17 @@
@Test
public void testDefaultBehavior_nonFirstBoot_ok_withOverriddenProperty() throws Exception {
boolean supportsOverrideUserIdProperty = true;
- // Must use a different helper as the property is set on constructor
- InitialUserSetter setter = spy(new InitialUserSetter(mHelper, mUm, mListener,
- mLockPatternUtils, OWNER_NAME, GUEST_NAME, supportsOverrideUserIdProperty));
UserInfo user = expectHasInitialUser(USER_ID, supportsOverrideUserIdProperty);
- expectSwitchUser(setter, USER_ID);
+ expectSwitchUser(USER_ID);
- setter.executeDefaultBehavior(/* replaceGuest= */ true);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ .setSupportsOverrideUserIdProperty(true)
+ .build());
- verifyUserSwitched(setter, USER_ID);
- verifyFallbackDefaultBehaviorNeverCalled();
+ verifyUserSwitched(USER_ID);
+ verifyFallbackDefaultBehaviorNeverCalled(supportsOverrideUserIdProperty);
verifyUserNeverCreated();
- verifySystemUserUnlocked(setter);
+ verifySystemUserUnlocked();
assertInitialUserSet(user);
}
@@ -516,7 +659,9 @@
UserInfo existingGuest = expectHasInitialGuest(USER_ID);
expectSwitchUser(USER_ID);
- mSetter.executeDefaultBehavior(/* replaceGuest= */ false);
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ .setReplaceGuest(false)
+ .build());
verifyUserSwitched(USER_ID);
verifyGuestNeverMarkedForDeletion();
@@ -633,12 +778,7 @@
}
private void expectSwitchUser(@UserIdInt int userId) throws Exception {
- expectSwitchUser(mSetter, userId);
- }
-
- private void expectSwitchUser(@NonNull InitialUserSetter setter, @UserIdInt int userId)
- throws Exception {
- doReturn(true).when(setter).startForegroundUser(userId);
+ doReturn(true).when(mSetter).startForegroundUser(userId);
}
private void expectSwitchUserFails(@UserIdInt int userId) {
when(mSetter.startForegroundUser(userId)).thenReturn(false);
@@ -683,12 +823,7 @@
}
private void verifyUserSwitched(@UserIdInt int userId) throws Exception {
- verifyUserSwitched(mSetter, userId);
- }
-
- private void verifyUserSwitched(@NonNull InitialUserSetter setter, @UserIdInt int userId)
- throws Exception {
- verify(setter).startForegroundUser(userId);
+ verify(mSetter).startForegroundUser(userId);
verify(mHelper).setLastActiveUser(userId);
}
@@ -714,25 +849,32 @@
}
private void verifyFallbackDefaultBehaviorCalledFromCreateOrSwitch() {
- verify(mSetter).fallbackDefaultBehavior(eq(true), anyString());
+ verify(mSetter).fallbackDefaultBehavior(isInitialInfo(false), eq(true), anyString());
assertInitialUserSet(null);
}
private void verifyFallbackDefaultBehaviorCalledFromDefaultBehavior() {
- verify(mSetter).fallbackDefaultBehavior(eq(false), anyString());
+ verify(mSetter).fallbackDefaultBehavior(isInitialInfo(false), eq(false), anyString());
assertInitialUserSet(null);
}
private void verifyFallbackDefaultBehaviorNeverCalled() {
- verify(mSetter, never()).fallbackDefaultBehavior(anyBoolean(), anyString());
+ verifyFallbackDefaultBehaviorNeverCalled(/* supportsOverrideUserIdProperty= */ false);
+ }
+
+ private void verifyFallbackDefaultBehaviorNeverCalled(boolean supportsOverrideUserIdProperty) {
+ verify(mSetter, never()).fallbackDefaultBehavior(
+ isInitialInfo(supportsOverrideUserIdProperty), anyBoolean(), anyString());
+ }
+
+ private static InitialUserInfo isInitialInfo(boolean supportsOverrideUserIdProperty) {
+ return argThat((info) -> {
+ return info.supportsOverrideUserIdProperty == supportsOverrideUserIdProperty;
+ });
}
private void verifySystemUserUnlocked() {
- verifySystemUserUnlocked(mSetter);
- }
-
- private void verifySystemUserUnlocked(InitialUserSetter setter) {
- verify(setter).unlockSystemUser();
+ verify(mSetter).unlockSystemUser();
}
private void verifySystemUserNeverUnlocked() {
@@ -750,6 +892,11 @@
.isSameAs(expectedUser);
}
+ private void assertSystemLocales(@NonNull String expected) {
+ // TODO(b/156033195): should test specific userId
+ assertThat(getSettingsString(Settings.System.SYSTEM_LOCALES)).isEqualTo(expected);
+ }
+
private final class MyListener implements Consumer<UserInfo> {
public int numberCalls;
public UserInfo initialUser;
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 33a852a..84f8bd6 100644
--- a/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
@@ -39,6 +39,8 @@
import android.annotation.NonNull;
import android.content.pm.UserInfo;
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.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
@@ -401,7 +403,7 @@
}
@Test
- public void testToUserIdentificationGetResponse_invalidPropType() {
+ public void testToUserIdentificationResponse_invalidPropType() {
VehiclePropValue prop = new VehiclePropValue();
assertThrows(IllegalArgumentException.class,
@@ -485,6 +487,228 @@
}
@Test
+ public void testToInitialUserInfoResponse_null() {
+ assertThrows(NullPointerException.class,
+ () -> UserHalHelper.toInitialUserInfoResponse(null));
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_invalidPropType() {
+ VehiclePropValue prop = new VehiclePropValue();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toInitialUserInfoResponse(prop));
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_invalidSize() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ // need at least 2: request_id, action_type
+ prop.value.int32Values.add(42);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toInitialUserInfoResponse(prop));
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_invalidRequest() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(0);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toInitialUserInfoResponse(prop));
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_invalidAction() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(-1); // InitialUserInfoResponseAction
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toInitialUserInfoResponse(prop));
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_default_ok_noStringValue() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(InitialUserInfoResponseAction.DEFAULT);
+
+ InitialUserInfoResponse response = UserHalHelper.toInitialUserInfoResponse(prop);
+
+ assertThat(response).isNotNull();
+ assertThat(response.requestId).isEqualTo(42);
+ assertThat(response.action).isEqualTo(InitialUserInfoResponseAction.DEFAULT);
+ assertThat(response.userNameToCreate).isEmpty();
+ assertThat(response.userToSwitchOrCreate.userId).isEqualTo(UserHandle.USER_NULL);
+ assertThat(response.userToSwitchOrCreate.flags).isEqualTo(UserFlags.NONE);
+ assertThat(response.userLocales).isEmpty();
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_default_ok_stringValueWithJustSeparator() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(InitialUserInfoResponseAction.DEFAULT);
+ prop.value.stringValue = "||";
+
+ InitialUserInfoResponse response = UserHalHelper.toInitialUserInfoResponse(prop);
+
+ assertThat(response).isNotNull();
+ assertThat(response.requestId).isEqualTo(42);
+ assertThat(response.action).isEqualTo(InitialUserInfoResponseAction.DEFAULT);
+ assertThat(response.userNameToCreate).isEmpty();
+ assertThat(response.userToSwitchOrCreate.userId).isEqualTo(UserHandle.USER_NULL);
+ assertThat(response.userToSwitchOrCreate.flags).isEqualTo(UserFlags.NONE);
+ assertThat(response.userLocales).isEmpty();
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_default_ok_stringValueWithLocale() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(InitialUserInfoResponseAction.DEFAULT);
+ prop.value.stringValue = "esperanto,klingon";
+
+ InitialUserInfoResponse response = UserHalHelper.toInitialUserInfoResponse(prop);
+
+ assertThat(response).isNotNull();
+ assertThat(response.requestId).isEqualTo(42);
+ assertThat(response.action).isEqualTo(InitialUserInfoResponseAction.DEFAULT);
+ assertThat(response.userNameToCreate).isEmpty();
+ assertThat(response.userToSwitchOrCreate.userId).isEqualTo(UserHandle.USER_NULL);
+ assertThat(response.userToSwitchOrCreate.flags).isEqualTo(UserFlags.NONE);
+ assertThat(response.userLocales).isEqualTo("esperanto,klingon");
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_default_ok_stringValueWithLocaleWithHalfSeparator() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(InitialUserInfoResponseAction.DEFAULT);
+ prop.value.stringValue = "esperanto|klingon";
+
+ InitialUserInfoResponse response = UserHalHelper.toInitialUserInfoResponse(prop);
+
+ assertThat(response).isNotNull();
+ assertThat(response.requestId).isEqualTo(42);
+ assertThat(response.action).isEqualTo(InitialUserInfoResponseAction.DEFAULT);
+ assertThat(response.userNameToCreate).isEmpty();
+ assertThat(response.userToSwitchOrCreate.userId).isEqualTo(UserHandle.USER_NULL);
+ assertThat(response.userToSwitchOrCreate.flags).isEqualTo(UserFlags.NONE);
+ assertThat(response.userLocales).isEqualTo("esperanto|klingon");
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_switch_missingUserId() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(InitialUserInfoResponseAction.SWITCH);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toInitialUserInfoResponse(prop));
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_switch_ok_noLocale() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(InitialUserInfoResponseAction.SWITCH);
+ prop.value.int32Values.add(108); // user id
+
+ InitialUserInfoResponse response = UserHalHelper.toInitialUserInfoResponse(prop);
+
+ assertThat(response).isNotNull();
+ assertThat(response.requestId).isEqualTo(42);
+ assertThat(response.action).isEqualTo(InitialUserInfoResponseAction.SWITCH);
+ assertThat(response.userNameToCreate).isEmpty();
+ assertThat(response.userToSwitchOrCreate.userId).isEqualTo(108);
+ assertThat(response.userToSwitchOrCreate.flags).isEqualTo(UserFlags.NONE);
+ assertThat(response.userLocales).isEmpty();
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_switch_ok_withLocale() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(InitialUserInfoResponseAction.SWITCH);
+ prop.value.int32Values.add(108); // user id
+ // add some extra | to make sure they're ignored
+ prop.value.stringValue = "esperanto,klingon|||";
+ InitialUserInfoResponse response = UserHalHelper.toInitialUserInfoResponse(prop);
+
+ assertThat(response).isNotNull();
+ assertThat(response.requestId).isEqualTo(42);
+ assertThat(response.action).isEqualTo(InitialUserInfoResponseAction.SWITCH);
+ assertThat(response.userNameToCreate).isEmpty();
+ assertThat(response.userToSwitchOrCreate.userId).isEqualTo(108);
+ assertThat(response.userToSwitchOrCreate.flags).isEqualTo(UserFlags.NONE);
+ assertThat(response.userLocales).isEqualTo("esperanto,klingon");
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_create_missingFlags() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(InitialUserInfoResponseAction.CREATE);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toInitialUserInfoResponse(prop));
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_create_ok_noLocale() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(InitialUserInfoResponseAction.CREATE);
+ prop.value.int32Values.add(UserFlags.GUEST);
+ prop.value.stringValue = "||ElGuesto";
+
+ InitialUserInfoResponse response = UserHalHelper.toInitialUserInfoResponse(prop);
+
+ assertThat(response).isNotNull();
+ assertThat(response.requestId).isEqualTo(42);
+ assertThat(response.action).isEqualTo(InitialUserInfoResponseAction.CREATE);
+ assertThat(response.userNameToCreate).isEqualTo("ElGuesto");
+ assertThat(response.userToSwitchOrCreate.userId).isEqualTo(UserHandle.USER_NULL);
+ assertThat(response.userToSwitchOrCreate.flags).isEqualTo(UserFlags.GUEST);
+ assertThat(response.userLocales).isEmpty();
+ }
+
+ @Test
+ public void testToInitialUserInfoResponse_create_ok_withLocale() {
+ VehiclePropValue prop = new VehiclePropValue();
+ prop.prop = UserHalHelper.INITIAL_USER_INFO_PROPERTY;
+ prop.value.int32Values.add(42); // request id
+ prop.value.int32Values.add(InitialUserInfoResponseAction.CREATE);
+ prop.value.int32Values.add(UserFlags.GUEST);
+ prop.value.stringValue = "esperanto,klingon||ElGuesto";
+
+ InitialUserInfoResponse response = UserHalHelper.toInitialUserInfoResponse(prop);
+
+ assertThat(response).isNotNull();
+ assertThat(response.requestId).isEqualTo(42);
+ assertThat(response.action).isEqualTo(InitialUserInfoResponseAction.CREATE);
+ assertThat(response.userNameToCreate).isEqualTo("ElGuesto");
+ assertThat(response.userToSwitchOrCreate.userId).isEqualTo(UserHandle.USER_NULL);
+ assertThat(response.userToSwitchOrCreate.flags).isEqualTo(UserFlags.GUEST);
+ assertThat(response.userLocales).isEqualTo("esperanto,klingon");
+ }
+
+ @Test
public void testUserIdentificationSetRequestToVehiclePropValue_null() {
assertThrows(NullPointerException.class,
() -> UserHalHelper.toVehiclePropValue((UserIdentificationSetRequest) null));
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 ad241b5..c7906be 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
@@ -26,11 +26,11 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -138,6 +138,8 @@
if (mService != null) {
mService.release();
}
+ CarServiceUtils.runOnLooperSync(CarServiceUtils.getHandlerThread(
+ CarPowerManagementService.class.getSimpleName()).getLooper(), () -> { });
mIOInterface.tearDown();
}
@@ -584,12 +586,18 @@
}
private void verifyUserNotSwitched() {
- verify(mInitialUserSetter, never()).switchUser(anyInt(), anyBoolean());
+ verify(mInitialUserSetter, never()).set(argThat((info) -> {
+ return info.type == InitialUserSetter.TYPE_SWITCH;
+ }));
}
private void verifyUserSwitched(int userId) {
// TODO(b/153679319): pass proper value for replaceGuest
- verify(mInitialUserSetter).switchUser(userId, true);
+ verify(mInitialUserSetter).set(argThat((info) -> {
+ return info.type == InitialUserSetter.TYPE_SWITCH
+ && info.switchUserId == userId
+ && info.replaceGuest;
+ }));
}
private void expectNewGuestCreated(int existingGuestId, UserInfo newGuest) {
@@ -599,15 +607,24 @@
private void verifyDefaultInitialUserBehaviorCalled() {
// TODO(b/153679319): pass proper value for replaceGuest
- verify(mInitialUserSetter).executeDefaultBehavior(true);
+ verify(mInitialUserSetter).set(argThat((info) -> {
+ return info.type == InitialUserSetter.TYPE_DEFAULT_BEHAVIOR
+ && info.replaceGuest;
+ }));
}
private void verifyDefaultInitilUserBehaviorNeverCalled() {
- verify(mInitialUserSetter, never()).executeDefaultBehavior(anyBoolean());
+ verify(mInitialUserSetter, never()).set(argThat((info) -> {
+ return info.type == InitialUserSetter.TYPE_DEFAULT_BEHAVIOR;
+ }));
}
private void verifyUserCreated(String name, int halFlags) {
- verify(mInitialUserSetter).createUser(name, halFlags);
+ verify(mInitialUserSetter).set(argThat((info) -> {
+ return info.type == InitialUserSetter.TYPE_CREATE
+ && info.newUserName == name
+ && info.newUserFlags == halFlags;
+ }));
}
private static final class MockDisplayInterface implements DisplayInterface {
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsAssociatedLayerTest.java b/tests/carservice_unit_test/src/com/android/car/VmsAssociatedLayerTest.java
deleted file mode 100644
index 1d384cc..0000000
--- a/tests/carservice_unit_test/src/com/android/car/VmsAssociatedLayerTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.car.vms.VmsAssociatedLayer;
-import android.car.vms.VmsLayer;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import android.os.Parcel;
-
-import java.util.Arrays;
-import java.util.HashSet;
-
-
-/*
- * A class to test the VmsAssociatedLayer parcelability.
- */
-@SmallTest
-public class VmsAssociatedLayerTest extends AndroidTestCase {
- private static final int LAYER_ID = 12;
- private static final int LAYER_VERSION = 34;
- private static final int LAYER_SUBTYPE = 56;
-
- private static final int PUBLISHER_ID_1 = 111;
- private static final int PUBLISHER_ID_2 = 222;
-
- private static final int DIFFERENT_LAYER_ID = 99;
-
- private static final VmsLayer VMS_LAYER = new VmsLayer(
- LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- private static final VmsLayer ANOTHER_VMS_LAYER = new VmsLayer(
- DIFFERENT_LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
-
- public void testNoPublishersAssociatedLayerParcel() throws Exception {
- VmsAssociatedLayer vmsAssociatedLayer =
- new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList()));
- Parcel parcel = Parcel.obtain();
- vmsAssociatedLayer.writeToParcel(parcel, vmsAssociatedLayer.describeContents());
- parcel.setDataPosition(0);
- VmsAssociatedLayer createdFromParcel = VmsAssociatedLayer.CREATOR.createFromParcel(parcel);
- assertEquals(vmsAssociatedLayer, createdFromParcel);
- }
-
- public void testLayerWithMultiplePublishersParcel() throws Exception {
- VmsAssociatedLayer vmsAssociatedLayer =
- new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(PUBLISHER_ID_1, PUBLISHER_ID_2)));
- Parcel parcel = Parcel.obtain();
- vmsAssociatedLayer.writeToParcel(parcel, vmsAssociatedLayer.describeContents());
- parcel.setDataPosition(0);
- VmsAssociatedLayer createdFromParcel = VmsAssociatedLayer.CREATOR.createFromParcel(parcel);
- assertEquals(vmsAssociatedLayer, createdFromParcel);
- }
-
- public void testNoPublishersAssociatedLayerEquality() throws Exception {
- VmsAssociatedLayer original =
- new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList()));
-
- VmsAssociatedLayer similar =
- new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList()));
-
- assertEquals(original.getVmsLayer(), similar.getVmsLayer());
- assertEquals(original.getPublisherIds(), similar.getPublisherIds());
- assertEquals(original, similar);
- }
-
- public void testLayerWithMultiplePublishersEquality() throws Exception {
- VmsAssociatedLayer original =
- new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(PUBLISHER_ID_1, PUBLISHER_ID_2)));
-
- VmsAssociatedLayer similar =
- new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(PUBLISHER_ID_1, PUBLISHER_ID_2)));
-
- assertEquals(original.getVmsLayer(), similar.getVmsLayer());
- assertEquals(original.getPublisherIds(), similar.getPublisherIds());
- assertEquals(original, similar);
- }
-
- public void testVerifyNonEqualOnLayer() throws Exception {
- VmsAssociatedLayer original =
- new VmsAssociatedLayer(
- ANOTHER_VMS_LAYER,
- new HashSet<>(Arrays.asList()));
-
- VmsAssociatedLayer similar =
- new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList()));
-
- assertNotEquals(original, similar);
- }
-
- public void testVerifyNonEqualOnPublisherIds() throws Exception {
- VmsAssociatedLayer original =
- new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(PUBLISHER_ID_1)));
-
- VmsAssociatedLayer similar =
- new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(PUBLISHER_ID_2)));
-
- assertNotEquals(original, similar);
- }
-}
\ No newline at end of file
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsAvailableLayersTest.java b/tests/carservice_unit_test/src/com/android/car/VmsAvailableLayersTest.java
deleted file mode 100644
index 63ac1a3..0000000
--- a/tests/carservice_unit_test/src/com/android/car/VmsAvailableLayersTest.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.car.vms.VmsAssociatedLayer;
-import android.car.vms.VmsAvailableLayers;
-import android.car.vms.VmsLayer;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import android.os.Parcel;
-
-import java.util.Arrays;
-import java.util.HashSet;
-
-
-/*
- * A class to test the VmsAvailableLayers parcelability.
- */
-@SmallTest
-public class VmsAvailableLayersTest extends AndroidTestCase {
- private static final int LAYER_ID = 12;
- private static final int LAYER_VERSION = 34;
- private static final int LAYER_SUBTYPE = 56;
- private static final int PUBLISHER_ID = 999;
- private static final int SEQUENCE_NUMBER = 17;
-
- private static final int DIFFERENT_SEQUENCE_NUMBER = 71;
- private static final int DIFFERENT_LAYER_ID = 21;
-
- private static final VmsLayer VMS_LAYER = new VmsLayer(
- LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- private static final VmsLayer ANOTHER_VMS_LAYER = new VmsLayer(
- DIFFERENT_LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- private static final VmsAssociatedLayer VMS_ASSOCIATED_LAYER = new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(PUBLISHER_ID)));
-
- private static final VmsAssociatedLayer ANOTHER_VMS_ASSOCIATED_LAYER = new VmsAssociatedLayer(
- ANOTHER_VMS_LAYER,
- new HashSet<>(Arrays.asList(PUBLISHER_ID)));
-
- public void testNoAvailableLayersParcel() throws Exception {
- VmsAvailableLayers vmsAvailableLayers =
- new VmsAvailableLayers(new HashSet<>(Arrays.asList()), SEQUENCE_NUMBER);
-
- Parcel parcel = Parcel.obtain();
-
- vmsAvailableLayers.writeToParcel(parcel, vmsAvailableLayers.describeContents());
- parcel.setDataPosition(0);
- VmsAvailableLayers createdFromParcel = VmsAvailableLayers.CREATOR.createFromParcel(parcel);
- assertEquals(vmsAvailableLayers, createdFromParcel);
- }
-
- public void testMultipleAvailableLayersParcel() throws Exception {
- VmsAvailableLayers vmsAvailableLayers =
- new VmsAvailableLayers(
- new HashSet<>(Arrays.asList(
- VMS_ASSOCIATED_LAYER,
- ANOTHER_VMS_ASSOCIATED_LAYER)),
- SEQUENCE_NUMBER);
-
- Parcel parcel = Parcel.obtain();
-
- vmsAvailableLayers.writeToParcel(parcel, vmsAvailableLayers.describeContents());
- parcel.setDataPosition(0);
- VmsAvailableLayers createdFromParcel = VmsAvailableLayers.CREATOR.createFromParcel(parcel);
- assertEquals(vmsAvailableLayers, createdFromParcel);
- }
-
- public void testNoAvailableLayerEquality() throws Exception {
- VmsAvailableLayers original =
- new VmsAvailableLayers(
- new HashSet<>(Arrays.asList()),
- SEQUENCE_NUMBER);
-
- VmsAvailableLayers similar =
- new VmsAvailableLayers(
- new HashSet<>(Arrays.asList()),
- SEQUENCE_NUMBER);
-
- assertEquals(original.getAssociatedLayers(), similar.getAssociatedLayers());
- assertEquals(original.getSequence(), similar.getSequence());
- assertEquals(original, similar);
- }
-
- public void testMultipleAvailableLayerEquality() throws Exception {
- VmsAvailableLayers original =
- new VmsAvailableLayers(
- new HashSet<>(Arrays.asList(
- VMS_ASSOCIATED_LAYER,
- ANOTHER_VMS_ASSOCIATED_LAYER)),
- SEQUENCE_NUMBER);
-
- VmsAvailableLayers similar =
- new VmsAvailableLayers(
- new HashSet<>(Arrays.asList(
- VMS_ASSOCIATED_LAYER,
- ANOTHER_VMS_ASSOCIATED_LAYER)),
- SEQUENCE_NUMBER);
-
- assertEquals(original.getAssociatedLayers(), similar.getAssociatedLayers());
- assertEquals(original.getSequence(), similar.getSequence());
- assertEquals(original, similar);
- }
-
- public void testVerifyNonEqualOnAssociatedLayer() throws Exception {
- VmsAvailableLayers original =
- new VmsAvailableLayers(
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)),
- SEQUENCE_NUMBER);
-
- VmsAvailableLayers similar =
- new VmsAvailableLayers(
- new HashSet<>(Arrays.asList(ANOTHER_VMS_ASSOCIATED_LAYER)),
- SEQUENCE_NUMBER);
-
- assertNotEquals(original, similar);
- }
-
- public void testVerifyNonEqualOnSequence() throws Exception {
- VmsAvailableLayers original =
- new VmsAvailableLayers(
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)),
- SEQUENCE_NUMBER);
-
- VmsAvailableLayers similar =
- new VmsAvailableLayers(
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)),
- DIFFERENT_SEQUENCE_NUMBER);
-
- assertNotEquals(original, similar);
- }
-}
\ No newline at end of file
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsLayerDependencyTest.java b/tests/carservice_unit_test/src/com/android/car/VmsLayerDependencyTest.java
deleted file mode 100644
index 4c659cd..0000000
--- a/tests/carservice_unit_test/src/com/android/car/VmsLayerDependencyTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsLayerDependency;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import android.os.Parcel;
-
-import java.util.Arrays;
-import java.util.HashSet;
-
-
-/*
- * A class to test the VmsLayerDependency parcelability.
- */
-@SmallTest
-public class VmsLayerDependencyTest extends AndroidTestCase {
- private static final int LAYER_ID = 112;
- private static final int LAYER_VERSION = 134;
- private static final int LAYER_SUBTYPE = 156;
-
- private static final int DEPENDENT_LAYER_ID = 212;
- private static final int DEPENDENT_LAYER_VERSION = 234;
- private static final int DEPENDENT_LAYER_SUBTYPE = 256;
-
- private static final int DIFFERENT_LAYER_ID = 99;
-
- private static final VmsLayer VMS_LAYER = new VmsLayer(
- LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- private static final VmsLayer ANOTHER_VMS_LAYER = new VmsLayer(
- DIFFERENT_LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- private static final VmsLayer DEPENDENT_VMS_LAYER = new VmsLayer(
- DEPENDENT_LAYER_ID,
- DEPENDENT_LAYER_SUBTYPE,
- DEPENDENT_LAYER_VERSION);
-
- private static final VmsLayer ANOTHER_DEPENDENT_VMS_LAYER = new VmsLayer(
- DIFFERENT_LAYER_ID,
- DEPENDENT_LAYER_SUBTYPE,
- DEPENDENT_LAYER_VERSION);
-
- public void testNoDependendtLayerParcel() throws Exception {
- VmsLayerDependency vmsLayerDependency =
- new VmsLayerDependency(
- VMS_LAYER,
- new HashSet<>(Arrays.asList()));
-
- Parcel parcel = Parcel.obtain();
- vmsLayerDependency.writeToParcel(parcel, vmsLayerDependency.describeContents());
- parcel.setDataPosition(0);
- VmsLayerDependency createdFromParcel = VmsLayerDependency.CREATOR.createFromParcel(parcel);
- assertEquals(vmsLayerDependency, createdFromParcel);
- }
-
- public void testMultipleDependendtLayerParcel() throws Exception {
- VmsLayerDependency vmsLayerDependency =
- new VmsLayerDependency(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(DEPENDENT_VMS_LAYER, ANOTHER_DEPENDENT_VMS_LAYER)));
-
- Parcel parcel = Parcel.obtain();
- vmsLayerDependency.writeToParcel(parcel, vmsLayerDependency.describeContents());
- parcel.setDataPosition(0);
- VmsLayerDependency createdFromParcel = VmsLayerDependency.CREATOR.createFromParcel(parcel);
- assertEquals(vmsLayerDependency, createdFromParcel);
- }
-
- public void testNoDependendtLayerEquality() throws Exception {
- VmsLayerDependency original =
- new VmsLayerDependency(
- VMS_LAYER,
- new HashSet<>(Arrays.asList()));
-
- VmsLayerDependency similar =
- new VmsLayerDependency(VMS_LAYER);
-
- assertEquals(original.getLayer(), similar.getLayer());
- assertEquals(original.getDependencies(), similar.getDependencies());
- assertEquals(original, similar);
- }
-
- public void testMultipleDependendtLayerEquality() throws Exception {
- VmsLayerDependency original =
- new VmsLayerDependency(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(DEPENDENT_VMS_LAYER, ANOTHER_DEPENDENT_VMS_LAYER)));
-
- VmsLayerDependency similar =
- new VmsLayerDependency(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(DEPENDENT_VMS_LAYER, ANOTHER_DEPENDENT_VMS_LAYER)));
- assertEquals(original.getLayer(), similar.getLayer());
- assertEquals(original.getDependencies(), similar.getDependencies());
- assertEquals(original, similar);
- }
-
- public void testVerifyNonEqualOnLayer() throws Exception {
- VmsLayerDependency original =
- new VmsLayerDependency(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(DEPENDENT_VMS_LAYER)));
-
- VmsLayerDependency similar =
- new VmsLayerDependency(
- ANOTHER_DEPENDENT_VMS_LAYER,
- new HashSet<>(Arrays.asList(DEPENDENT_VMS_LAYER)));
-
- assertNotEquals(original, similar);
- }
-
- public void testVerifyNonEqualOnDependentLayer() throws Exception {
- VmsLayerDependency original =
- new VmsLayerDependency(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(DEPENDENT_VMS_LAYER)));
-
- VmsLayerDependency similar =
- new VmsLayerDependency(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(ANOTHER_DEPENDENT_VMS_LAYER)));
-
- assertNotEquals(original, similar);
- }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsLayerTest.java b/tests/carservice_unit_test/src/com/android/car/VmsLayerTest.java
deleted file mode 100644
index 56fb129..0000000
--- a/tests/carservice_unit_test/src/com/android/car/VmsLayerTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.car.vms.VmsLayer;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import android.os.Parcel;
-
-import java.util.Arrays;
-import java.util.HashSet;
-
-/*
- * A class to test the VmsLayer parcelability.
- */
-@SmallTest
-public class VmsLayerTest extends AndroidTestCase {
- private static final int LAYER_ID = 12;
- private static final int LAYER_VERSION = 34;
- private static final int LAYER_SUBTYPE = 56;
-
- private static final int DIFFERENT_LAYER_ID = 99;
-
- public void testLayerParcel() throws Exception {
- VmsLayer vmsLayer =
- new VmsLayer(
- LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- Parcel parcel = Parcel.obtain();
- vmsLayer.writeToParcel(parcel, vmsLayer.describeContents());
- parcel.setDataPosition(0);
- VmsLayer createdFromParcel = VmsLayer.CREATOR.createFromParcel(parcel);
- assertEquals(vmsLayer, createdFromParcel);
- }
-
- public void testLayerEquality() throws Exception {
- VmsLayer original =
- new VmsLayer(
- LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- VmsLayer similar =
- new VmsLayer(
- LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- assertEquals(original.getType(), similar.getType());
- assertEquals(original.getSubtype(), similar.getSubtype());
- assertEquals(original.getVersion(), similar.getVersion());
- assertEquals(original, similar);
- }
-
- public void testVerifyNonEqual() throws Exception {
- VmsLayer original =
- new VmsLayer(
- LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- VmsLayer similar =
- new VmsLayer(
- DIFFERENT_LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- assertNotEquals(original, similar);
- }
-}
\ No newline at end of file
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsLayersOfferingTest.java b/tests/carservice_unit_test/src/com/android/car/VmsLayersOfferingTest.java
deleted file mode 100644
index c2111a9..0000000
--- a/tests/carservice_unit_test/src/com/android/car/VmsLayersOfferingTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsLayerDependency;
-import android.car.vms.VmsLayersOffering;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.os.Parcel;
-
-import java.util.Arrays;
-import java.util.HashSet;
-
-import android.util.Log;
-
-/*
- * A class to test the VmsLayersOffering parcelability.
- */
-@SmallTest
-public class VmsLayersOfferingTest extends AndroidTestCase {
- private static final int PUBLISHER_ID = 1;
-
- private static final int LAYER_ID = 112;
- private static final int LAYER_VERSION = 134;
- private static final int LAYER_SUBTYPE = 156;
-
- private static final int DEPENDENT_LAYER_ID = 212;
- private static final int DEPENDENT_LAYER_VERSION = 234;
- private static final int DEPENDENT_LAYER_SUBTYPE = 256;
-
- private static final int DIFFERENT_LAYER_ID = 99;
- private static final int DIFFERENT_PUBLISHER_ID = 2;
-
- private static final VmsLayer VMS_LAYER = new VmsLayer(
- LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- private static final VmsLayer DEPENDENT_VMS_LAYER = new VmsLayer(
- DEPENDENT_LAYER_ID,
- DEPENDENT_LAYER_SUBTYPE,
- DEPENDENT_LAYER_VERSION);
-
- private static final VmsLayer ANOTHER_DEPENDENT_VMS_LAYER = new VmsLayer(
- DIFFERENT_LAYER_ID,
- DEPENDENT_LAYER_SUBTYPE,
- DEPENDENT_LAYER_VERSION);
-
- private static final VmsLayerDependency VMS_LAYER_DEPENDENCY =
- new VmsLayerDependency(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(DEPENDENT_VMS_LAYER)));
-
- private static final VmsLayerDependency ANOTHER_VMS_LAYER_DEPENDENCY =
- new VmsLayerDependency(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(ANOTHER_DEPENDENT_VMS_LAYER)));
-
- public void testNoOfferingParcel() throws Exception {
- VmsLayersOffering vmsLayersOffering =
- new VmsLayersOffering(new HashSet<>(Arrays.asList()), PUBLISHER_ID);
-
- Parcel parcel = Parcel.obtain();
- vmsLayersOffering.writeToParcel(parcel, vmsLayersOffering.describeContents());
- parcel.setDataPosition(0);
- VmsLayersOffering createdFromParcel = VmsLayersOffering.CREATOR.createFromParcel(parcel);
- assertEquals(vmsLayersOffering, createdFromParcel);
- }
-
- public void testMultipleOfferingParcel() throws Exception {
- VmsLayersOffering vmsLayersOffering =
- new VmsLayersOffering(
- new HashSet<>(Arrays.asList(
- VMS_LAYER_DEPENDENCY,
- ANOTHER_VMS_LAYER_DEPENDENCY)),
- PUBLISHER_ID);
-
- Parcel parcel = Parcel.obtain();
- vmsLayersOffering.writeToParcel(parcel, vmsLayersOffering.describeContents());
- parcel.setDataPosition(0);
- VmsLayersOffering createdFromParcel = VmsLayersOffering.CREATOR.createFromParcel(parcel);
-
- assertEquals(vmsLayersOffering, createdFromParcel);
- }
-
- public void testNoOfferingEquality() throws Exception {
- VmsLayersOffering original =
- new VmsLayersOffering(
- new HashSet<>(Arrays.asList()), PUBLISHER_ID);
-
- VmsLayersOffering similar =
- new VmsLayersOffering(
- new HashSet<>(Arrays.asList()), PUBLISHER_ID);
-
- assertEquals(original.getDependencies(), similar.getDependencies());
- assertEquals(original.getPublisherId(), similar.getPublisherId());
- assertEquals(original, similar);
- }
-
- public void testMultipleOfferingEquality() throws Exception {
- VmsLayersOffering original =
- new VmsLayersOffering(
- new HashSet<>(Arrays.asList(VMS_LAYER_DEPENDENCY, ANOTHER_VMS_LAYER_DEPENDENCY)), PUBLISHER_ID);
-
- VmsLayersOffering similar =
- new VmsLayersOffering(
- new HashSet<>(Arrays.asList(VMS_LAYER_DEPENDENCY, ANOTHER_VMS_LAYER_DEPENDENCY)), PUBLISHER_ID);
-
- assertEquals(original.getDependencies(), similar.getDependencies());
- assertEquals(original.getPublisherId(), similar.getPublisherId());
- assertEquals(original, similar);
- }
-
- public void testVerifyNonEqualOnPublisherId() throws Exception {
- VmsLayersOffering original =
- new VmsLayersOffering(
- new HashSet<>(Arrays.asList(VMS_LAYER_DEPENDENCY)), PUBLISHER_ID);
-
- VmsLayersOffering similar =
- new VmsLayersOffering(
- new HashSet<>(Arrays.asList(VMS_LAYER_DEPENDENCY)), DIFFERENT_PUBLISHER_ID);
-
- assertNotEquals(original, similar);
- }
-
- public void testVerifyNonEqualOnDependency() throws Exception {
- VmsLayersOffering original =
- new VmsLayersOffering(
- new HashSet<>(Arrays.asList(VMS_LAYER_DEPENDENCY)), PUBLISHER_ID);
-
- VmsLayersOffering similar =
- new VmsLayersOffering(
- new HashSet<>(Arrays.asList(ANOTHER_VMS_LAYER_DEPENDENCY)), PUBLISHER_ID);
-
- assertNotEquals(original, similar);
- }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java b/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java
deleted file mode 100644
index 1938a19..0000000
--- a/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class VmsPublishersInfoTest {
- public static final byte[] MOCK_INFO_1 = new byte[]{2, 3, 5, 7, 11, 13, 17};
- public static final byte[] SAME_MOCK_INFO_1 = new byte[]{2, 3, 5, 7, 11, 13, 17};
- public static final byte[] MOCK_INFO_2 = new byte[]{2, 3, 5, 7, 11, 13, 17, 19};
-
- private VmsPublishersInfo mVmsPublishersInfo;
-
- @Before
- public void setUp() throws Exception {
- mVmsPublishersInfo = new VmsPublishersInfo();
- }
-
- @Test
- public void testSingleInfo() throws Exception {
- int id = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_1);
- assertEquals(1, id);
- assertArrayEquals(MOCK_INFO_1, mVmsPublishersInfo.getPublisherInfo(id));
- }
-
- @Test
- public void testSingleInfo_NoSuchId() throws Exception {
- assertEquals(0, mVmsPublishersInfo.getPublisherInfo(12345).length);
- }
-
- @Test
- public void testTwoInfos() throws Exception {
- int id1 = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_1);
- int id2 = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_2);
- assertEquals(1, id1);
- assertEquals(2, id2);
- assertArrayEquals(MOCK_INFO_1, mVmsPublishersInfo.getPublisherInfo(id1));
- assertArrayEquals(MOCK_INFO_2, mVmsPublishersInfo.getPublisherInfo(id2));
- }
-
- @Test
- public void testSingleInfoInsertedTwice() throws Exception {
- int id = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_1);
- assertEquals(1, id);
-
- int sameId = mVmsPublishersInfo.getIdForInfo(SAME_MOCK_INFO_1);
- assertEquals(sameId, id);
- }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsSubscriptionStateTest.java b/tests/carservice_unit_test/src/com/android/car/VmsSubscriptionStateTest.java
deleted file mode 100644
index ea41286..0000000
--- a/tests/carservice_unit_test/src/com/android/car/VmsSubscriptionStateTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.car.vms.VmsAssociatedLayer;
-import android.car.vms.VmsLayer;
-import android.car.vms.VmsSubscriptionState;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import android.os.Parcel;
-
-import java.util.Arrays;
-import java.util.HashSet;
-
-/*
- * A class to test the VmsSubscriptionState parcelability.
- */
-@SmallTest
-public class VmsSubscriptionStateTest extends AndroidTestCase {
- private static final int LAYER_ID = 12;
- private static final int LAYER_VERSION = 34;
- private static final int LAYER_SUBTYPE = 56;
-
- private static final int PUBLISHER_ID_1= 111;
- private static final int PUBLISHER_ID_2= 222;
-
- private static final int DIFFERENT_LAYER_ID = 99;
-
- private static final int SEQUENCE_NUMBER = 1;
- private static final int DIFFERENT_SEQUENCE_NUMBER = 2;
-
- private static final VmsLayer VMS_LAYER = new VmsLayer(
- LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- private static final VmsLayer ANOTHER_VMS_LAYER = new VmsLayer(
- DIFFERENT_LAYER_ID,
- LAYER_SUBTYPE,
- LAYER_VERSION);
-
- private static final VmsAssociatedLayer VMS_ASSOCIATED_LAYER =
- new VmsAssociatedLayer(
- VMS_LAYER,
- new HashSet<>(Arrays.asList(PUBLISHER_ID_1)));
-
- public void testNoSubscriptionsParcel() throws Exception {
- VmsSubscriptionState vmsSubscriptionState =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList()),
- new HashSet<>(Arrays.asList()));
-
- Parcel parcel = Parcel.obtain();
- vmsSubscriptionState.writeToParcel(parcel, vmsSubscriptionState.describeContents());
- parcel.setDataPosition(0);
- VmsSubscriptionState createdFromParcel = VmsSubscriptionState.CREATOR.createFromParcel(parcel);
- assertEquals(vmsSubscriptionState, createdFromParcel);
- }
-
- public void testMultipleSubscriptionsParcel() throws Exception {
- VmsSubscriptionState vmsSubscriptionState =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList(VMS_LAYER)),
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)));
-
- Parcel parcel = Parcel.obtain();
- vmsSubscriptionState.writeToParcel(parcel, vmsSubscriptionState.describeContents());
- parcel.setDataPosition(0);
- VmsSubscriptionState createdFromParcel = VmsSubscriptionState.CREATOR.createFromParcel(parcel);
- assertEquals(vmsSubscriptionState, createdFromParcel);
- }
-
- public void testNoSubscriptionsEquality() throws Exception {
- VmsSubscriptionState original =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList()),
- new HashSet<>(Arrays.asList()));
-
- VmsSubscriptionState similar =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList()),
- new HashSet<>(Arrays.asList()));
-
- assertEquals(original.getSequenceNumber(), similar.getSequenceNumber());
- assertEquals(original.getLayers(), similar.getLayers());
- assertEquals(original.getAssociatedLayers(), similar.getAssociatedLayers());
- assertEquals(original, similar);
- }
-
- public void testMultipleSubscriptionsEquality() throws Exception {
- VmsSubscriptionState original =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList(VMS_LAYER)),
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)));
-
- VmsSubscriptionState similar =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList(VMS_LAYER)),
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)));
-
- assertEquals(original.getSequenceNumber(), similar.getSequenceNumber());
- assertEquals(original.getLayers(), similar.getLayers());
- assertEquals(original.getAssociatedLayers(), similar.getAssociatedLayers());
- assertEquals(original, similar);
- }
-
- public void testVerifyNonEqualOnSequenceNumber() throws Exception {
- VmsSubscriptionState original =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList(VMS_LAYER)),
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)));
-
- VmsSubscriptionState similar =
- new VmsSubscriptionState(
- DIFFERENT_SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList(VMS_LAYER)),
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)));
-
- if (!original.equals(similar)) {
- return;
- }
- fail("VmsSubscriptionState with different sequence numbers appear to be equal. original: " +
- original +
- ", similar: " +
- similar);
- }
-
- public void testVerifyNonEqualOnLayers() throws Exception {
- VmsSubscriptionState original =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList(VMS_LAYER)),
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)));
-
- VmsSubscriptionState similar =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList()),
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)));
-
- assertNotEquals(original, similar);
- }
-
- public void testVerifyNonEqualOnAssociatedLayers() throws Exception {
- VmsSubscriptionState original =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList(VMS_LAYER)),
- new HashSet<>(Arrays.asList(VMS_ASSOCIATED_LAYER)));
-
- VmsSubscriptionState similar =
- new VmsSubscriptionState(
- SEQUENCE_NUMBER,
- new HashSet<>(Arrays.asList(VMS_LAYER)),
- new HashSet<>(Arrays.asList()));
-
- assertNotEquals(original, similar);
- }
-}
\ No newline at end of file
diff --git a/tests/carservice_unit_test/src/com/android/car/com/android/car/cluster/InstrumentClusterServiceTest.java b/tests/carservice_unit_test/src/com/android/car/cluster/InstrumentClusterServiceTest.java
similarity index 96%
rename from tests/carservice_unit_test/src/com/android/car/com/android/car/cluster/InstrumentClusterServiceTest.java
rename to tests/carservice_unit_test/src/com/android/car/cluster/InstrumentClusterServiceTest.java
index f1bd536..79ebe4f 100644
--- a/tests/carservice_unit_test/src/com/android/car/com/android/car/cluster/InstrumentClusterServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/cluster/InstrumentClusterServiceTest.java
@@ -26,6 +26,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import android.car.CarAppFocusManager;
import android.car.cluster.renderer.IInstrumentCluster;
import android.car.cluster.renderer.IInstrumentClusterNavigation;
import android.car.navigation.CarNavigationInstrumentCluster;
@@ -34,6 +35,7 @@
import android.content.Context;
import android.os.Bundle;
import android.os.Looper;
+import android.os.Process;
import android.view.KeyEvent;
import androidx.test.InstrumentationRegistry;
@@ -125,6 +127,9 @@
if (connect) {
notifyRendererServiceConnection();
}
+ // Give nav focus to the test
+ mService.onFocusAcquired(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, Process.myUid(),
+ Process.myPid());
}
private void notifyRendererServiceConnection() {
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 033bb9a..56d5395 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
@@ -33,9 +33,11 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -60,12 +62,18 @@
import android.hardware.automotive.vehicle.V2_0.UsersInfo;
import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.os.Handler;
+import android.os.Looper;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
+import com.android.car.CarLocalServices;
+import com.android.car.user.CarUserService;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -116,6 +124,10 @@
@Mock
private VehicleHal mVehicleHal;
+ @Mock
+ private CarUserService mCarUserService;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private final UserInfo mUser0 = new UserInfo();
private final UserInfo mUser10 = new UserInfo();
@@ -127,7 +139,7 @@
@Before
public void setFixtures() {
- mUserHalService = spy(new UserHalService(mVehicleHal));
+ mUserHalService = spy(new UserHalService(mVehicleHal, mHandler));
// Needs at least one property, otherwise isSupported() will return false
mUserHalService.takeProperties(Arrays.asList(newSubscribableConfig(INITIAL_USER_INFO)));
@@ -141,6 +153,13 @@
mUsersInfo.existingUsers = new ArrayList<>(2);
mUsersInfo.existingUsers.add(mUser0);
mUsersInfo.existingUsers.add(mUser10);
+
+ CarLocalServices.addService(CarUserService.class, mCarUserService);
+ }
+
+ @After
+ public void clearFixtures() {
+ CarLocalServices.removeServiceForTest(CarUserService.class);
}
@Test
@@ -359,7 +378,7 @@
VehiclePropValue propResponse = UserHalHelper.createPropRequest(INITIAL_USER_INFO,
REQUEST_ID_PLACE_HOLDER, InitialUserInfoResponseAction.CREATE);
propResponse.value.int32Values.add(newUserFlags);
- propResponse.value.stringValue = newUserName;
+ propResponse.value.stringValue = "||" + newUserName;
AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
@@ -378,6 +397,7 @@
assertThat(callback.status).isEqualTo(HalCallback.STATUS_OK);
InitialUserInfoResponse actualResponse = callback.response;
assertThat(actualResponse.action).isEqualTo(InitialUserInfoResponseAction.CREATE);
+ assertThat(actualResponse.userLocales).isEmpty();
assertThat(actualResponse.userNameToCreate).isEqualTo(newUserName);
UserInfo newUser = actualResponse.userToSwitchOrCreate;
assertThat(newUser).isNotNull();
@@ -464,7 +484,7 @@
@Test
public void testSwitchUser_halReturnedInvalidMessageType() throws Exception {
VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
- REQUEST_ID_PLACE_HOLDER, SwitchUserMessageType.VEHICLE_REQUEST);
+ REQUEST_ID_PLACE_HOLDER, SwitchUserMessageType.LEGACY_ANDROID_SWITCH);
propResponse.value.int32Values.add(SwitchUserStatus.SUCCESS);
AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
@@ -576,6 +596,35 @@
}
@Test
+ public void testUserSwitch_OEMRequest_success() throws Exception {
+ int requestId = -4;
+ int targetUserId = 11;
+ VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
+ requestId, SwitchUserMessageType.VEHICLE_REQUEST);
+
+ propResponse.value.int32Values.add(targetUserId);
+
+ mUserHalService.onHalEvents(Arrays.asList(propResponse));
+ waitForHandler();
+
+ verify(mCarUserService).switchAndroidUserFromHal(requestId, targetUserId);
+ }
+
+ @Test
+ public void testUserSwitch_OEMRequest_failure_positiveRequestId() throws Exception {
+ int requestId = 4;
+ int targetUserId = 11;
+ VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
+ requestId, SwitchUserMessageType.VEHICLE_REQUEST);
+ propResponse.value.int32Values.add(targetUserId);
+
+ mUserHalService.onHalEvents(Arrays.asList(propResponse));
+ waitForHandler();
+
+ verify(mCarUserService, never()).switchAndroidUserFromHal(anyInt(), anyInt());
+ }
+
+ @Test
public void testPostSwitchResponse_noUsersInfo() {
assertThrows(NullPointerException.class,
() -> mUserHalService.postSwitchResponse(42, mUser10, null));
@@ -1052,6 +1101,13 @@
return request;
}
+ /**
+ * Run empty runnable to make sure that all posted handlers are done.
+ */
+ private void waitForHandler() {
+ mHandler.runWithScissors(() -> { }, /* Default timeout */ CALLBACK_TIMEOUT_TIMEOUT);
+ }
+
private void mockNextRequestId(int requestId) {
doReturn(requestId).when(mUserHalService).getNextRequestId();
}
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
new file mode 100644
index 0000000..6100dbc
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
@@ -0,0 +1,391 @@
+/*
+ * 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.hardware.power;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.car.hardware.power.CarPowerManager;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.mocks.JavaMockitoHelper;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
+import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateShutdownParam;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.CarPowerManagementService;
+import com.android.car.MockedPowerHalService;
+import com.android.car.R;
+import com.android.car.hal.PowerHalService;
+import com.android.car.hal.PowerHalService.PowerState;
+import com.android.car.systeminterface.DisplayInterface;
+import com.android.car.systeminterface.SystemInterface;
+import com.android.car.systeminterface.SystemStateInterface;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+
+@SmallTest
+public class CarPowerManagerUnitTest extends AbstractExtendedMockitoTestCase {
+ private static final String TAG = CarPowerManagerUnitTest.class.getSimpleName();
+ private static final long WAIT_TIMEOUT_MS = 5_000;
+ private static final long WAIT_TIMEOUT_LONG_MS = 10_000;
+ // A shorter value for use when the test is expected to time out
+ private static final long WAIT_WHEN_TIMEOUT_EXPECTED_MS = 100;
+
+ private final MockDisplayInterface mDisplayInterface = new MockDisplayInterface();
+ private final MockSystemStateInterface mSystemStateInterface = new MockSystemStateInterface();
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ private MockedPowerHalService mPowerHal;
+ private SystemInterface mSystemInterface;
+ private CarPowerManagementService mService;
+ private CarPowerManager mCarPowerManager;
+
+ @Mock
+ private Resources mResources;
+ @Mock
+ private Car mCar;
+
+ @Before
+ public void setUp() throws Exception {
+ mPowerHal = new MockedPowerHalService(true /*isPowerStateSupported*/,
+ true /*isDeepSleepAllowed*/, true /*isTimedWakeupAllowed*/);
+ mSystemInterface = SystemInterface.Builder.defaultSystemInterface(mContext)
+ .withDisplayInterface(mDisplayInterface)
+ .withSystemStateInterface(mSystemStateInterface)
+ .build();
+ setService();
+ mCarPowerManager = new CarPowerManager(mCar, mService);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mService != null) {
+ mService.release();
+ }
+ }
+
+ @Test
+ public void testRequestShutdownOnNextSuspend_positive() throws Exception {
+ setPowerOn();
+ // Tell it to shutdown
+ mCarPowerManager.requestShutdownOnNextSuspend();
+ // Request suspend
+ setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+ VehicleApPowerStateShutdownParam.CAN_SLEEP);
+ // Verify shutdown
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_SHUTDOWN_START, 0);
+ }
+
+ @Test
+ public void testRequestShutdownOnNextSuspend_negative() throws Exception {
+ setPowerOn();
+
+ // Do not tell it to shutdown
+
+ // Request suspend
+ setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+ VehicleApPowerStateShutdownParam.CAN_SLEEP);
+ // Verify suspend
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+ }
+
+ @Test
+ public void testScheduleNextWakeupTime() throws Exception {
+ setPowerOn();
+
+ int wakeTime = 1234;
+ mCarPowerManager.scheduleNextWakeupTime(wakeTime);
+
+ setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+ VehicleApPowerStateShutdownParam.CAN_SLEEP);
+
+ // Verify that we suspended with the requested wake-up time
+ assertStateReceivedForShutdownOrSleepWithPostpone(
+ PowerHalService.SET_DEEP_SLEEP_ENTRY, wakeTime);
+ }
+
+ @Test
+ public void testSetListener() throws Exception {
+ setPowerOn();
+
+ WaitablePowerStateListener listener = new WaitablePowerStateListener(2);
+
+ setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+ VehicleApPowerStateShutdownParam.CAN_SLEEP);
+
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+
+ int finalState = listener.await();
+ assertThat(finalState).isEqualTo(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+ }
+
+ @Test
+ public void testSetListenerWithCompletion() throws Exception {
+ setPowerOn();
+
+ WaitablePowerStateListenerWithCompletion listener =
+ new WaitablePowerStateListenerWithCompletion(2);
+
+ setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+ VehicleApPowerStateShutdownParam.CAN_SLEEP);
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+
+ int finalState = listener.await();
+ assertThat(finalState).isEqualTo(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+ }
+
+ @Test
+ public void testClearListener() throws Exception {
+ setPowerOn();
+
+ // Set a listener with a short timeout, because we expect the timeout to happen
+ WaitablePowerStateListener listener =
+ new WaitablePowerStateListener(1, WAIT_WHEN_TIMEOUT_EXPECTED_MS);
+
+ mCarPowerManager.clearListener();
+
+ setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+ VehicleApPowerStateShutdownParam.CAN_SLEEP);
+
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+ // Verify that the listener didn't run
+ assertThrows(IllegalStateException.class, () -> listener.await());
+ }
+
+ @Test
+ public void testGetPowerState() throws Exception {
+ setPowerOn();
+ assertThat(mCarPowerManager.getPowerState()).isEqualTo(PowerHalService.SET_ON);
+
+ // Request suspend
+ setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+ VehicleApPowerStateShutdownParam.CAN_SLEEP);
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+ assertThat(mCarPowerManager.getPowerState())
+ .isEqualTo(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+ }
+
+ /**
+ * Helper method to create mService and initialize a test case
+ */
+ private void setService() throws Exception {
+ Log.i(TAG, "setService(): overridden overlay properties: "
+ + "config_disableUserSwitchDuringResume="
+ + mResources.getBoolean(R.bool.config_disableUserSwitchDuringResume)
+ + ", maxGarageModeRunningDurationInSecs="
+ + mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
+ mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
+ mSystemInterface, null, null, null);
+ mService.init();
+ mService.setShutdownTimersForTest(0, 0);
+ assertStateReceived(MockedPowerHalService.SET_WAIT_FOR_VHAL, 0);
+ }
+
+ private void assertStateReceived(int expectedState, int expectedParam) throws Exception {
+ int[] state = mPowerHal.waitForSend(WAIT_TIMEOUT_MS);
+ assertThat(state).asList().containsExactly(expectedState, expectedParam).inOrder();
+ }
+
+ /**
+ * Helper method to get the system into ON
+ */
+ private void setPowerOn() throws Exception {
+ setPowerState(VehicleApPowerStateReq.ON, 0);
+ assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isTrue();
+ }
+
+ /**
+ * Helper to set the PowerHal state
+ *
+ * @param stateEnum Requested state enum
+ * @param stateParam Addition state parameter
+ */
+ private void setPowerState(int stateEnum, int stateParam) {
+ mPowerHal.setCurrentPowerState(new PowerState(stateEnum, stateParam));
+ }
+
+ private void assertStateReceivedForShutdownOrSleepWithPostpone(
+ int lastState, int stateParameter) throws Exception {
+ long startTime = System.currentTimeMillis();
+ while (true) {
+ int[] state = mPowerHal.waitForSend(WAIT_TIMEOUT_LONG_MS);
+ if (state[0] == lastState) {
+ assertThat(state[1]).isEqualTo(stateParameter);
+ return;
+ }
+ assertThat(state[0]).isEqualTo(PowerHalService.SET_SHUTDOWN_POSTPONE);
+ assertThat(System.currentTimeMillis() - startTime).isLessThan(WAIT_TIMEOUT_LONG_MS);
+ }
+ }
+
+ private static final class MockDisplayInterface implements DisplayInterface {
+ private boolean mDisplayOn = true;
+ private final Semaphore mDisplayStateWait = new Semaphore(0);
+
+ @Override
+ public void setDisplayBrightness(int brightness) {}
+
+ @Override
+ public synchronized void setDisplayState(boolean on) {
+ mDisplayOn = on;
+ mDisplayStateWait.release();
+ }
+
+ public synchronized boolean getDisplayState() {
+ return mDisplayOn;
+ }
+
+ public boolean waitForDisplayStateChange(long timeoutMs) throws Exception {
+ JavaMockitoHelper.await(mDisplayStateWait, timeoutMs);
+ return mDisplayOn;
+ }
+
+ @Override
+ public void startDisplayStateMonitoring(CarPowerManagementService service) {}
+
+ @Override
+ public void stopDisplayStateMonitoring() {}
+
+ @Override
+ public void refreshDisplayBrightness() {}
+ }
+
+ /**
+ * Helper class to set a power-state listener,
+ * verify that the listener gets called the
+ * right number of times, and return the final
+ * power state.
+ */
+ private final class WaitablePowerStateListener {
+ private final CountDownLatch mLatch;
+ private int mListenedState = -1;
+ private long mTimeoutValue = WAIT_TIMEOUT_MS;
+
+ WaitablePowerStateListener(int initialCount, long customTimeout) {
+ this(initialCount);
+ mTimeoutValue = customTimeout;
+ }
+
+ WaitablePowerStateListener(int initialCount) {
+ mLatch = new CountDownLatch(initialCount);
+ mCarPowerManager.setListener(
+ (state) -> {
+ mListenedState = state;
+ mLatch.countDown();
+ });
+ }
+
+ int await() throws Exception {
+ JavaMockitoHelper.await(mLatch, WAIT_TIMEOUT_MS);
+ return mListenedState;
+ }
+ }
+
+ /**
+ * Helper class to set a power-state listener with completion,
+ * verify that the listener gets called the right number of times,
+ * verify that the CompletableFuture is provided, complete the
+ * CompletableFuture, and return the final power state.
+ */
+ private final class WaitablePowerStateListenerWithCompletion {
+ private final CountDownLatch mLatch;
+ private int mListenedState = -1;
+ WaitablePowerStateListenerWithCompletion(int initialCount) {
+ mLatch = new CountDownLatch(initialCount);
+ mCarPowerManager.setListenerWithCompletion(
+ (state, future) -> {
+ mListenedState = state;
+ if (state == PowerHalService.SET_SHUTDOWN_PREPARE) {
+ assertThat(future).isNotNull();
+ future.complete(null);
+ } else {
+ assertThat(future).isNull();
+ }
+ mLatch.countDown();
+ });
+ }
+
+ int await() throws Exception {
+ JavaMockitoHelper.await(mLatch, WAIT_TIMEOUT_MS);
+ return mListenedState;
+ }
+ }
+
+ private static final class MockSystemStateInterface implements SystemStateInterface {
+ private final Semaphore mShutdownWait = new Semaphore(0);
+ private final Semaphore mSleepWait = new Semaphore(0);
+ private final Semaphore mSleepExitWait = new Semaphore(0);
+ private boolean mWakeupCausedByTimer = false;
+
+ @Override
+ public void shutdown() {
+ mShutdownWait.release();
+ }
+
+ public void waitForShutdown(long timeoutMs) throws Exception {
+ JavaMockitoHelper.await(mShutdownWait, timeoutMs);
+ }
+
+ @Override
+ public boolean enterDeepSleep() {
+ mSleepWait.release();
+ try {
+ mSleepExitWait.acquire();
+ } catch (InterruptedException e) {
+ }
+ return true;
+ }
+
+ public void waitForSleepEntryAndWakeup(long timeoutMs) throws Exception {
+ JavaMockitoHelper.await(mSleepWait, timeoutMs);
+ mSleepExitWait.release();
+ }
+
+ @Override
+ public void scheduleActionForBootCompleted(Runnable action, Duration delay) {}
+
+ @Override
+ public boolean isWakeupCausedByTimer() {
+ Log.i(TAG, "isWakeupCausedByTimer:" + mWakeupCausedByTimer);
+ return mWakeupCausedByTimer;
+ }
+
+ public synchronized void setWakeupCausedByTimer(boolean set) {
+ mWakeupCausedByTimer = set;
+ }
+
+ @Override
+ public boolean isSystemSupportingDeepSleep() {
+ return true;
+ }
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
index 3be64a5..ddea0c0 100644
--- a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
@@ -214,13 +214,13 @@
@UserIdInt int userId) throws InterruptedException {
// Adding a blocking listener to ensure CarUserService event notification is completed
// before proceeding with test execution.
- BlockingUserLifecycleListener blockingListener = BlockingUserLifecycleListener
- .newDefaultListener();
+ BlockingUserLifecycleListener blockingListener =
+ BlockingUserLifecycleListener.forAnyEvent().build();
mCarUserService.addUserLifecycleListener(blockingListener);
runOnMainThreadAndWaitForIdle(() -> mCarUserService.onUserLifecycleEvent(eventType,
/* timestampMs= */ 0, /* fromUserId= */ UserHandle.USER_NULL, userId));
- blockingListener.waitForEvent();
+ blockingListener.waitForAnyEvent();
}
/** Overrides framework behavior to succeed on binding/starting processes. */
diff --git a/tests/carservice_unit_test/src/com/android/car/testapi/BlockingUserLifecycleListenerTest.java b/tests/carservice_unit_test/src/com/android/car/testapi/BlockingUserLifecycleListenerTest.java
new file mode 100644
index 0000000..d236848
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/testapi/BlockingUserLifecycleListenerTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.testapi;
+
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.car.testapi.BlockingUserLifecycleListener;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleEvent;
+import android.car.user.CarUserManager.UserLifecycleEventType;
+import android.os.UserHandle;
+import android.util.Log;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public final class BlockingUserLifecycleListenerTest {
+
+ private static final String TAG = BlockingUserLifecycleListenerTest.class.getSimpleName();
+
+ @Test
+ public void testListener_forAnyEvent_invalidBuilderMethods() throws Exception {
+ BlockingUserLifecycleListener.Builder builder = BlockingUserLifecycleListener.forAnyEvent()
+ .setTimeout(666);
+
+ assertThrows(IllegalStateException.class, () -> builder.forUser(10));
+ assertThrows(IllegalStateException.class, () -> builder.forPreviousUser(10));
+ assertThrows(IllegalStateException.class,
+ () -> builder.addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED));
+ }
+
+ @Test
+ public void testForAnyEvent_invalidMethods() throws Exception {
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener.forAnyEvent()
+ .build();
+
+ assertThrows(IllegalStateException.class, () -> listener.waitForEvents());
+ assertThrows(IllegalStateException.class, () -> listener.getAllReceivedEvents());
+ }
+
+ @Test
+ public void testForAnyEvent_timesout() throws Exception {
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener.forAnyEvent()
+ .build();
+
+ assertThrows(IllegalStateException.class, () -> listener.waitForAnyEvent());
+ }
+
+ @Test
+ public void testForAnyEvent_ok() throws Exception {
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener.forAnyEvent()
+ .build();
+ sendAsyncEvents(listener, 10, USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
+
+ UserLifecycleEvent event = listener.waitForAnyEvent();
+ assertEvent(event, 10, USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
+ }
+
+ @Test
+ public void testForSpecificEvents_invalidMethods() throws Exception {
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener.forSpecificEvents()
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING).build();
+
+ assertThrows(IllegalStateException.class, () -> listener.waitForAnyEvent());
+ }
+
+ @Test
+ public void testForSpecificEvents_receivedOnlyExpected() throws Exception {
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener.forSpecificEvents()
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .build();
+
+ sendAsyncEvents(listener, 10,
+ USER_LIFECYCLE_EVENT_TYPE_STARTING,
+ USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
+
+ List<UserLifecycleEvent> events = listener.waitForEvents();
+ assertEvents(events, 10,
+ USER_LIFECYCLE_EVENT_TYPE_STARTING,
+ USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
+
+ List<UserLifecycleEvent> allReceivedEvents = listener.getAllReceivedEvents();
+ assertThat(allReceivedEvents).containsAllIn(events).inOrder();
+ }
+
+ @Test
+ public void testForSpecificEvents_receivedExtraEvents() throws Exception {
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener.forSpecificEvents()
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)
+ .build();
+
+ sendAsyncEvents(listener, 10,
+ USER_LIFECYCLE_EVENT_TYPE_STARTING,
+ USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
+ USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
+
+ List<UserLifecycleEvent> events = listener.waitForEvents();
+ assertEvents(events, 10,
+ USER_LIFECYCLE_EVENT_TYPE_STARTING,
+ USER_LIFECYCLE_EVENT_TYPE_UNLOCKING);
+
+ List<UserLifecycleEvent> allReceivedEvents = listener.getAllReceivedEvents();
+ assertEvents(allReceivedEvents, 10,
+ USER_LIFECYCLE_EVENT_TYPE_STARTING,
+ USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
+ USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
+ }
+
+ @Test
+ public void testForSpecificEvents_filterByUser() throws Exception {
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener.forSpecificEvents()
+ .forUser(10)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .build();
+ UserLifecycleEvent wrong1 = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, 11);
+ UserLifecycleEvent wrong2 = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, 11);
+ UserLifecycleEvent right1 = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, 10);
+ UserLifecycleEvent right2 = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, 10);
+
+ sendAsyncEvents(listener, Arrays.asList(wrong1, right1, right2, wrong2));
+
+ List<UserLifecycleEvent> events = listener.waitForEvents();
+ assertThat(events).containsExactly(right1, right2).inOrder();
+
+ List<UserLifecycleEvent> allReceivedEvents = listener.getAllReceivedEvents();
+ assertThat(allReceivedEvents)
+ .containsExactly(wrong1, right1, right2, wrong2)
+ .inOrder();
+ }
+
+ @Test
+ public void testForSpecificEvents_filterByUserDuplicatedEventTypes() throws Exception {
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener.forSpecificEvents()
+ .forUser(10)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
+ .build();
+ UserLifecycleEvent wrong1 = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, 11);
+ UserLifecycleEvent wrong2 = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, 11);
+ UserLifecycleEvent wrong3 = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, 11);
+ UserLifecycleEvent right1 = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, 10);
+ UserLifecycleEvent right2 = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, 10);
+ UserLifecycleEvent right3 = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, 10);
+
+ sendAsyncEvents(listener, Arrays.asList(wrong1, right1, wrong2, right2, right3, wrong3));
+
+ List<UserLifecycleEvent> events = listener.waitForEvents();
+ assertThat(events).containsExactly(right1, right2, right3).inOrder();
+
+ List<UserLifecycleEvent> allReceivedEvents = listener.getAllReceivedEvents();
+ assertThat(allReceivedEvents)
+ .containsExactly(wrong1, right1, wrong2, right2, right3, wrong3)
+ .inOrder();
+ }
+
+ @Test
+ public void testForSpecificEvents_filterByPreviousUser() throws Exception {
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener.forSpecificEvents()
+ .forPreviousUser(10)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .build();
+ UserLifecycleEvent wrong1 =
+ new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, 11);
+ UserLifecycleEvent wrong2 =
+ new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, 10);
+ UserLifecycleEvent wrong3 =
+ new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, 11, 10);
+ UserLifecycleEvent right1 =
+ new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, 10, 11);
+ UserLifecycleEvent right2 =
+ new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, 10, 12);
+
+ sendAsyncEvents(listener, Arrays.asList(wrong1, right1, wrong2, right2, wrong3));
+
+ List<UserLifecycleEvent> events = listener.waitForEvents();
+ assertThat(events).containsExactly(right1, right2).inOrder();
+
+ List<UserLifecycleEvent> allReceivedEvents = listener.getAllReceivedEvents();
+ assertThat(allReceivedEvents)
+ .containsExactly(wrong1, right1, wrong2, right2, wrong3)
+ .inOrder();
+ }
+
+ @Test
+ public void testForSpecificEvents_filterByPreviousAndTargetUsers() throws Exception {
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener.forSpecificEvents()
+ .forPreviousUser(10)
+ .forUser(11)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .build();
+ UserLifecycleEvent wrong1 =
+ new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, 11);
+ UserLifecycleEvent wrong2 =
+ new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, 10);
+ UserLifecycleEvent wrong3 =
+ new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, 10, 12);
+ UserLifecycleEvent right1 =
+ new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, 10, 11);
+ UserLifecycleEvent right2 =
+ new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, 10, 11);
+
+ sendAsyncEvents(listener, Arrays.asList(wrong1, right1, wrong2, right2, wrong3));
+
+ List<UserLifecycleEvent> events = listener.waitForEvents();
+ assertThat(events).containsExactly(right1, right2).inOrder();
+
+ List<UserLifecycleEvent> allReceivedEvents = listener.getAllReceivedEvents();
+ assertThat(allReceivedEvents)
+ .containsExactly(wrong1, right1, wrong2, right2, wrong3)
+ .inOrder();
+ }
+
+ private static void sendAsyncEvents(@NonNull BlockingUserLifecycleListener listener,
+ @UserIdInt int userId, @UserLifecycleEventType int... eventTypes) {
+ List<UserLifecycleEvent> events = Arrays.stream(eventTypes)
+ .mapToObj((type) -> new UserLifecycleEvent(type, userId))
+ .collect(Collectors.toList());
+ sendAsyncEvents(listener, events);
+ }
+
+ private static void sendAsyncEvents(@NonNull BlockingUserLifecycleListener listener,
+ @NonNull List<UserLifecycleEvent> events) {
+ Log.d(TAG, "sendAsyncEvents(): " + events);
+ new Thread(() -> events.forEach((e) -> listener.onEvent(e)), "AsyncEventsThread").start();
+ }
+
+ private static void assertEvent(UserLifecycleEvent event, @UserIdInt int expectedUserId,
+ @UserLifecycleEventType int expectedType) {
+ assertThat(event).isNotNull();
+ assertWithMessage("wrong type on %s; expected %s", event,
+ CarUserManager.lifecycleEventTypeToString(expectedType)).that(event.getEventType())
+ .isEqualTo(expectedType);
+ assertThat(event.getUserId()).isEqualTo(expectedUserId);
+ assertThat(event.getUserHandle().getIdentifier()).isEqualTo(expectedUserId);
+ assertThat(event.getPreviousUserId()).isEqualTo(UserHandle.USER_NULL);
+ assertThat(event.getPreviousUserHandle()).isNull();
+ }
+
+ private static void assertEvents(List<UserLifecycleEvent> events, @UserIdInt int expectedUserId,
+ @UserLifecycleEventType int... expectedTypes) {
+ assertThat(events).isNotNull();
+ assertThat(events).hasSize(expectedTypes.length);
+ for (int i = 0; i < expectedTypes.length; i++) {
+ int expectedType = expectedTypes[i];
+ UserLifecycleEvent event = events.get(i);
+ assertEvent(event, expectedUserId, expectedType);
+ }
+ }
+}
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 abfd48d..13cebbd 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
@@ -15,6 +15,7 @@
*/
package com.android.car.user;
+import static android.car.test.mocks.AndroidMockitoHelper.getResult;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUsers;
import static android.car.test.util.UserTestingHelper.newUsers;
import static android.car.testapi.CarMockitoHelper.mockHandleRemoteExceptionFromCarServiceWithDefaultValue;
@@ -26,21 +27,22 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.car.Car;
import android.car.ICarUserService;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleListener;
import android.car.user.CarUserManager.UserSwitchUiCallback;
-import android.car.user.GetUserIdentificationAssociationResponse;
+import android.car.user.UserIdentificationAssociationResponse;
import android.car.user.UserSwitchResult;
import android.content.pm.UserInfo;
import android.os.RemoteException;
@@ -53,13 +55,9 @@
import org.mockito.Mock;
import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
public final class CarUserManagerUnitTest extends AbstractExtendedMockitoTestCase {
- private static final long ASYNC_TIMEOUT_MS = 500;
-
@Mock
private Car mCar;
@Mock
@@ -117,9 +115,55 @@
}
@Test
+ public void testAddListener_nullExecutor() {
+ assertThrows(NullPointerException.class, () -> mMgr.addListener(null, (e) -> { }));
+ }
+
+ @Test
+ public void testAddListener_nullListener() {
+ assertThrows(NullPointerException.class, () -> mMgr.addListener(Runnable::run, null));
+ }
+
+ @Test
+ public void testAddListener_sameListenerAddedTwice() {
+ UserLifecycleListener listener = (e) -> { };
+
+ mMgr.addListener(Runnable::run, listener);
+ assertThrows(IllegalStateException.class, () -> mMgr.addListener(Runnable::run, listener));
+ }
+
+ @Test
+ public void testAddListener_differentListenersAddedTwice() {
+ mMgr.addListener(Runnable::run, (e) -> { });
+ mMgr.addListener(Runnable::run, (e) -> { });
+ }
+
+ @Test
+ public void testRemoveListener_nullListener() {
+ assertThrows(NullPointerException.class, () -> mMgr.removeListener(null));
+ }
+
+ @Test
+ public void testRemoveListener_notAddedBefore() {
+ UserLifecycleListener listener = (e) -> { };
+
+ assertThrows(IllegalStateException.class, () -> mMgr.removeListener(listener));
+ }
+
+ @Test
+ public void testRemoveListener_addAndRemove() {
+ UserLifecycleListener listener = (e) -> { };
+
+ mMgr.addListener(Runnable::run, listener);
+ mMgr.removeListener(listener);
+
+ // Make sure it was removed
+ assertThrows(IllegalStateException.class, () -> mMgr.removeListener(listener));
+ }
+
+ @Test
public void testSwitchUser_success() throws Exception {
- expectServiceSwitchUserSucceeds(11, UserSwitchResult.STATUS_SUCCESSFUL,
- "D'OH!");
+ expectServiceSwitchUserSucceeds(11, UserSwitchResult.STATUS_SUCCESSFUL, "D'OH!");
AndroidFuture<UserSwitchResult> future = mMgr.switchUser(11);
@@ -170,24 +214,101 @@
@Test
public void testGetUserIdentificationAssociation_remoteException() throws Exception {
+ int[] types = new int[] {1};
+ when(mService.getUserIdentificationAssociation(types))
+ .thenThrow(new RemoteException("D'OH!"));
mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
- assertThrows(IllegalArgumentException.class,
- () -> mMgr.getUserIdentificationAssociation(new int[] {}));
+
+ assertThat(mMgr.getUserIdentificationAssociation(types)).isNull();
}
@Test
public void testGetUserIdentificationAssociation_ok() throws Exception {
int[] types = new int[] { 4, 8, 15, 16, 23, 42 };
- GetUserIdentificationAssociationResponse expectedResponse =
- new GetUserIdentificationAssociationResponse(null, new int[] {});
+ UserIdentificationAssociationResponse expectedResponse =
+ UserIdentificationAssociationResponse.forSuccess(types);
when(mService.getUserIdentificationAssociation(types)).thenReturn(expectedResponse);
- GetUserIdentificationAssociationResponse actualResponse =
+ UserIdentificationAssociationResponse actualResponse =
mMgr.getUserIdentificationAssociation(types);
assertThat(actualResponse).isSameAs(expectedResponse);
}
+ @Test
+ public void testSetUserIdentificationAssociation_nullTypes() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mMgr.setUserIdentificationAssociation(null, new int[] {42}));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_emptyTypes() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mMgr.setUserIdentificationAssociation(new int[0], new int[] {42}));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_nullValues() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mMgr.setUserIdentificationAssociation(new int[] {42}, null));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_emptyValues() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mMgr.setUserIdentificationAssociation(new int[] {42}, new int[0]));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_sizeMismatch() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mMgr.setUserIdentificationAssociation(new int[] {1}, new int[] {2, 3}));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_remoteException() throws Exception {
+ int[] types = new int[] {1};
+ int[] values = new int[] {2};
+ doThrow(new RemoteException("D'OH!")).when(mService)
+ .setUserIdentificationAssociation(anyInt(), same(types), same(values), notNull());
+ mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+ AndroidFuture<UserIdentificationAssociationResponse> future =
+ mMgr.setUserIdentificationAssociation(types, values);
+
+ assertThat(future).isNotNull();
+ UserIdentificationAssociationResponse result = getResult(future);
+ assertThat(result.isSuccess()).isFalse();
+ assertThat(result.getValues()).isNull();
+ assertThat(result.getErrorMessage()).isNull();
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_ok() throws Exception {
+ int[] types = new int[] { 1, 2, 3 };
+ int[] values = new int[] { 10, 20, 30 };
+ doAnswer((inv) -> {
+ @SuppressWarnings("unchecked")
+ AndroidFuture<UserIdentificationAssociationResponse> future =
+ (AndroidFuture<UserIdentificationAssociationResponse>) inv.getArguments()[3];
+ UserIdentificationAssociationResponse response =
+ UserIdentificationAssociationResponse.forSuccess(values, "D'OH!");
+ future.complete(response);
+ return null;
+ }).when(mService)
+ .setUserIdentificationAssociation(anyInt(), same(types), same(values), notNull());
+ mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+ AndroidFuture<UserIdentificationAssociationResponse> future =
+ mMgr.setUserIdentificationAssociation(types, values);
+
+ assertThat(future).isNotNull();
+ UserIdentificationAssociationResponse result = getResult(future);
+ assertThat(result.isSuccess()).isTrue();
+ assertThat(result.getValues()).asList().containsAllOf(10, 20, 30).inOrder();
+ assertThat(result.getErrorMessage()).isEqualTo("D'OH!");
+ }
+
private void expectServiceSwitchUserSucceeds(@UserIdInt int userId,
@UserSwitchResult.Status int status, @Nullable String errorMessage)
throws RemoteException {
@@ -205,15 +326,6 @@
.switchUser(eq(userId), anyInt(), notNull());
}
- @NonNull
- private static <T> T getResult(@NonNull AndroidFuture<T> future) throws Exception {
- try {
- return future.get(ASYNC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- } catch (TimeoutException e) {
- throw new IllegalStateException("not called in " + ASYNC_TIMEOUT_MS + "ms", e);
- }
- }
-
private void setExistingUsers(int... userIds) {
List<UserInfo> users = newUsers(userIds);
mockUmGetUsers(mUserManager, users);
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java
index 3b95c82..f50ad32 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserNoticeServiceTest.java
@@ -16,26 +16,25 @@
package com.android.car.user;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static android.car.test.mocks.AndroidMockitoHelper.mockContextGetService;
-import static com.google.common.truth.Truth.assertThat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
+import android.car.AbstractExtendedMockitoCarServiceTestCase;
import android.car.hardware.power.CarPowerManager;
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
import android.car.settings.CarSettings;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.mocks.JavaMockitoHelper;
import android.car.user.CarUserManager;
import android.car.user.CarUserManager.UserLifecycleEvent;
import android.car.user.CarUserManager.UserLifecycleListener;
@@ -64,13 +63,16 @@
import org.mockito.Mock;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-public class CarUserNoticeServiceTest extends AbstractExtendedMockitoTestCase {
+public class CarUserNoticeServiceTest extends AbstractExtendedMockitoCarServiceTestCase {
+
+ private static final long TIMEOUT_MS = 10_000;
@Mock
private Context mMockContext;
@Mock
+ private Context mOtherMockContext;
+ @Mock
private Resources mMockedResources;
@Mock
private CarPowerManagementService mMockCarPowerManagementService;
@@ -110,22 +112,21 @@
@Before
public void setUpMocks() throws Exception {
doReturn(mCarPowerManager).when(() -> CarLocalServices.createCarPowerManager(mMockContext));
- doReturn(mMockCarPowerManagementService)
- .when(() -> CarLocalServices.getService(CarPowerManagementService.class));
- doReturn(mMockCarUserService)
- .when(() -> CarLocalServices.getService(CarUserService.class));
+ mockGetCarLocalService(CarPowerManagementService.class, mMockCarPowerManagementService);
+ mockGetCarLocalService(CarUserService.class, mMockCarUserService);
putSettingsInt(CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER, 1);
- doReturn(mMockedResources).when(mMockContext).getResources();
- doReturn(InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getContentResolver())
- .when(mMockContext).getContentResolver();
- doReturn("com.foo/.Blah").when(mMockedResources).getString(anyInt());
- doReturn(mMockPowerManager).when(mMockContext).getSystemService(PowerManager.class);
- doReturn(mMockAppOpsManager).when(mMockContext).getSystemService(AppOpsManager.class);
- doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
- doReturn(1).when(mMockPackageManager).getPackageUidAsUser(any(), anyInt());
+ when(mMockContext.getResources()).thenReturn(mMockedResources);
+ when(mMockContext.getContentResolver())
+ .thenReturn(InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .getContentResolver());
+ when(mMockedResources.getString(anyInt())).thenReturn("com.foo/.Blah");
+
+ mockContextGetService(mMockContext, PowerManager.class, mMockPowerManager);
+ mockContextGetService(mMockContext, AppOpsManager.class, mMockAppOpsManager);
+ mockContextGetService(mMockContext, PackageManager.class, mMockPackageManager);
+ when(mMockPackageManager.getPackageUidAsUser(any(), anyInt())).thenReturn(1);
mCarUserNoticeService = new CarUserNoticeService(mMockContext, mHandler);
mCarUserNoticeService.init();
verify(mMockCarUserService).addUserLifecycleListener(
@@ -137,14 +138,14 @@
@Test
public void featureDisabledTest() {
- Context mockContext = mock(Context.class);
- // if feature is disabled, Resources.getString will return an
- // empty string
- doReturn("").when(mMockedResources).getString(R.string.config_userNoticeUiService);
- doReturn(mMockedResources).when(mockContext).getResources();
- CarUserNoticeService carUserNoticeService = new CarUserNoticeService(mockContext);
- carUserNoticeService.init();
- verify(mockContext, never()).registerReceiver(any(), any());
+ // Feature is disabled when the string is empty
+ when(mMockedResources.getString(R.string.config_userNoticeUiService)).thenReturn("");
+ when(mOtherMockContext.getResources()).thenReturn(mMockedResources);
+
+ CarUserNoticeService otherService = new CarUserNoticeService(mOtherMockContext);
+ otherService.init();
+
+ verify(mOtherMockContext, never()).registerReceiver(any(), any());
}
@Test
@@ -154,7 +155,7 @@
setDisplayOff();
CountDownLatch latch = mockUnbindService();
sendBroadcast(Intent.ACTION_SCREEN_OFF);
- assetLatchCalled(latch);
+ assertLatchCalled(latch);
}
@Test
@@ -164,13 +165,13 @@
setDisplayOff();
CountDownLatch latch = mockUnbindService();
sendBroadcast(Intent.ACTION_SCREEN_OFF);
- assetLatchCalled(latch);
+ assertLatchCalled(latch);
// send screen on broadcast
setDisplayOn();
latch = mockBindService();
sendBroadcast(Intent.ACTION_SCREEN_ON);
- assetLatchCalled(latch);
+ assertLatchCalled(latch);
}
@Test
@@ -180,7 +181,7 @@
setDisplayOff();
CountDownLatch latch = mockUnbindService();
sendPowerStateChange(CarPowerManager.CarPowerStateListener.SHUTDOWN_PREPARE);
- assetLatchCalled(latch);
+ assertLatchCalled(latch);
}
@Test
@@ -190,13 +191,13 @@
setDisplayOff();
CountDownLatch latch = mockUnbindService();
sendPowerStateChange(CarPowerManager.CarPowerStateListener.SHUTDOWN_PREPARE);
- assetLatchCalled(latch);
+ assertLatchCalled(latch);
// send Power On
setDisplayOn();
latch = mockBindService();
sendPowerStateChange(CarPowerManager.CarPowerStateListener.ON);
- assetLatchCalled(latch);
+ assertLatchCalled(latch);
}
@Test
@@ -206,20 +207,20 @@
setDisplayOff();
CountDownLatch latch = mockUnbindService();
sendBroadcast(Intent.ACTION_SCREEN_OFF);
- assetLatchCalled(latch);
+ assertLatchCalled(latch);
// UI not shown if key is disabled
setDisplayOn();
latch = mockKeySettings(
CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER, 0);
sendBroadcast(Intent.ACTION_SCREEN_ON);
- assetLatchCalled(latch);
+ assertLatchCalled(latch);
// invoked only once, when user switched
verify(mMockContext, times(1)).bindServiceAsUser(any(), any(), anyInt(), any());
}
- private void assetLatchCalled(CountDownLatch latch) throws Exception {
- assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
+ private static void assertLatchCalled(CountDownLatch latch) throws Exception {
+ JavaMockitoHelper.await(latch, TIMEOUT_MS);
}
private void switchUser(int userId) throws Exception {
@@ -260,11 +261,11 @@
}
private void setDisplayOn() {
- doReturn(true).when(mMockPowerManager).isInteractive();
+ when(mMockPowerManager.isInteractive()).thenReturn(true);
}
private void setDisplayOff() {
- doReturn(false).when(mMockPowerManager).isInteractive();
+ when(mMockPowerManager.isInteractive()).thenReturn(false);
}
private void sendBroadcast(String action) {
@@ -282,6 +283,6 @@
setDisplayOn();
CountDownLatch latch = mockBindService();
switchUser(UserHandle.MIN_SECONDARY_USER_ID);
- assetLatchCalled(latch);
+ assertLatchCalled(latch);
}
}
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 f52e48d..87fdc18 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
@@ -16,6 +16,8 @@
package com.android.car.user;
+import static android.car.test.mocks.AndroidMockitoHelper.getResult;
+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;
@@ -54,6 +56,7 @@
import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.car.settings.CarSettings;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.mocks.AndroidMockitoHelper;
import android.car.test.mocks.BlockingAnswer;
import android.car.test.util.BlockingResultReceiver;
import android.car.testapi.BlockingUserLifecycleListener;
@@ -61,12 +64,14 @@
import android.car.user.CarUserManager.UserLifecycleEvent;
import android.car.user.CarUserManager.UserLifecycleEventType;
import android.car.user.CarUserManager.UserLifecycleListener;
-import android.car.user.GetUserIdentificationAssociationResponse;
+import android.car.user.UserIdentificationAssociationResponse;
import android.car.user.UserSwitchResult;
import android.car.userlib.CarUserManagerHelper;
import android.car.userlib.HalCallback;
import android.car.userlib.UserHalHelper;
+import android.car.userlib.UserHelper;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -79,12 +84,15 @@
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest;
import android.hardware.automotive.vehicle.V2_0.UsersInfo;
import android.location.LocationManager;
+import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.sysprop.CarProperties;
import android.util.Log;
import android.util.SparseArray;
@@ -107,9 +115,8 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
/**
* This class contains unit tests for the {@link CarUserService}.
@@ -141,9 +148,10 @@
@Mock private Drawable mMockedDrawable;
@Mock private UserMetrics mUserMetrics;
@Mock IResultReceiver mSwitchUserUiReceiver;
+ @Mock PackageManager mPackageManager;
private final BlockingUserLifecycleListener mUserLifecycleListener =
- BlockingUserLifecycleListener.newDefaultListener();
+ BlockingUserLifecycleListener.forAnyEvent().build();
@Captor private ArgumentCaptor<UsersInfo> mUsersInfoCaptor;
@@ -153,6 +161,8 @@
private final int mGetUserInfoRequestType = InitialUserInfoRequestType.COLD_BOOT;
private final AndroidFuture<UserSwitchResult> mUserSwitchFuture = new AndroidFuture<>();
+ private final AndroidFuture<UserIdentificationAssociationResponse> mUserAssociationRespFuture =
+ new AndroidFuture<>();
private final int mAsyncCallTimeoutMs = 100;
private final BlockingResultReceiver mReceiver =
new BlockingResultReceiver(mAsyncCallTimeoutMs);
@@ -174,7 +184,12 @@
@Override
protected void onSessionBuilder(CustomMockitoSessionBuilder builder) {
builder
- .spyStatic(ActivityManager.class);
+ .spyStatic(ActivityManager.class)
+ // TODO(b/156299496): it cannot spy on UserManager, as it would slow down the tests
+ // considerably (more than 5 minutes total, instead of just a couple seconds). So, it's
+ // mocking UserHelper.isHeadlessSystemUser() (on mockIsHeadlessSystemUser()) instead...
+ .spyStatic(UserHelper.class)
+ .spyStatic(CarProperties.class);
}
/**
@@ -193,6 +208,7 @@
doReturn(mMockedDrawable).when(mMockedDrawable).mutate();
doReturn(1).when(mMockedDrawable).getIntrinsicWidth();
doReturn(1).when(mMockedDrawable).getIntrinsicHeight();
+ doReturn(Optional.of(mAsyncCallTimeoutMs)).when(() -> CarProperties.user_hal_timeout());
mCarUserService =
new CarUserService(
mMockContext,
@@ -218,7 +234,7 @@
}
@Test
- public void testOnUserLifecycleEvent_nofityListener() throws Exception {
+ public void testOnUserLifecycleEvent_notifyListener() throws Exception {
// Arrange
mCarUserService.addUserLifecycleListener(mUserLifecycleListener);
mockExistingUsers();
@@ -254,11 +270,19 @@
private void verifyListenerOnEventInvoked(int expectedNewUserId, int expectedEventType)
throws Exception {
- UserLifecycleEvent actualEvent = mUserLifecycleListener.waitForEvent();
+ UserLifecycleEvent actualEvent = mUserLifecycleListener.waitForAnyEvent();
assertThat(actualEvent.getEventType()).isEqualTo(expectedEventType);
assertThat(actualEvent.getUserId()).isEqualTo(expectedNewUserId);
}
+ private void verifyLastActiveUserSet(@UserIdInt int userId) {
+ verify(mMockedCarUserManagerHelper).setLastActiveUser(userId);
+ }
+
+ private void verifyLastActiveUserNotSet() {
+ verify(mMockedCarUserManagerHelper, never()).setLastActiveUser(anyInt());
+ }
+
/**
* Test that the {@link CarUserService} disables the location service for headless user 0 upon
* first run.
@@ -271,13 +295,32 @@
}
/**
- * Test that the {@link CarUserService} updates last active user on user switch.
+ * Test that the {@link CarUserService} updates last active user on user switch in non-headless
+ * system user mode.
*/
@Test
- public void testLastActiveUserUpdatedOnUserSwitch() throws Exception {
+ public void testLastActiveUserUpdatedOnUserSwitch_nonHeadlessSystemUser() throws Exception {
+ mockIsHeadlessSystemUser(mRegularUser.id, false);
mockExistingUsers();
+
sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
- verify(mMockedCarUserManagerHelper).setLastActiveUser(mRegularUser.id);
+
+ verifyLastActiveUserSet(mRegularUser.id);
+ }
+
+ /**
+ * 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();
}
/**
@@ -489,34 +532,37 @@
}
@Test
- public void testSwitchDriver() throws RemoteException {
- int currentId = 11;
- int targetId = 12;
- mockGetCurrentUser(currentId);
- doReturn(true).when(mMockedIActivityManager).switchUser(targetId);
- doReturn(false).when(mMockedUserManager)
- .hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
- assertTrue(mCarUserService.switchDriver(targetId));
+ public void testSwitchDriver() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ int requestId = 42;
+ mSwitchUserResponse.status = SwitchUserStatus.SUCCESS;
+ mSwitchUserResponse.requestId = requestId;
+ mockHalSwitch(mAdminUser.id, mRegularUser, mSwitchUserResponse);
+ mockAmSwitchUser(mRegularUser, true);
+ when(mMockedUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH))
+ .thenReturn(false);
+ mCarUserService.switchDriver(mRegularUser.id, mUserSwitchFuture);
+ assertThat(getUserSwitchResult().getStatus())
+ .isEqualTo(UserSwitchResult.STATUS_SUCCESSFUL);
}
@Test
- public void testSwitchDriver_IfUserSwitchIsNotAllowed() throws RemoteException {
- int currentId = 11;
- int targetId = 12;
- mockGetCurrentUser(currentId);
- doReturn(true).when(mMockedIActivityManager).switchUser(targetId);
- doReturn(UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED).when(mMockedUserManager)
- .getUserSwitchability();
- assertFalse(mCarUserService.switchDriver(targetId));
+ public void testSwitchDriver_IfUserSwitchIsNotAllowed() throws Exception {
+ when(mMockedUserManager.getUserSwitchability())
+ .thenReturn(UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED);
+ mCarUserService.switchDriver(mRegularUser.id, mUserSwitchFuture);
+ assertThat(getUserSwitchResult().getStatus())
+ .isEqualTo(UserSwitchResult.STATUS_INVALID_REQUEST);
}
@Test
- public void testSwitchDriver_IfSwitchedToCurrentUser() throws RemoteException {
- int currentId = 11;
- mockGetCurrentUser(currentId);
- doReturn(false).when(mMockedUserManager)
- .hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
- assertTrue(mCarUserService.switchDriver(11));
+ public void testSwitchDriver_IfSwitchedToCurrentUser() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ when(mMockedUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH))
+ .thenReturn(false);
+ mCarUserService.switchDriver(mAdminUser.id, mUserSwitchFuture);
+ assertThat(getUserSwitchResult().getStatus())
+ .isEqualTo(UserSwitchResult.STATUS_ALREADY_REQUESTED_USER);
}
@Test
@@ -571,7 +617,8 @@
new UserInfo(15, "test15", UserInfo.FLAG_EPHEMERAL),
new UserInfo(16, "test16", UserInfo.FLAG_DISABLED),
new UserInfo(17, "test17", UserInfo.FLAG_MANAGED_PROFILE),
- new UserInfo(18, "test18", UserInfo.FLAG_MANAGED_PROFILE)
+ new UserInfo(18, "test18", UserInfo.FLAG_MANAGED_PROFILE),
+ new UserInfo(19, "test19", NO_USER_INFO_FLAGS)
));
// Parent: test10, child: test12
associateParentChild(users.get(0), users.get(2));
@@ -585,12 +632,13 @@
@Test
public void testGetAllPossibleDrivers() {
Set<Integer> expected = new HashSet<Integer>(Arrays.asList(10, 11, 13, 14));
- doReturn(prepareUserList()).when(mMockedUserManager).getUsers(true);
+ when(mMockedUserManager.getUsers(true)).thenReturn(prepareUserList());
+ mockIsHeadlessSystemUser(19, true);
for (UserInfo user : mCarUserService.getAllDrivers()) {
- assertTrue(expected.contains(user.id));
+ assertThat(expected).contains(user.id);
expected.remove(user.id);
}
- assertEquals(0, expected.size());
+ assertThat(expected).isEmpty();
}
@Test
@@ -600,18 +648,19 @@
put(0, new HashSet<Integer>());
put(10, new HashSet<Integer>(Arrays.asList(12)));
put(11, new HashSet<Integer>());
- put(13, new HashSet<Integer>(Arrays.asList(17, 18)));
+ put(13, new HashSet<Integer>(Arrays.asList(17)));
}
};
+ mockIsHeadlessSystemUser(18, true);
for (int i = 0; i < testCases.size(); i++) {
- doReturn(prepareUserList()).when(mMockedUserManager).getUsers(true);
+ when(mMockedUserManager.getUsers(true)).thenReturn(prepareUserList());
List<UserInfo> passengers = mCarUserService.getPassengers(testCases.keyAt(i));
HashSet<Integer> expected = testCases.valueAt(i);
for (UserInfo user : passengers) {
- assertTrue(expected.contains(user.id));
+ assertThat(expected).contains(user.id);
expected.remove(user.id);
}
- assertEquals(0, expected.size());
+ assertThat(expected).isEmpty();
}
}
@@ -939,6 +988,8 @@
@Test
public void testSetSwitchUserUI_receiverSetAndCalled() throws Exception {
mockExistingUsersAndCurrentUser(mAdminUser);
+ int callerId = Binder.getCallingUid();
+ mockCallerUid(callerId, true);
int requestId = 42;
mSwitchUserResponse.status = SwitchUserStatus.SUCCESS;
mSwitchUserResponse.requestId = requestId;
@@ -953,6 +1004,39 @@
}
@Test
+ public void testSetSwitchUserUI_nonCarSysUiCaller() throws Exception {
+ int callerId = Binder.getCallingUid();
+ mockCallerUid(callerId, false);
+
+ assertThrows(SecurityException.class,
+ () -> mCarUserService.setUserSwitchUiCallback(mSwitchUserUiReceiver));
+ }
+
+ @Test
+ public void testSwitchUser_OEMRequest_success() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ mockAmSwitchUser(mRegularUser, true);
+ int requestId = -1;
+
+ mCarUserService.switchAndroidUserFromHal(requestId, mRegularUser.id);
+ mockCurrentUser(mRegularUser);
+ sendUserUnlockedEvent(mRegularUser.id);
+
+ assertPostSwitch(requestId, mRegularUser.id, mRegularUser.id);
+ }
+
+ @Test
+ public void testSwitchUser_OEMRequest_failure() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ mockAmSwitchUser(mRegularUser, false);
+ int requestId = -1;
+
+ mCarUserService.switchAndroidUserFromHal(requestId, mRegularUser.id);
+
+ assertPostSwitch(requestId, mAdminUser.id, mRegularUser.id);
+ }
+
+ @Test
public void testGetUserInfo_nullReceiver() throws Exception {
assertThrows(NullPointerException.class, () -> mCarUserService
.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, null));
@@ -978,6 +1062,24 @@
Bundle resultData = mReceiver.getResultData();
assertThat(resultData).isNotNull();
assertInitialInfoAction(resultData, mGetUserInfoResponse.action);
+ assertInitialInfoUserLocales(resultData, null);
+ }
+
+ @Test
+ public void testGetUserInfo_defaultResponse_withLocale() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+
+ mGetUserInfoResponse.action = InitialUserInfoResponseAction.DEFAULT;
+ mGetUserInfoResponse.userLocales = "LOL";
+ mockGetInitialInfo(mAdminUser.id, mGetUserInfoResponse);
+
+ mCarUserService.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, mReceiver);
+
+ assertThat(mReceiver.getResultCode()).isEqualTo(HalCallback.STATUS_OK);
+ Bundle resultData = mReceiver.getResultData();
+ assertThat(resultData).isNotNull();
+ assertInitialInfoAction(resultData, mGetUserInfoResponse.action);
+ assertInitialInfoUserLocales(resultData, "LOL");
}
@Test
@@ -1099,39 +1201,133 @@
@Test
public void testGetUserIdentificationAssociation_service_returnNull() throws Exception {
- // Must use the real user id - and not mock it - as the service will infer the id from
- // the Binder call - it's not worth the effort of mocking that.
- int currentUserId = ActivityManager.getCurrentUser();
- Log.d(TAG, "testGetUserIdentificationAssociation_ok(): current user is " + currentUserId);
- UserInfo currentUser = mockUmGetUserInfo(mMockedUserManager, currentUserId,
- UserInfo.FLAG_ADMIN);
+ mockCurrentUserForBinderCalls();
// Not mocking service call, so it will return null
- GetUserIdentificationAssociationResponse response = mCarUserService
+ UserIdentificationAssociationResponse response = mCarUserService
.getUserIdentificationAssociation(new int[] { 108 });
- assertThat(response).isNull();
+ assertThat(response.isSuccess()).isFalse();
+ assertThat(response.getValues()).isNull();
+ assertThat(response.getErrorMessage()).isNull();
}
@Test
public void testGetUserIdentificationAssociation_ok() throws Exception {
- // Must use the real user id - and not mock it - as the service will infer the id from
- // the Binder call - it's not worth the effort of mocking that.
- int currentUserId = ActivityManager.getCurrentUser();
- Log.d(TAG, "testGetUserIdentificationAssociation_ok(): current user is " + currentUserId);
- UserInfo currentUser = mockUmGetUserInfo(mMockedUserManager, currentUserId,
- UserInfo.FLAG_ADMIN);
+ UserInfo currentUser = mockCurrentUserForBinderCalls();
int[] types = new int[] { 1, 2, 3 };
- mockHalGetUserIdentificationAssociation(currentUser, types, new int[] { 10, 20, 30 },
- "D'OH!");
+ int[] values = new int[] { 10, 20, 30 };
+ mockHalGetUserIdentificationAssociation(currentUser, types, values, "D'OH!");
- GetUserIdentificationAssociationResponse response = mCarUserService
+ UserIdentificationAssociationResponse response = mCarUserService
.getUserIdentificationAssociation(types);
- assertThat(response.getValues()).asList().containsExactly(10, 20, 30)
- .inOrder();
+ assertThat(response.isSuccess()).isTrue();
+ assertThat(response.getValues()).asList().containsAllOf(10, 20, 30).inOrder();
+ assertThat(response.getErrorMessage()).isEqualTo("D'OH!");
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_nullTypes() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> mCarUserService
+ .setUserIdentificationAssociation(mAsyncCallTimeoutMs,
+ null, new int[] {42}, mUserAssociationRespFuture));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_emptyTypes() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> mCarUserService
+ .setUserIdentificationAssociation(mAsyncCallTimeoutMs,
+ new int[0], new int[] {42}, mUserAssociationRespFuture));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_nullValues() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> mCarUserService
+ .setUserIdentificationAssociation(mAsyncCallTimeoutMs,
+ new int[] {42}, null, mUserAssociationRespFuture));
+ }
+ @Test
+ public void testSetUserIdentificationAssociation_sizeMismatch() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> mCarUserService
+ .setUserIdentificationAssociation(mAsyncCallTimeoutMs,
+ new int[] {1}, new int[] {2, 2}, mUserAssociationRespFuture));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_nullFuture() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> mCarUserService
+ .setUserIdentificationAssociation(mAsyncCallTimeoutMs,
+ new int[] {42}, new int[] {42}, null));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_noPermission() throws Exception {
+ mockManageUsersPermission(android.Manifest.permission.MANAGE_USERS, false);
+ assertThrows(SecurityException.class, () -> mCarUserService
+ .setUserIdentificationAssociation(mAsyncCallTimeoutMs,
+ new int[] {42}, new int[] {42}, mUserAssociationRespFuture));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_noCurrentUser() throws Exception {
+ // Should fail because we're not mocking UserManager.getUserInfo() to set the flag
+ assertThrows(IllegalArgumentException.class, () -> mCarUserService
+ .setUserIdentificationAssociation(mAsyncCallTimeoutMs,
+ new int[] {42}, new int[] {42}, mUserAssociationRespFuture));
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_halFailedWithErrorMessage() throws Exception {
+ mockCurrentUserForBinderCalls();
+ mockHalSetUserIdentificationAssociationFailure("D'OH!");
+ int[] types = new int[] { 1, 2, 3 };
+ int[] values = new int[] { 10, 20, 30 };
+ mCarUserService.setUserIdentificationAssociation(mAsyncCallTimeoutMs, types, values,
+ mUserAssociationRespFuture);
+
+ UserIdentificationAssociationResponse response = getUserAssociationRespResult();
+
+ assertThat(response.isSuccess()).isFalse();
+ assertThat(response.getValues()).isNull();
+ assertThat(response.getErrorMessage()).isEqualTo("D'OH!");
+
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_halFailedWithoutErrorMessage()
+ throws Exception {
+ mockCurrentUserForBinderCalls();
+ mockHalSetUserIdentificationAssociationFailure(/* errorMessage= */ null);
+ int[] types = new int[] { 1, 2, 3 };
+ int[] values = new int[] { 10, 20, 30 };
+ mCarUserService.setUserIdentificationAssociation(mAsyncCallTimeoutMs, types, values,
+ mUserAssociationRespFuture);
+
+ UserIdentificationAssociationResponse response = getUserAssociationRespResult();
+
+ assertThat(response.isSuccess()).isFalse();
+ assertThat(response.getValues()).isNull();
+ assertThat(response.getErrorMessage()).isNull();
+ }
+
+ @Test
+ public void testSetUserIdentificationAssociation_ok() throws Exception {
+ UserInfo currentUser = mockCurrentUserForBinderCalls();
+
+ int[] types = new int[] { 1, 2, 3 };
+ int[] values = new int[] { 10, 20, 30 };
+ mockHalSetUserIdentificationAssociationSuccess(currentUser, types, values, "D'OH!");
+
+ mCarUserService.setUserIdentificationAssociation(mAsyncCallTimeoutMs, types, values,
+ mUserAssociationRespFuture);
+
+ UserIdentificationAssociationResponse response = getUserAssociationRespResult();
+
+ assertThat(response.isSuccess()).isTrue();
+ assertThat(response.getValues()).asList().containsAllOf(10, 20, 30).inOrder();
assertThat(response.getErrorMessage()).isEqualTo("D'OH!");
}
@@ -1161,12 +1357,22 @@
}
@NonNull
- private <T> T getResult(@NonNull AndroidFuture<T> future) throws Exception {
- try {
- return future.get(mAsyncCallTimeoutMs, TimeUnit.MILLISECONDS);
- } catch (TimeoutException e) {
- throw new IllegalStateException("not called in " + mAsyncCallTimeoutMs + "ms", e);
- }
+ private UserIdentificationAssociationResponse getUserAssociationRespResult()
+ throws Exception {
+ return getResult(mUserAssociationRespFuture);
+ }
+
+ /**
+ * This method must be called for cases where the service infers the user id of the caller
+ * using Binder - it's not worth the effort of mocking such (native) calls.
+ */
+ @NonNull
+ private UserInfo mockCurrentUserForBinderCalls() {
+ int currentUserId = ActivityManager.getCurrentUser();
+ Log.d(TAG, "testetUserIdentificationAssociation_ok(): current user is " + currentUserId);
+ UserInfo currentUser = mockUmGetUserInfo(mMockedUserManager, currentUserId,
+ UserInfo.FLAG_ADMIN);
+ return currentUser;
}
/**
@@ -1181,7 +1387,7 @@
private void mockExistingUsers() {
mockUmGetUsers(mMockedUserManager, mExistingUsers);
for (UserInfo user : mExistingUsers) {
- when(mMockedUserManager.getUserInfo(user.id)).thenReturn(user);
+ AndroidMockitoHelper.mockUmGetUserInfo(mMockedUserManager, user);
}
}
@@ -1208,11 +1414,29 @@
eq(usersInfo), notNull());
}
+ private void mockIsHeadlessSystemUser(@UserIdInt int userId, boolean mode) {
+ doReturn(mode).when(() -> UserHelper.isHeadlessSystemUser(userId));
+ }
+
private void mockHalSwitch(@UserIdInt int currentUserId, @NonNull UserInfo androidTargetUser,
@Nullable SwitchUserResponse response) {
mockHalSwitch(currentUserId, HalCallback.STATUS_OK, response, androidTargetUser);
}
+ private void mockCallerUid(int uid, boolean returnCorrectUid) throws Exception {
+ String packageName = "packageName";
+ String className = "className";
+ when(mMockedResources.getString(anyInt())).thenReturn(packageName + "/" + className);
+ when(mMockContext.createContextAsUser(any(), anyInt())).thenReturn(mMockContext);
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+
+ if (returnCorrectUid) {
+ when(mPackageManager.getPackageUid(any(), anyInt())).thenReturn(uid);
+ } else {
+ when(mPackageManager.getPackageUid(any(), anyInt())).thenReturn(uid + 1);
+ }
+ }
+
private BlockingAnswer<Void> mockHalSwitchLateResponse(@UserIdInt int currentUserId,
@NonNull UserInfo androidTargetUser, @Nullable SwitchUserResponse response) {
android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
@@ -1272,6 +1496,53 @@
.thenReturn(response);
}
+ private void mockHalSetUserIdentificationAssociationSuccess(@NonNull UserInfo user,
+ @NonNull int[] types, @NonNull int[] values, @Nullable String errorMessage) {
+ assertWithMessage("mismatch on number of types and values").that(types.length)
+ .isEqualTo(values.length);
+
+ UserIdentificationResponse response = new UserIdentificationResponse();
+ response.numberAssociation = types.length;
+ response.errorMessage = errorMessage;
+ for (int i = 0; i < types.length; i++) {
+ UserIdentificationAssociation association = new UserIdentificationAssociation();
+ association.type = types[i];
+ association.value = values[i];
+ response.associations.add(association);
+ }
+
+ doAnswer((invocation) -> {
+ Log.d(TAG, "Answering " + invocation + " with " + response);
+ @SuppressWarnings("unchecked")
+ UserIdentificationSetRequest request =
+ (UserIdentificationSetRequest) invocation.getArguments()[1];
+ assertWithMessage("Wrong user on %s", request)
+ .that(request.userInfo.userId)
+ .isEqualTo(user.id);
+ assertWithMessage("Wrong flags on %s", request)
+ .that(UserHalHelper.toUserInfoFlags(request.userInfo.flags))
+ .isEqualTo(user.flags);
+ @SuppressWarnings("unchecked")
+ HalCallback<UserIdentificationResponse> callback =
+ (HalCallback<UserIdentificationResponse>) invocation.getArguments()[2];
+ callback.onResponse(HalCallback.STATUS_OK, response);
+ return null;
+ }).when(mUserHal).setUserAssociation(eq(mAsyncCallTimeoutMs), notNull(), notNull());
+ }
+
+ private void mockHalSetUserIdentificationAssociationFailure(@NonNull String errorMessage) {
+ UserIdentificationResponse response = new UserIdentificationResponse();
+ response.errorMessage = errorMessage;
+ doAnswer((invocation) -> {
+ Log.d(TAG, "Answering " + invocation + " with " + response);
+ @SuppressWarnings("unchecked")
+ HalCallback<UserIdentificationResponse> callback =
+ (HalCallback<UserIdentificationResponse>) invocation.getArguments()[2];
+ callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, response);
+ return null;
+ }).when(mUserHal).setUserAssociation(eq(mAsyncCallTimeoutMs), notNull(), notNull());
+ }
+
private void mockManageUsersPermission(String permission, boolean granted) {
int result;
if (granted) {
@@ -1383,6 +1654,13 @@
.isEqualTo(expectedAction);
}
+ private void assertInitialInfoUserLocales(Bundle resultData, String expectedLocales) {
+ String actualLocales = resultData.getString(CarUserService.BUNDLE_USER_LOCALES);
+ assertWithMessage("wrong locales on bundle extra %s",
+ CarUserService.BUNDLE_USER_LOCALES).that(actualLocales)
+ .isEqualTo(expectedLocales);
+ }
+
private void assertNoPostSwitch() {
verify(mUserHal, never()).postSwitchResponse(anyInt(), any(), any());
}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/ExperimentalCarUserManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/user/ExperimentalCarUserManagerUnitTest.java
index 026754c..8b4dc55 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/ExperimentalCarUserManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/ExperimentalCarUserManagerUnitTest.java
@@ -15,10 +15,15 @@
*/
package com.android.car.user;
+import static android.car.test.mocks.AndroidMockitoHelper.getResult;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
@@ -28,10 +33,14 @@
import android.car.test.util.UserTestingHelper;
import android.car.user.CarUserManager;
import android.car.user.ExperimentalCarUserManager;
+import android.car.user.UserSwitchResult;
import android.content.pm.UserInfo;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import com.android.internal.infra.AndroidFuture;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@@ -92,16 +101,19 @@
@Test
public void testSwitchDriver_Success() throws Exception {
- expectSwitchDriverSucceed();
- boolean success = mManager.switchDriver(10);
- assertThat(success).isTrue();
+ expectSwitchDriverSucceed(10);
+ AndroidFuture<UserSwitchResult> future = mManager.switchDriver(10);
+ UserSwitchResult result = getResult(future);
+ assertThat(result.getStatus()).isEqualTo(UserSwitchResult.STATUS_SUCCESSFUL);
}
@Test
public void testSwitchDriver_Error() throws Exception {
- expectSwitchDriverFail();
- boolean success = mManager.switchDriver(20);
- assertThat(success).isFalse();
+ expectSwitchDriverFail(20);
+ AndroidFuture<UserSwitchResult> future = mManager.switchDriver(20);
+ assertThat(future).isNotNull();
+ UserSwitchResult result = getResult(future);
+ assertThat(result.getStatus()).isEqualTo(UserSwitchResult.STATUS_HAL_INTERNAL_FAILURE);
}
@Test
@@ -171,12 +183,19 @@
when(mService.createPassenger("test passenger", /* driverId = */ 10)).thenReturn(null);
}
- private void expectSwitchDriverSucceed() throws Exception {
- when(mService.switchDriver(10)).thenReturn(true);
+ private void expectSwitchDriverSucceed(@UserIdInt int userId) throws Exception {
+ doAnswer((invocation) -> {
+ @SuppressWarnings("unchecked")
+ AndroidFuture<UserSwitchResult> future = (AndroidFuture<UserSwitchResult>) invocation
+ .getArguments()[1];
+ future.complete(new UserSwitchResult(UserSwitchResult.STATUS_SUCCESSFUL, null));
+ return null;
+ }).when(mService).switchDriver(eq(userId), notNull());
}
- private void expectSwitchDriverFail() throws Exception {
- when(mService.switchDriver(20)).thenReturn(false);
+ private void expectSwitchDriverFail(@UserIdInt int userId) throws Exception {
+ doThrow(new RemoteException("D'OH!")).when(mService)
+ .switchDriver(eq(userId), notNull());
}
private void expectStartPassengerSucceed() throws Exception {
diff --git a/tests/carservice_unit_test/src/com/android/car/user/UserIdentificationAssociationResponseTest.java b/tests/carservice_unit_test/src/com/android/car/user/UserIdentificationAssociationResponseTest.java
new file mode 100644
index 0000000..f9ca16d
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/user/UserIdentificationAssociationResponseTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.user;
+
+import static android.car.user.UserIdentificationAssociationResponse.forFailure;
+import static android.car.user.UserIdentificationAssociationResponse.forSuccess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.car.user.UserIdentificationAssociationResponse;
+
+import org.junit.Test;
+
+public final class UserIdentificationAssociationResponseTest {
+
+ @Test
+ public void testFailure_noMessage() {
+ UserIdentificationAssociationResponse response = forFailure();
+
+ assertThat(response).isNotNull();
+ assertThat(response.isSuccess()).isFalse();
+ assertThat(response.getErrorMessage()).isNull();
+ assertThat(response.getValues()).isNull();
+ }
+
+ @Test
+ public void testFailure_withMessage() {
+ UserIdentificationAssociationResponse response = forFailure("D'OH!");
+
+ assertThat(response).isNotNull();
+ assertThat(response.isSuccess()).isFalse();
+ assertThat(response.getErrorMessage()).isEqualTo("D'OH!");
+ assertThat(response.getValues()).isNull();
+ }
+
+ @Test
+ public void testSuccess_nullValues() {
+ assertThrows(IllegalArgumentException.class, () -> forSuccess(null));
+ }
+
+ @Test
+ public void testSuccess_nullValuesWithMessage() {
+ assertThrows(IllegalArgumentException.class, () -> forSuccess(null, "D'OH!"));
+ }
+
+ @Test
+ public void testSuccess_emptyValues() {
+ assertThrows(IllegalArgumentException.class, () -> forSuccess(new int[0]));
+ }
+
+ @Test
+ public void testSuccess_emptyValuesWithMessage() {
+ assertThrows(IllegalArgumentException.class, () -> forSuccess(new int[0], "D'OH!"));
+ }
+
+ @Test
+ public void testSuccess_noMessage() {
+ UserIdentificationAssociationResponse response = forSuccess(new int[] {1, 2, 3});
+
+ assertThat(response).isNotNull();
+ assertThat(response.isSuccess()).isTrue();
+ assertThat(response.getErrorMessage()).isNull();
+ assertThat(response.getValues()).asList().containsAllOf(1, 2, 3).inOrder();
+ }
+
+ @Test
+ public void testSuccess_withMessage() {
+ UserIdentificationAssociationResponse response = forSuccess(new int[] {1, 2, 3}, "D'OH!");
+
+ assertThat(response).isNotNull();
+ assertThat(response.isSuccess()).isTrue();
+ assertThat(response.getErrorMessage()).isEqualTo("D'OH!");
+ assertThat(response.getValues()).asList().containsAllOf(1, 2, 3).inOrder();
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/vms/VmsBrokerServiceTest.java b/tests/carservice_unit_test/src/com/android/car/vms/VmsBrokerServiceTest.java
index dd1c02f..e85dece 100644
--- a/tests/carservice_unit_test/src/com/android/car/vms/VmsBrokerServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/vms/VmsBrokerServiceTest.java
@@ -19,8 +19,11 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -46,6 +49,8 @@
import android.os.RemoteException;
import android.os.SharedMemory;
import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Pair;
import androidx.test.filters.SmallTest;
@@ -63,6 +68,7 @@
import java.util.Arrays;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
@RunWith(MockitoJUnitRunner.class)
@@ -106,14 +112,14 @@
@Mock
private IVmsClientCallback mClientCallback1;
@Mock
- private Binder mClientBinder1;
+ private IBinder mClientBinder1;
@Mock
private VmsClientLogger mClientLog1;
@Mock
private IVmsClientCallback mClientCallback2;
@Mock
- private Binder mClientBinder2;
+ private IBinder mClientBinder2;
@Mock
private VmsClientLogger mClientLog2;
@@ -126,6 +132,8 @@
private VmsBrokerService mBrokerService;
private int mCallingAppUid;
+ private Map<IBinder /* token */, Pair<IBinder /* callback */, IBinder.DeathRecipient>>
+ mDeathRecipients = new ArrayMap<>();
@Before
public void setUp() throws Exception {
@@ -160,7 +168,7 @@
@Test
public void testRegister() {
VmsRegistrationInfo registrationInfo =
- mBrokerService.registerClient(mClientToken1, mClientCallback1, false);
+ registerClient(mClientToken1, mClientCallback1, false);
verify(mClientLog1).logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
assertThat(registrationInfo.getAvailableLayers()).isEqualTo(DEFAULT_AVAILABLE_LAYERS);
@@ -168,9 +176,20 @@
}
@Test
+ public void testRegister_DeadCallback() throws Exception {
+ doThrow(RemoteException.class).when(mClientBinder1).linkToDeath(any(), anyInt());
+ assertThrows(
+ IllegalStateException.class,
+ () -> registerClient(mClientToken1, mClientCallback1));
+
+ verify(mClientLog1).logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
+ verify(mClientLog1).logConnectionState(VmsClientLogger.ConnectionState.DISCONNECTED);
+ }
+
+ @Test
public void testRegister_LegacyClient() {
VmsRegistrationInfo registrationInfo =
- mBrokerService.registerClient(mClientToken1, mClientCallback1, true);
+ registerClient(mClientToken1, mClientCallback1, true);
verify(mClientLog1).logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
assertThat(registrationInfo.getAvailableLayers()).isEqualTo(DEFAULT_AVAILABLE_LAYERS);
@@ -178,11 +197,22 @@
}
@Test
+ public void testRegister_LegacyClient_DeadCallback() throws Exception {
+ doThrow(RemoteException.class).when(mClientBinder1).linkToDeath(any(), anyInt());
+ assertThrows(
+ IllegalStateException.class,
+ () -> registerClient(mClientToken1, mClientCallback1, true));
+
+ verify(mClientLog1).logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
+ verify(mClientLog1).logConnectionState(VmsClientLogger.ConnectionState.DISCONNECTED);
+ }
+
+ @Test
public void testRegister_TwoClients_OneProcess() {
VmsRegistrationInfo registrationInfo =
- mBrokerService.registerClient(mClientToken1, mClientCallback1, false);
+ registerClient(mClientToken1, mClientCallback1, false);
VmsRegistrationInfo registrationInfo2 =
- mBrokerService.registerClient(mClientToken2, mClientCallback2, false);
+ registerClient(mClientToken2, mClientCallback2, false);
verify(mClientLog1, times(2))
.logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
@@ -192,10 +222,10 @@
@Test
public void testRegister_TwoClients_TwoProcesses() {
VmsRegistrationInfo registrationInfo =
- mBrokerService.registerClient(mClientToken1, mClientCallback1, false);
+ registerClient(mClientToken1, mClientCallback1, false);
mCallingAppUid = TEST_APP_UID2;
VmsRegistrationInfo registrationInfo2 =
- mBrokerService.registerClient(mClientToken2, mClientCallback2, false);
+ registerClient(mClientToken2, mClientCallback2, false);
verify(mClientLog1).logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
verify(mClientLog2).logConnectionState(VmsClientLogger.ConnectionState.CONNECTED);
@@ -214,7 +244,7 @@
));
VmsRegistrationInfo registrationInfo =
- mBrokerService.registerClient(mClientToken2, mClientCallback2, false);
+ registerClient(mClientToken2, mClientCallback2, false);
VmsAvailableLayers expectedLayers = new VmsAvailableLayers(1, asSet(
new VmsAssociatedLayer(LAYER1,
@@ -230,6 +260,15 @@
}
@Test
+ public void testUnregisterClient_UnknownClient() throws Exception {
+ registerClient(mClientToken1, mClientCallback1);
+ mBrokerService.unregisterClient(mClientToken2);
+
+ verify(mClientCallback1, never()).onLayerAvailabilityChanged(any());
+ verify(mClientCallback1, never()).onSubscriptionStateChanged(any());
+ }
+
+ @Test
public void testRegisterProvider_UnknownClient() {
assertThrows(
IllegalStateException.class,
@@ -441,7 +480,7 @@
mBrokerService.setSubscriptions(mClientToken2, asList(
new VmsAssociatedLayer(LAYER2, emptySet())
));
- mBrokerService.unregisterClient(mClientToken2);
+ unregisterClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
asSet(LAYER1),
@@ -461,7 +500,7 @@
mBrokerService.setSubscriptions(mClientToken2, asList(
new VmsAssociatedLayer(LAYER1, emptySet())
));
- mBrokerService.unregisterClient(mClientToken2);
+ unregisterClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
asSet(LAYER1),
@@ -481,7 +520,7 @@
mBrokerService.setSubscriptions(mClientToken2, asList(
new VmsAssociatedLayer(LAYER2, emptySet())
));
- disconnectClient(mClientCallback2);
+ disconnectClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
asSet(LAYER1),
@@ -501,7 +540,7 @@
mBrokerService.setSubscriptions(mClientToken2, asList(
new VmsAssociatedLayer(LAYER1, emptySet())
));
- disconnectClient(mClientCallback2);
+ disconnectClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
asSet(LAYER1),
@@ -756,7 +795,7 @@
mBrokerService.setSubscriptions(mClientToken2, asList(
new VmsAssociatedLayer(LAYER2, asSet(54321))
));
- mBrokerService.unregisterClient(mClientToken2);
+ unregisterClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
emptySet(),
@@ -776,7 +815,7 @@
mBrokerService.setSubscriptions(mClientToken2, asList(
new VmsAssociatedLayer(LAYER1, asSet(54321))
));
- mBrokerService.unregisterClient(mClientToken2);
+ unregisterClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
emptySet(),
@@ -796,7 +835,7 @@
mBrokerService.setSubscriptions(mClientToken2, asList(
new VmsAssociatedLayer(LAYER1, asSet(12345))
));
- mBrokerService.unregisterClient(mClientToken2);
+ unregisterClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
emptySet(),
@@ -816,7 +855,7 @@
mBrokerService.setSubscriptions(mClientToken2, asList(
new VmsAssociatedLayer(LAYER2, asSet(54321))
));
- disconnectClient(mClientCallback2);
+ disconnectClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
emptySet(),
@@ -836,7 +875,7 @@
mBrokerService.setSubscriptions(mClientToken2, asList(
new VmsAssociatedLayer(LAYER1, asSet(54321))
));
- disconnectClient(mClientCallback2);
+ disconnectClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
emptySet(),
@@ -856,7 +895,7 @@
mBrokerService.setSubscriptions(mClientToken2, asList(
new VmsAssociatedLayer(LAYER1, asSet(12345))
));
- disconnectClient(mClientCallback2);
+ disconnectClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(1,
emptySet(),
@@ -1033,7 +1072,7 @@
new VmsAssociatedLayer(LAYER2, emptySet()),
new VmsAssociatedLayer(LAYER1, asSet(54321))
));
- mBrokerService.unregisterClient(mClientToken2);
+ unregisterClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
asSet(LAYER1),
@@ -1055,7 +1094,7 @@
new VmsAssociatedLayer(LAYER2, emptySet()),
new VmsAssociatedLayer(LAYER1, asSet(54321))
));
- disconnectClient(mClientCallback2);
+ disconnectClient(mClientToken2);
VmsSubscriptionState expectedSubscriptions = new VmsSubscriptionState(3,
asSet(LAYER1),
@@ -1222,7 +1261,7 @@
@Test
public void testSetProviderOfferings_UnknownProviderId_LegacyClient() throws Exception {
- mBrokerService.registerClient(mClientToken1, mClientCallback1, true);
+ registerClient(mClientToken1, mClientCallback1, true);
mBrokerService.setProviderOfferings(mClientToken1, 12345, asList(
new VmsLayerDependency(LAYER1)
@@ -1249,7 +1288,7 @@
public void testSetProviderOfferings_OtherClientsProviderId_LegacyClient() throws Exception {
registerClient(mClientToken1, mClientCallback1);
int providerId = mBrokerService.registerProvider(mClientToken1, PROVIDER_INFO1);
- mBrokerService.registerClient(mClientToken2, mClientCallback2, true);
+ registerClient(mClientToken2, mClientCallback2, true);
mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
new VmsLayerDependency(LAYER1)
@@ -1610,7 +1649,7 @@
mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
new VmsLayerDependency(LAYER1)
));
- mBrokerService.unregisterClient(mClientToken1);
+ unregisterClient(mClientToken1);
VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(1, asSet(
new VmsAssociatedLayer(LAYER1, asSet(providerId)))
@@ -1636,7 +1675,7 @@
mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
new VmsLayerDependency(LAYER1)
));
- mBrokerService.unregisterClient(mClientToken1);
+ unregisterClient(mClientToken1);
VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(2, asSet(
new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
@@ -1661,7 +1700,7 @@
mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
new VmsLayerDependency(LAYER1)
));
- mBrokerService.unregisterClient(mClientToken1);
+ unregisterClient(mClientToken1);
VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(2, asSet(
new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
@@ -1688,7 +1727,7 @@
mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
new VmsLayerDependency(LAYER1)
));
- disconnectClient(mClientCallback1);
+ disconnectClient(mClientToken1);
VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(1, asSet(
new VmsAssociatedLayer(LAYER1, asSet(providerId)))
@@ -1712,7 +1751,7 @@
mBrokerService.setProviderOfferings(mClientToken1, providerId, asList(
new VmsLayerDependency(LAYER1)
));
- disconnectClient(mClientCallback1);
+ disconnectClient(mClientToken1);
VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(1, asSet(
new VmsAssociatedLayer(LAYER1, asSet(providerId)))
@@ -1738,7 +1777,7 @@
mBrokerService.setProviderOfferings(mClientToken1, providerId2, asList(
new VmsLayerDependency(LAYER1)
));
- disconnectClient(mClientCallback1);
+ disconnectClient(mClientToken1);
VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(2, asSet(
new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
@@ -1763,7 +1802,7 @@
mBrokerService.setProviderOfferings(mClientToken2, providerId2, asList(
new VmsLayerDependency(LAYER1)
));
- disconnectClient(mClientCallback1);
+ disconnectClient(mClientToken1);
VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(2, asSet(
new VmsAssociatedLayer(LAYER1, asSet(providerId, providerId2)))
@@ -1790,7 +1829,7 @@
mBrokerService.setProviderOfferings(mClientToken2, providerId, asList(
new VmsLayerDependency(LAYER1)
));
- disconnectClient(mClientCallback1);
+ disconnectClient(mClientToken1);
VmsAvailableLayers expectedLayers1 = new VmsAvailableLayers(1, asSet(
new VmsAssociatedLayer(LAYER1, asSet(providerId)))
@@ -2506,7 +2545,7 @@
@Test
public void testPublishPacket_UnknownOffering_LegacyClient() throws Exception {
- mBrokerService.registerClient(mClientToken1, mClientCallback1, true);
+ registerClient(mClientToken1, mClientCallback1, true);
mBrokerService.setSubscriptions(mClientToken1, asList(
new VmsAssociatedLayer(LAYER1, emptySet())
@@ -2840,7 +2879,7 @@
@Test
public void testPublishLargePacket_UnknownOffering_LegacyClient() throws Exception {
setupLargePacket();
- mBrokerService.registerClient(mClientToken1, mClientCallback1, true);
+ registerClient(mClientToken1, mClientCallback1, true);
mBrokerService.setSubscriptions(mClientToken1, asList(
new VmsAssociatedLayer(LAYER1, emptySet())
@@ -3210,14 +3249,46 @@
}
private void registerClient(IBinder token, IVmsClientCallback callback) {
- mBrokerService.registerClient(token, callback, false);
+ registerClient(token, callback, false);
}
- private static void disconnectClient(IVmsClientCallback callback) throws Exception {
- ArgumentCaptor<IBinder.DeathRecipient> deathRecipient =
- ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
- verify(callback.asBinder()).linkToDeath(deathRecipient.capture(), eq(0));
- deathRecipient.getValue().binderDied();
+ private VmsRegistrationInfo registerClient(IBinder token, IVmsClientCallback callback,
+ boolean legacyClient) {
+ VmsRegistrationInfo registrationInfo =
+ mBrokerService.registerClient(token, callback, legacyClient);
+
+ IBinder callbackBinder = callback.asBinder();
+ try {
+ if (!mDeathRecipients.containsKey(token)) {
+ ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
+ ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+ verify(callbackBinder).linkToDeath(deathRecipientCaptor.capture(), eq(0));
+ mDeathRecipients.put(token,
+ Pair.create(callbackBinder, deathRecipientCaptor.getValue()));
+ } else {
+ verify(callbackBinder, never()).linkToDeath(any(), anyInt());
+ }
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+
+ return registrationInfo;
+ }
+
+ private void unregisterClient(IBinder token) {
+ mBrokerService.unregisterClient(token);
+
+ Pair<IBinder, IBinder.DeathRecipient> deathRecipientPair = mDeathRecipients.get(token);
+ assertThat(deathRecipientPair).isNotNull();
+ verify(deathRecipientPair.first).unlinkToDeath(same(deathRecipientPair.second), eq(0));
+ }
+
+ private void disconnectClient(IBinder token) {
+ Pair<IBinder, IBinder.DeathRecipient> deathRecipientPair = mDeathRecipients.get(token);
+ assertThat(deathRecipientPair).isNotNull();
+
+ deathRecipientPair.second.binderDied();
+ verify(deathRecipientPair.first).unlinkToDeath(same(deathRecipientPair.second), eq(0));
}
private static void verifyLayerAvailability(
@@ -3252,6 +3323,7 @@
int providerId, VmsLayer layer, byte[] payload) throws RemoteException {
verify(callback).onPacketReceived(providerId, layer, payload);
}
+
private static void verifyLargePacketReceived(
IVmsClientCallback callback,
int providerId, VmsLayer layer, SharedMemory packet) throws RemoteException {
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java b/tests/carservice_unit_test/src/com/android/car/vms/VmsLayerAvailabilityTest.java
similarity index 78%
rename from tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java
rename to tests/carservice_unit_test/src/com/android/car/vms/VmsLayerAvailabilityTest.java
index ebf50d8..9010802 100644
--- a/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/vms/VmsLayerAvailabilityTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.car;
+package com.android.car.vms;
import android.car.vms.VmsAssociatedLayer;
import android.car.vms.VmsLayer;
@@ -29,7 +29,7 @@
import java.util.Set;
@SmallTest
-public class VmsLayersAvailabilityTest extends AndroidTestCase {
+public class VmsLayerAvailabilityTest extends AndroidTestCase {
private static final VmsLayer LAYER_X = new VmsLayer(1, 1, 2);
private static final VmsLayer LAYER_Y = new VmsLayer(3, 2, 4);
@@ -38,38 +38,38 @@
private static final int PUBLISHER_ID_1 = 19;
private static final int PUBLISHER_ID_2 = 28;
- private static final Set<Integer> PUBLISHERS_1 = new HashSet<>(Arrays.asList(PUBLISHER_ID_1));
- private static final Set<Integer> PUBLISHERS_2 = new HashSet<>(Arrays.asList(PUBLISHER_ID_2));
+ private static final Set<Integer> PUBLISHERS_1 = Collections.singleton(PUBLISHER_ID_1);
+ private static final Set<Integer> PUBLISHERS_2 = Collections.singleton(PUBLISHER_ID_2);
private static final Set<Integer> PUBLISHERS_1_AND_2 =
new HashSet<>(Arrays.asList(PUBLISHER_ID_1, PUBLISHER_ID_2));
private static final VmsLayerDependency X_DEPENDS_ON_Y =
- new VmsLayerDependency(LAYER_X, new HashSet<VmsLayer>(Arrays.asList(LAYER_Y)));
+ new VmsLayerDependency(LAYER_X, Collections.singleton(LAYER_Y));
private static final VmsLayerDependency X_DEPENDS_ON_Z =
- new VmsLayerDependency(LAYER_X, new HashSet<VmsLayer>(Arrays.asList(LAYER_Z)));
+ new VmsLayerDependency(LAYER_X, Collections.singleton(LAYER_Z));
private static final VmsLayerDependency Y_DEPENDS_ON_Z =
- new VmsLayerDependency(LAYER_Y, new HashSet<VmsLayer>(Arrays.asList(LAYER_Z)));
+ new VmsLayerDependency(LAYER_Y, Collections.singleton(LAYER_Z));
private static final VmsLayerDependency Y_DEPENDS_ON_X =
- new VmsLayerDependency(LAYER_Y, new HashSet<VmsLayer>(Arrays.asList(LAYER_X)));
+ new VmsLayerDependency(LAYER_Y, Collections.singleton(LAYER_X));
private static final VmsLayerDependency Z_DEPENDS_ON_X =
- new VmsLayerDependency(LAYER_Z, new HashSet<VmsLayer>(Arrays.asList(LAYER_X)));
+ new VmsLayerDependency(LAYER_Z, Collections.singleton(LAYER_X));
private static final VmsLayerDependency Z_DEPENDS_ON_NOTHING =
new VmsLayerDependency(LAYER_Z);
private static final VmsLayerDependency X_DEPENDS_ON_SELF =
- new VmsLayerDependency(LAYER_X, new HashSet<VmsLayer>(Arrays.asList(LAYER_X)));
+ new VmsLayerDependency(LAYER_X, Collections.singleton(LAYER_X));
private Set<VmsLayersOffering> mOfferings;
- private VmsLayersAvailability mLayersAvailability;
+ private VmsLayerAvailability mLayersAvailability;
@Override
protected void setUp() throws Exception {
- mLayersAvailability = new VmsLayersAvailability();
+ mLayersAvailability = new VmsLayerAvailability();
mOfferings = new HashSet<>();
super.setUp();
}
@@ -79,16 +79,16 @@
}
public void testEmptyOffering() {
- mLayersAvailability.setPublishersOffering(Collections.EMPTY_LIST);
+ mLayersAvailability.setPublishersOffering(Collections.emptyList());
assertTrue(mLayersAvailability.getAvailableLayers().getAssociatedLayers().isEmpty());
}
- public void testSingleLayerNoDeps() throws Exception {
+ public void testSingleLayerNoDeps() {
Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_2));
VmsLayersOffering offering =
- new VmsLayersOffering(new HashSet<>(Arrays.asList(new VmsLayerDependency(LAYER_X))),
+ new VmsLayersOffering(Collections.singleton(new VmsLayerDependency(LAYER_X)),
PUBLISHER_ID_2);
mOfferings.add(offering);
@@ -98,7 +98,7 @@
mLayersAvailability.getAvailableLayers().getAssociatedLayers());
}
- public void testChainOfDependenciesSatisfied() throws Exception {
+ public void testChainOfDependenciesSatisfied() {
Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Y, PUBLISHERS_1));
@@ -114,11 +114,11 @@
mLayersAvailability.setPublishersOffering(mOfferings);
assertEquals(expectedAvailableAssociatedLayers,
- new HashSet<VmsAssociatedLayer>(
+ new HashSet<>(
mLayersAvailability.getAvailableLayers().getAssociatedLayers()));
}
- public void testChainOfDependenciesSatisfiedTwoOfferings() throws Exception {
+ public void testChainOfDependenciesSatisfiedTwoOfferings() {
Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Y, PUBLISHERS_1));
@@ -130,7 +130,7 @@
PUBLISHER_ID_1);
VmsLayersOffering offering2 =
- new VmsLayersOffering(new HashSet<>(Arrays.asList(Z_DEPENDS_ON_NOTHING)),
+ new VmsLayersOffering(Collections.singleton(Z_DEPENDS_ON_NOTHING),
PUBLISHER_ID_1);
mOfferings.add(offering1);
@@ -138,11 +138,11 @@
mLayersAvailability.setPublishersOffering(mOfferings);
assertEquals(expectedAvailableAssociatedLayers,
- new HashSet<VmsAssociatedLayer>(
+ new HashSet<>(
mLayersAvailability.getAvailableLayers().getAssociatedLayers()));
}
- public void testChainOfDependencieNotSatisfied() throws Exception {
+ public void testChainOfDependenciesNotSatisfied() {
Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
VmsLayersOffering offering =
new VmsLayersOffering(new HashSet<>(Arrays.asList(X_DEPENDS_ON_Y, Y_DEPENDS_ON_Z)),
@@ -152,11 +152,11 @@
mLayersAvailability.setPublishersOffering(mOfferings);
assertEquals(expectedAvailableAssociatedLayers,
- new HashSet<VmsAssociatedLayer>(
+ new HashSet<>(
mLayersAvailability.getAvailableLayers().getAssociatedLayers()));
}
- public void testOneOfMultipleDependencySatisfied() throws Exception {
+ public void testOneOfMultipleDependencySatisfied() {
Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Z, PUBLISHERS_1));
@@ -172,11 +172,11 @@
mLayersAvailability.setPublishersOffering(mOfferings);
assertEquals(expectedAvailableAssociatedLayers,
- new HashSet<VmsAssociatedLayer>(
+ new HashSet<>(
mLayersAvailability.getAvailableLayers().getAssociatedLayers()));
}
- public void testCyclicDependency() throws Exception {
+ public void testCyclicDependency() {
Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
VmsLayersOffering offering =
@@ -189,11 +189,11 @@
mLayersAvailability.setPublishersOffering(mOfferings);
assertEquals(expectedAvailableAssociatedLayers,
- new HashSet<VmsAssociatedLayer>(
+ new HashSet<>(
mLayersAvailability.getAvailableLayers().getAssociatedLayers()));
}
- public void testAlmostCyclicDependency() throws Exception {
+ public void testAlmostCyclicDependency() {
Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Z, PUBLISHERS_1_AND_2));
expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
@@ -216,7 +216,7 @@
mLayersAvailability.getAvailableLayers().getAssociatedLayers());
}
- public void testCyclicDependencyAndLayerWithoutDependency() throws Exception {
+ public void testCyclicDependencyAndLayerWithoutDependency() {
Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Z, PUBLISHERS_1));
@@ -227,29 +227,29 @@
PUBLISHER_ID_1);
VmsLayersOffering offering2 =
- new VmsLayersOffering(new HashSet<>(Arrays.asList(Y_DEPENDS_ON_X)), PUBLISHER_ID_2);
+ new VmsLayersOffering(Collections.singleton(Y_DEPENDS_ON_X), PUBLISHER_ID_2);
mOfferings.add(offering1);
mOfferings.add(offering2);
mLayersAvailability.setPublishersOffering(mOfferings);
assertEquals(expectedAvailableAssociatedLayers,
- new HashSet<VmsAssociatedLayer>(
+ new HashSet<>(
mLayersAvailability.getAvailableLayers().getAssociatedLayers()));
}
- public void testSelfDependency() throws Exception {
+ public void testSelfDependency() {
Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
VmsLayersOffering offering =
- new VmsLayersOffering(new HashSet<>(Arrays.asList(X_DEPENDS_ON_SELF)),
+ new VmsLayersOffering(Collections.singleton(X_DEPENDS_ON_SELF),
PUBLISHER_ID_1);
mOfferings.add(offering);
mLayersAvailability.setPublishersOffering(mOfferings);
assertEquals(expectedAvailableAssociatedLayers,
- new HashSet<VmsAssociatedLayer>(
+ new HashSet<>(
mLayersAvailability.getAvailableLayers().getAssociatedLayers()));
}
-}
\ No newline at end of file
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/vms/VmsProviderInfoStoreTest.java b/tests/carservice_unit_test/src/com/android/car/vms/VmsProviderInfoStoreTest.java
new file mode 100644
index 0000000..ae28fd1
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/vms/VmsProviderInfoStoreTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.vms;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class VmsProviderInfoStoreTest {
+ private static final byte[] MOCK_INFO_1 = new byte[]{2, 3, 5, 7, 11, 13, 17};
+ private static final byte[] SAME_MOCK_INFO_1 = new byte[]{2, 3, 5, 7, 11, 13, 17};
+ private static final byte[] MOCK_INFO_2 = new byte[]{2, 3, 5, 7, 11, 13, 17, 19};
+
+ private VmsProviderInfoStore mProviderInfoStore;
+
+ @Before
+ public void setUp() throws Exception {
+ mProviderInfoStore = new VmsProviderInfoStore();
+ }
+
+ @Test
+ public void testSingleInfo() {
+ int id = mProviderInfoStore.getProviderId(MOCK_INFO_1);
+ assertEquals(1, id);
+ assertArrayEquals(MOCK_INFO_1, mProviderInfoStore.getProviderInfo(id));
+ }
+
+ @Test
+ public void testSingleInfo_NoSuchId() {
+ assertNull(mProviderInfoStore.getProviderInfo(12345));
+ }
+
+ @Test
+ public void testTwoInfos() {
+ int id1 = mProviderInfoStore.getProviderId(MOCK_INFO_1);
+ int id2 = mProviderInfoStore.getProviderId(MOCK_INFO_2);
+ assertEquals(1, id1);
+ assertEquals(2, id2);
+ assertArrayEquals(MOCK_INFO_1, mProviderInfoStore.getProviderInfo(id1));
+ assertArrayEquals(MOCK_INFO_2, mProviderInfoStore.getProviderInfo(id2));
+ }
+
+ @Test
+ public void testSingleInfoInsertedTwice() {
+ int id = mProviderInfoStore.getProviderId(MOCK_INFO_1);
+ assertEquals(1, id);
+
+ int sameId = mProviderInfoStore.getProviderId(SAME_MOCK_INFO_1);
+ assertEquals(sameId, id);
+ }
+}
diff --git a/user/car-user-lib/src/android/car/userlib/CommonConstants.java b/user/car-user-lib/src/android/car/userlib/CommonConstants.java
index ef9a354..89c09c5 100644
--- a/user/car-user-lib/src/android/car/userlib/CommonConstants.java
+++ b/user/car-user-lib/src/android/car/userlib/CommonConstants.java
@@ -33,6 +33,7 @@
public static final String BUNDLE_USER_ID = "user.id";
public static final String BUNDLE_USER_FLAGS = "user.flags";
public static final String BUNDLE_USER_NAME = "user.name";
+ public static final String BUNDLE_USER_LOCALES = "user.locales";
public static final String BUNDLE_INITIAL_INFO_ACTION = "initial_info.action";
private CarUserServiceConstants() {
diff --git a/user/car-user-lib/src/android/car/userlib/InitialUserSetter.java b/user/car-user-lib/src/android/car/userlib/InitialUserSetter.java
index d3e5d52..10b642b 100644
--- a/user/car-user-lib/src/android/car/userlib/InitialUserSetter.java
+++ b/user/car-user-lib/src/android/car/userlib/InitialUserSetter.java
@@ -18,6 +18,7 @@
import static android.car.userlib.UserHalHelper.userFlagsToString;
import static android.car.userlib.UserHelper.safeName;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -30,6 +31,8 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
+import android.sysprop.CarProperties;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -40,6 +43,8 @@
import com.android.internal.widget.LockPatternUtils;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.function.Consumer;
/**
@@ -49,8 +54,50 @@
private static final String TAG = InitialUserSetter.class.getSimpleName();
- // TODO(b/151758646): STOPSHIP if not false
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
+
+ /**
+ * Sets the initial user using the default behavior.
+ *
+ * <p>The default behavior is:
+ *
+ * <ol>
+ * <li>On first boot, it creates and switches to a new user.
+ * <li>Otherwise, it will switch to either:
+ * <ol>
+ * <li>User defined by {@code android.car.systemuser.bootuseroverrideid} (when it was
+ * constructed with such option enabled).
+ * <li>Last active user (as defined by
+ * {@link android.provider.Settings.Global.LAST_ACTIVE_USER_ID}.
+ * </ol>
+ * </ol>
+ */
+ public static final int TYPE_DEFAULT_BEHAVIOR = 0;
+
+ /**
+ * Switches to the given user, falling back to {@link #fallbackDefaultBehavior(String)} if it
+ * fails.
+ */
+ public static final int TYPE_SWITCH = 1;
+
+ /**
+ * Creates a new user and switches to it, falling back to
+ * {@link #fallbackDefaultBehavior(String) if any of these steps fails.
+ *
+ * @param name (optional) name of the new user
+ * @param halFlags user flags as defined by Vehicle HAL ({@code UserFlags} enum).
+ */
+ public static final int TYPE_CREATE = 2;
+
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_DEFAULT_BEHAVIOR,
+ TYPE_SWITCH,
+ TYPE_CREATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InitialUserInfoType { }
+
+ private final Context mContext;
// TODO(b/150413304): abstract AM / UM into interfaces, then provide local and remote
// implementation (where local is implemented by ActivityManagerInternal / UserManagerInternal)
@@ -58,74 +105,218 @@
private final UserManager mUm;
private final LockPatternUtils mLockPatternUtils;
- // TODO(b/151758646): make sure it's unit tested
- private final boolean mSupportsOverrideUserIdProperty;
-
private final String mNewUserName;
private final String mNewGuestName;
private final Consumer<UserInfo> mListener;
- public InitialUserSetter(@NonNull Context context, @NonNull Consumer<UserInfo> listener,
- boolean supportsOverrideUserIdProperty) {
- this(context, listener, /* newGuestName= */ null, supportsOverrideUserIdProperty);
+ public InitialUserSetter(@NonNull Context context, @NonNull Consumer<UserInfo> listener) {
+ this(context, listener, /* newGuestName= */ null);
}
public InitialUserSetter(@NonNull Context context, @NonNull Consumer<UserInfo> listener,
- @Nullable String newGuestName, boolean supportsOverrideUserIdProperty) {
- this(new CarUserManagerHelper(context), UserManager.get(context), listener,
+ @Nullable String newGuestName) {
+ this(context, new CarUserManagerHelper(context), UserManager.get(context), listener,
new LockPatternUtils(context),
- context.getString(com.android.internal.R.string.owner_name), newGuestName,
- supportsOverrideUserIdProperty);
+ context.getString(com.android.internal.R.string.owner_name), newGuestName);
}
@VisibleForTesting
- public InitialUserSetter(@NonNull CarUserManagerHelper helper, @NonNull UserManager um,
- @NonNull Consumer<UserInfo> listener, @NonNull LockPatternUtils lockPatternUtils,
- @Nullable String newUserName, @Nullable String newGuestName,
- boolean supportsOverrideUserIdProperty) {
+ public InitialUserSetter(@NonNull Context context, @NonNull CarUserManagerHelper helper,
+ @NonNull UserManager um, @NonNull Consumer<UserInfo> listener,
+ @NonNull LockPatternUtils lockPatternUtils,
+ @Nullable String newUserName, @Nullable String newGuestName) {
+ mContext = context;
mHelper = helper;
mUm = um;
mListener = listener;
mLockPatternUtils = lockPatternUtils;
mNewUserName = newUserName;
mNewGuestName = newGuestName;
- mSupportsOverrideUserIdProperty = supportsOverrideUserIdProperty;
}
/**
- * Sets the initial user using the default behavior.
+ * Builder for {@link InitialUserInfo} objects.
*
- * <p>The default behavior is:
- * <ol>
- * <li>On first boot, it creates and switches to a new user.
- * <li>Otherwise, it will switch to either:
- * <ol>
- * <li>User defined by {@code android.car.systemuser.bootuseroverrideid} (when it was
- * constructed with such option enabled).
- * <li>Last active user (as defined by
- * {@link android.provider.Settings..Global.LAST_ACTIVE_USER_ID}.
- * </ol>
- * </ol>
*/
- public void executeDefaultBehavior(boolean replaceGuest) {
- executeDefaultBehavior(replaceGuest, /* fallback= */ false);
- }
+ public static final class Builder {
- private void executeDefaultBehavior(boolean replaceGuest, boolean fallback) {
- if (!mHelper.hasInitialUser()) {
- if (DBG) Log.d(TAG, "executeDefaultBehavior(): no initial user, creating it");
- createAndSwitchUser(mNewUserName, UserFlags.ADMIN, fallback);
- } else {
- if (DBG) Log.d(TAG, "executeDefaultBehavior(): switching to initial user");
- int userId = mHelper.getInitialUser(mSupportsOverrideUserIdProperty);
- switchUser(userId, replaceGuest, fallback);
+ private final @InitialUserInfoType int mType;
+ private boolean mReplaceGuest;
+ private @UserIdInt int mSwitchUserId;
+ private @Nullable String mNewUserName;
+ private int mNewUserFlags;
+ private boolean mSupportsOverrideUserIdProperty;
+ private @Nullable String mUserLocales;
+
+ /**
+ * Constructor for the given type.
+ *
+ * @param type {@link #TYPE_DEFAULT_BEHAVIOR}, {@link #TYPE_SWITCH},
+ * or {@link #TYPE_CREATE}.
+ */
+ public Builder(@InitialUserInfoType int type) {
+ Preconditions.checkArgument(
+ type == TYPE_DEFAULT_BEHAVIOR || type == TYPE_SWITCH || type == TYPE_CREATE,
+ "invalid builder type");
+ mType = type;
+ }
+
+ /**
+ * Sets the id of the user to be switched to.
+ *
+ * @throws IllegalArgumentException if builder is not for {@link #TYPE_SWITCH}.
+ */
+ @NonNull
+ public Builder setSwitchUserId(@UserIdInt int userId) {
+ Preconditions.checkArgument(mType == TYPE_SWITCH, "invalid builder type: " + mType);
+ mSwitchUserId = userId;
+ return this;
+ }
+
+ /**
+ * Sets whether the current user should be replaced when it's a guest.
+ */
+ @NonNull
+ public Builder setReplaceGuest(boolean value) {
+ mReplaceGuest = value;
+ return this;
+ }
+
+ /**
+ * Sets the name of the new user being created.
+ *
+ * @throws IllegalArgumentException if builder is not for {@link #TYPE_CREATE}.
+ */
+ @NonNull
+ public Builder setNewUserName(@Nullable String name) {
+ Preconditions.checkArgument(mType == TYPE_CREATE, "invalid builder type: " + mType);
+ mNewUserName = name;
+ return this;
+ }
+
+ /**
+ * Sets the flags (as defined by {@link android.hardware.automotive.vehicle.V2_0.UserFlags})
+ * of the new user being created.
+ *
+ * @throws IllegalArgumentException if builder is not for {@link #TYPE_CREATE}.
+ */
+ @NonNull
+ public Builder setNewUserFlags(int flags) {
+ Preconditions.checkArgument(mType == TYPE_CREATE, "invalid builder type: " + mType);
+ mNewUserFlags = flags;
+ return this;
+ }
+
+ /**
+ * Sets whether the {@link CarProperties#boot_user_override_id()} should be taking in
+ * account when using the default behavior.
+ */
+ @NonNull
+ public Builder setSupportsOverrideUserIdProperty(boolean value) {
+ mSupportsOverrideUserIdProperty = value;
+ return this;
+ }
+
+ /**
+ * Sets the system locales for the initial user (when it's created).
+ */
+ @NonNull
+ public Builder setUserLocales(@Nullable String userLocales) {
+ mUserLocales = userLocales;
+ return this;
+ }
+
+ /**
+ * Builds the object.
+ */
+ @NonNull
+ public InitialUserInfo build() {
+ return new InitialUserInfo(this);
}
}
+ /**
+ * Object used to define the properties of the initial user (which can then be set by
+ * {@link InitialUserSetter#set(InitialUserInfo)});
+ */
+ public static final class InitialUserInfo {
+ public final @InitialUserInfoType int type;
+ public final boolean replaceGuest;
+ public final @UserIdInt int switchUserId;
+ public final @Nullable String newUserName;
+ public final int newUserFlags;
+ public final boolean supportsOverrideUserIdProperty;
+ public @Nullable String userLocales;
+
+ private InitialUserInfo(@NonNull Builder builder) {
+ type = builder.mType;
+ switchUserId = builder.mSwitchUserId;
+ replaceGuest = builder.mReplaceGuest;
+ newUserName = builder.mNewUserName;
+ newUserFlags = builder.mNewUserFlags;
+ supportsOverrideUserIdProperty = builder.mSupportsOverrideUserIdProperty;
+ userLocales = builder.mUserLocales;
+ }
+ }
+
+ /**
+ * Sets the initial user.
+ */
+ public void set(@NonNull InitialUserInfo info) {
+ Preconditions.checkArgument(info != null, "info cannot be null");
+
+ switch (info.type) {
+ case TYPE_DEFAULT_BEHAVIOR:
+ executeDefaultBehavior(info, /* fallback= */ false);
+ break;
+ case TYPE_SWITCH:
+ try {
+ switchUser(info, /* fallback= */ true);
+ } catch (Exception e) {
+ fallbackDefaultBehavior(info, /* fallback= */ true,
+ "Exception switching user: " + e);
+ }
+ break;
+ case TYPE_CREATE:
+ try {
+ createAndSwitchUser(info, /* fallback= */ true);
+ } catch (Exception e) {
+ fallbackDefaultBehavior(info, /* fallback= */ true,
+ "Exception createUser user with name "
+ + UserHelper.safeName(info.newUserName) + " and flags "
+ + UserHalHelper.userFlagsToString(info.newUserFlags) + ": "
+ + e);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("invalid InitialUserInfo type: " + info.type);
+ }
+ }
+
+ private void executeDefaultBehavior(@NonNull InitialUserInfo info, boolean fallback) {
+ if (!mHelper.hasInitialUser()) {
+ if (DBG) Log.d(TAG, "executeDefaultBehavior(): no initial user, creating it");
+ createAndSwitchUser(new Builder(TYPE_CREATE)
+ .setNewUserName(mNewUserName)
+ .setNewUserFlags(UserFlags.ADMIN)
+ .setSupportsOverrideUserIdProperty(info.supportsOverrideUserIdProperty)
+ .setUserLocales(info.userLocales)
+ .build(), fallback);
+ } else {
+ if (DBG) Log.d(TAG, "executeDefaultBehavior(): switching to initial user");
+ int userId = mHelper.getInitialUser(info.supportsOverrideUserIdProperty);
+ switchUser(new Builder(TYPE_SWITCH)
+ .setSwitchUserId(userId)
+ .setSupportsOverrideUserIdProperty(info.supportsOverrideUserIdProperty)
+ .setReplaceGuest(info.replaceGuest)
+ .build(), fallback);
+ }
+ }
@VisibleForTesting
- void fallbackDefaultBehavior(boolean fallback, @NonNull String reason) {
+ void fallbackDefaultBehavior(@NonNull InitialUserInfo info, boolean fallback,
+ @NonNull String reason) {
if (!fallback) {
// Only log the error
Log.w(TAG, reason);
@@ -134,22 +325,13 @@
return;
}
Log.w(TAG, "Falling back to default behavior. Reason: " + reason);
- executeDefaultBehavior(/* replaceGuest= */ true, /* fallback= */ false);
+ executeDefaultBehavior(info, /* fallback= */ false);
}
- /**
- * Switches to the given user, falling back to {@link #fallbackDefaultBehavior(String)} if it
- * fails.
- */
- public void switchUser(@UserIdInt int userId, boolean replaceGuest) {
- try {
- switchUser(userId, replaceGuest, /* fallback= */ true);
- } catch (Exception e) {
- fallbackDefaultBehavior(/* fallback= */ true, "Exception switching user: " + e);
- }
- }
+ private void switchUser(@NonNull InitialUserInfo info, boolean fallback) {
+ int userId = info.switchUserId;
+ boolean replaceGuest = info.replaceGuest;
- private void switchUser(@UserIdInt int userId, boolean replaceGuest, boolean fallback) {
if (DBG) {
Log.d(TAG, "switchUser(): userId=" + userId + ", replaceGuest=" + replaceGuest
+ ", fallback=" + fallback);
@@ -157,7 +339,7 @@
UserInfo user = mUm.getUserInfo(userId);
if (user == null) {
- fallbackDefaultBehavior(fallback, "user with id " + userId + " doesn't exist");
+ fallbackDefaultBehavior(info, fallback, "user with id " + userId + " doesn't exist");
return;
}
@@ -167,7 +349,8 @@
actualUser = replaceGuestIfNeeded(user);
if (actualUser == null) {
- fallbackDefaultBehavior(fallback, "could not replace guest " + user.toFullString());
+ fallbackDefaultBehavior(info, fallback, "could not replace guest "
+ + user.toFullString());
return;
}
}
@@ -179,7 +362,8 @@
int currentUserId = ActivityManager.getCurrentUser();
if (actualUserId != currentUserId) {
if (!startForegroundUser(actualUserId)) {
- fallbackDefaultBehavior(fallback, "am.switchUser(" + actualUserId + ") failed");
+ fallbackDefaultBehavior(info, fallback,
+ "am.switchUser(" + actualUserId + ") failed");
return;
}
mHelper.setLastActiveUser(actualUserId);
@@ -242,7 +426,10 @@
Log.w(TAG, "failed to mark guest " + user.id + " for deletion");
}
- Pair<UserInfo, String> result = createNewUser(mNewGuestName, halFlags);
+ Pair<UserInfo, String> result = createNewUser(new Builder(TYPE_CREATE)
+ .setNewUserName(mNewGuestName)
+ .setNewUserFlags(halFlags)
+ .build());
String errorMessage = result.second;
if (errorMessage != null) {
@@ -253,31 +440,18 @@
return result.first;
}
- /**
- * Creates a new user and switches to it, falling back to
- * {@link #fallbackDefaultBehavior(String) if any of these steps fails.
- *
- * @param name (optional) name of the new user
- * @param halFlags user flags as defined by Vehicle HAL ({@code UserFlags} enum).
- */
- public void createUser(@Nullable String name, int halFlags) {
- try {
- createAndSwitchUser(name, halFlags, /* fallback= */ true);
- } catch (Exception e) {
- fallbackDefaultBehavior(/* fallback= */ true, "Exception createUser user with flags "
- + UserHalHelper.userFlagsToString(halFlags) + ": " + e);
- }
- }
-
- private void createAndSwitchUser(@Nullable String name, int halFlags, boolean fallback) {
- Pair<UserInfo, String> result = createNewUser(name, halFlags);
+ private void createAndSwitchUser(@NonNull InitialUserInfo info, boolean fallback) {
+ Pair<UserInfo, String> result = createNewUser(info);
String reason = result.second;
if (reason != null) {
- fallbackDefaultBehavior(fallback, reason);
+ fallbackDefaultBehavior(info, fallback, reason);
return;
}
- switchUser(result.first.id, /* replaceGuest= */ false, fallback);
+ switchUser(new Builder(TYPE_SWITCH)
+ .setSwitchUserId(result.first.id)
+ .setSupportsOverrideUserIdProperty(info.supportsOverrideUserIdProperty)
+ .build(), fallback);
}
/**
@@ -287,7 +461,10 @@
* error message.
*/
@NonNull
- private Pair<UserInfo, String> createNewUser(@Nullable String name, int halFlags) {
+ private Pair<UserInfo, String> createNewUser(@NonNull InitialUserInfo info) {
+ String name = info.newUserName;
+ int halFlags = info.newUserFlags;
+
if (DBG) {
Log.d(TAG, "createUser(name=" + safeName(name) + ", flags="
+ userFlagsToString(halFlags) + ")");
@@ -330,6 +507,15 @@
}
if (DBG) Log.d(TAG, "user created: " + userInfo.id);
+
+ if (info.userLocales != null) {
+ if (DBG) {
+ Log.d(TAG, "setting locale for user " + userInfo.id + " to " + info.userLocales);
+ }
+ Settings.System.putStringForUser(mContext.getContentResolver(),
+ Settings.System.SYSTEM_LOCALES, info.userLocales, userInfo.id);
+ }
+
return new Pair<>(userInfo, null);
}
@@ -390,8 +576,6 @@
public void dump(@NonNull PrintWriter writer) {
writer.println("InitialUserSetter");
String indent = " ";
- writer.printf("%smSupportsOverrideUserIdProperty: %s\n", indent,
- mSupportsOverrideUserIdProperty);
writer.printf("%smNewUserName: %s\n", indent, mNewUserName);
writer.printf("%smNewGuestName: %s\n", indent, mNewGuestName);
}
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 109a55f..06e449d 100644
--- a/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
@@ -22,6 +22,8 @@
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
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.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue;
@@ -35,8 +37,11 @@
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.DebugUtils;
+import android.util.Log;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -44,8 +49,14 @@
*/
public final class UserHalHelper {
+ private static final String TAG = UserHalHelper.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ public static final int INITIAL_USER_INFO_PROPERTY = 299896583;
public static final int USER_IDENTIFICATION_ASSOCIATION_PROPERTY = 299896587;
+ private static final String STRING_SEPARATOR = "\\|\\|";
+
/**
* Gets user-friendly representation of the status.
*/
@@ -268,6 +279,8 @@
/**
* Creates a {@link UserIdentificationResponse} from a generic {@link VehiclePropValue} sent by
* HAL.
+ *
+ * @throws IllegalArgumentException if the HAL property doesn't have the proper format.
*/
@NonNull
public static UserIdentificationResponse toUserIdentificationResponse(
@@ -276,7 +289,7 @@
checkArgument(prop.prop == USER_IDENTIFICATION_ASSOCIATION_PROPERTY,
"invalid prop on %s", prop);
// need at least 4: request_id, number associations, type1, value1
- checkArgument(prop.value.int32Values.size() >= 4, "not enough int32Values on %s", prop);
+ assertMinimumSize(prop, 4);
int requestId = prop.value.int32Values.get(0);
checkArgument(requestId > 0, "invalid request id (%d) on %s", requestId, prop);
@@ -312,8 +325,73 @@
}
/**
+ * Creates a {@link InitialUserInfoResponse} from a generic {@link VehiclePropValue} sent by
+ * HAL.
+ *
+ * @throws IllegalArgumentException if the HAL property doesn't have the proper format.
+ */
+ @NonNull
+ public static InitialUserInfoResponse toInitialUserInfoResponse(
+ @NonNull VehiclePropValue prop) {
+ if (DEBUG) Log.d(TAG, "toInitialUserInfoResponse(): " + prop);
+ Objects.requireNonNull(prop, "prop cannot be null");
+ checkArgument(prop.prop == INITIAL_USER_INFO_PROPERTY, "invalid prop on %s", prop);
+
+ // need at least 2: request_id, action_type
+ assertMinimumSize(prop, 2);
+
+ int requestId = prop.value.int32Values.get(0);
+ checkArgument(requestId > 0, "invalid request id (%d) on %s", requestId, prop);
+
+ InitialUserInfoResponse response = new InitialUserInfoResponse();
+ response.requestId = requestId;
+ response.action = prop.value.int32Values.get(1);
+
+ String[] stringValues = null;
+ if (!TextUtils.isEmpty(prop.value.stringValue)) {
+ stringValues = TextUtils.split(prop.value.stringValue, STRING_SEPARATOR);
+ if (DEBUG) {
+ Log.d(TAG, "toInitialUserInfoResponse(): values=" + Arrays.toString(stringValues)
+ + " length: " + stringValues.length);
+ }
+ }
+ if (stringValues != null && stringValues.length > 0) {
+ response.userLocales = stringValues[0];
+ }
+
+ switch (response.action) {
+ case InitialUserInfoResponseAction.DEFAULT:
+ response.userToSwitchOrCreate.userId = UserHandle.USER_NULL;
+ response.userToSwitchOrCreate.flags = UserFlags.NONE;
+ break;
+ case InitialUserInfoResponseAction.SWITCH:
+ assertMinimumSize(prop, 3);
+ response.userToSwitchOrCreate.userId = prop.value.int32Values.get(2);
+ response.userToSwitchOrCreate.flags = UserFlags.NONE;
+ break;
+ case InitialUserInfoResponseAction.CREATE:
+ assertMinimumSize(prop, 3);
+ response.userToSwitchOrCreate.userId = UserHandle.USER_NULL;
+ response.userToSwitchOrCreate.flags = prop.value.int32Values.get(2);
+ if (stringValues.length > 1) {
+ response.userNameToCreate = stringValues[1];
+ }
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid response action (" + response.action + " on " + prop);
+ }
+
+ if (DEBUG) Log.d(TAG, "returning : " + response);
+
+ return response;
+ }
+
+ /**
* Creates a generic {@link VehiclePropValue} (that can be sent to HAL) from a
* {@link UserIdentificationGetRequest}.
+ *
+ * @throws IllegalArgumentException if the request doesn't have the proper format.
*/
@NonNull
public static VehiclePropValue toVehiclePropValue(
@@ -343,6 +421,8 @@
/**
* Creates a generic {@link VehiclePropValue} (that can be sent to HAL) from a
* {@link UserIdentificationSetRequest}.
+ *
+ * @throws IllegalArgumentException if the request doesn't have the proper format.
*/
@NonNull
public static VehiclePropValue toVehiclePropValue(
@@ -372,6 +452,11 @@
return propValue;
}
+ private static void assertMinimumSize(@NonNull VehiclePropValue prop, int minSize) {
+ checkArgument(prop.value.int32Values.size() >= minSize,
+ "not enough int32Values (minimum is %d) on %s", minSize, prop);
+ }
+
private UserHalHelper() {
throw new UnsupportedOperationException("contains only static methods");
}
diff --git a/watchdog/server/src/WatchdogProcessService.cpp b/watchdog/server/src/WatchdogProcessService.cpp
index 749ed30..8d4ec06 100644
--- a/watchdog/server/src/WatchdogProcessService.cpp
+++ b/watchdog/server/src/WatchdogProcessService.cpp
@@ -15,7 +15,7 @@
*/
#define LOG_TAG "carwatchdogd"
-#define DEBUG true // TODO(b/151474489): stop ship if true.
+#define DEBUG false // STOPSHIP if true.
#include "WatchdogProcessService.h"
diff --git a/watchdog/server/tests/LooperStub.cpp b/watchdog/server/tests/LooperStub.cpp
index 1b7aa82..bd3311b 100644
--- a/watchdog/server/tests/LooperStub.cpp
+++ b/watchdog/server/tests/LooperStub.cpp
@@ -31,8 +31,15 @@
using android::base::Error;
using android::base::Result;
-const std::chrono::milliseconds kLooperPollTimeout = 10ms;
-const std::chrono::milliseconds kStubPollCheckTimeout = 200ms;
+// As the messages, which are to be polled immediately, are enqueued in the underlying looper
+// handler before calling its poll method, the looper handler doesn't have to wait for any new
+// messages.
+const std::chrono::milliseconds kLooperPollTimeout = 0ms;
+
+// Maximum timeout before giving up on the underlying looper handler. This doesn't block the test
+// as long as the underlying looper handler processes the enqueued messages quickly and updates
+// |mShouldPoll|.
+const std::chrono::milliseconds kStubPollCheckTimeout = 3min;
int LooperStub::pollAll(int /*timeoutMillis*/) {
{