Merge "Limit preferSource package to Maps." into rvc-dev
diff --git a/FrameworkPackageStubs/AndroidManifest.xml b/FrameworkPackageStubs/AndroidManifest.xml
index 912944c..6c25bcd 100644
--- a/FrameworkPackageStubs/AndroidManifest.xml
+++ b/FrameworkPackageStubs/AndroidManifest.xml
@@ -95,6 +95,10 @@
<action android:name="android.settings.USER_DICTIONARY_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
+ <intent-filter android:priority="-1">
+ <action android:name="android.settings.PICTURE_IN_PICTURE_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
</activity>
<!-- CDD Core Application Intents Stubs -->
diff --git a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
index 5fec33f..46af12b 100644
--- a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
+++ b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
@@ -78,6 +78,14 @@
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)
+150115 car_user_svc_create_user_req (safe_name|3),(user_type|3),(flags|1),(timeout|1)
+150116 car_user_svc_create_user_resp (status|1),(result|1),(error_message|3)
+150117 car_user_svc_create_user_user_created (user_id|1),(safe_name|3),(user_type|3),(flags|1)
+150118 car_user_svc_create_user_user_removed (user_id|1),(reason|3)
+150119 car_user_svc_remove_user_req (user_id|1)
+150120 car_user_svc_remove_user_resp (user_id|1),(result|1)
+150121 car_user_svc_notify_app_lifecycle_listener (uid|1),(event_type|1),(from_user_id|1),(to_user_id|1)
+150122 car_user_svc_notify_internal_lifecycle_listener (listener_name|3),(event_type|1),(from_user_id|1),(to_user_id|1)
150140 car_user_hal_initial_user_info_req (request_id|1),(request_type|1),(timeout|1)
150141 car_user_hal_initial_user_info_resp (request_id|1),(status|1),(action|1),(user_id|1),(flags|1),(safe_name|3),(user_locales|3)
@@ -92,13 +100,19 @@
150150 car_user_hal_oem_switch_user_req (request_id|1),(target_user_id|1)
150151 car_user_hal_create_user_req (request_id|1),(safe_name|3),(flags|1),(timeout|1)
150152 car_user_hal_create_user_resp (request_id|1),(status|1),(result|1),(error_message|3)
+150153 car_user_hal_remove_user_req (target_user_id|1),(current_user_id|1)
150171 car_user_mgr_add_listener (uid|1)
150172 car_user_mgr_remove_listener (uid|1)
150173 car_user_mgr_disconnected (uid|1)
-150174 car_user_mgr_switch_user_request (uid|1),(user_id|1)
-150175 car_user_mgr_switch_user_response (uid|1),(status|1),(error_message|3)
+150174 car_user_mgr_switch_user_req (uid|1),(user_id|1)
+150175 car_user_mgr_switch_user_resp (uid|1),(status|1),(error_message|3)
150176 car_user_mgr_get_user_auth_req (types|4)
150177 car_user_mgr_get_user_auth_resp (values|4)
150178 car_user_mgr_set_user_auth_req (types_and_values_pairs|4)
150179 car_user_mgr_set_user_auth_resp (values|4)
+150180 car_user_mgr_create_user_req (uid|1),(safe_name|3),(user_type|3),(flags|1)
+150181 car_user_mgr_create_user_resp (uid|1),(status|1),(error_message|3)
+150182 car_user_mgr_remove_user_req (uid|1),(user_id|1)
+150183 car_user_mgr_remove_user_resp (uid|1),(status|1)
+150184 car_user_mgr_notify_lifecycle_listener (number_listeners|1),(event_type|1),(from_user_id|1),(to_user_id|1)
diff --git a/car-lib/Android.bp b/car-lib/Android.bp
index 18a15c0..39db28e 100644
--- a/car-lib/Android.bp
+++ b/car-lib/Android.bp
@@ -139,6 +139,7 @@
droidstubs {
name: "android.car-stubs-docs",
defaults: ["android.car-docs-default"],
+ removed_dex_api_filename: "removed-dex.txt",
args: "--hide UnavailableSymbol --no-docs --stub-packages android.car* ",
installable: false,
check_api: {
@@ -166,6 +167,7 @@
droidstubs {
name: "android.car-system-stubs-docs",
defaults: ["android.car-docs-default"],
+ removed_dex_api_filename: "system-removed-dex.txt",
args: "--hide UnavailableSymbol --no-docs --stub-packages android.car* " +
"--show-annotation android.annotation.SystemApi ",
installable: false,
@@ -194,7 +196,7 @@
droidstubs {
name: "android.car-test-stubs-docs",
defaults: ["android.car-docs-default"],
- args: "--hide UnavailableSymbol --no-docs --stub-packages android.car* " +
+ args: "--hide HiddenSuperclass --hide UnavailableSymbol --no-docs --stub-packages android.car* " +
"--show-annotation android.annotation.TestApi ",
installable: false,
check_api: {
@@ -217,7 +219,7 @@
"android.car",
],
api_filename: "api.txt",
- args: "--hide UnavailableSymbol --no-docs --stub-packages android.car* ",
+ args: "--hide HiddenSuperclass --hide UnavailableSymbol --no-docs --stub-packages android.car* ",
installable: false,
product_variables: {
pdk: {
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 4a966d4..c404acd 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -955,6 +955,17 @@
}
+package android.car.settings {
+
+ public class CarSettings {
+ }
+
+ public static final class CarSettings.Secure {
+ field public static final String KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL = "android.car.KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL";
+ }
+
+}
+
package android.car.storagemonitoring {
public final class CarStorageMonitoringManager {
diff --git a/car-lib/src/android/car/CarAppFocusManager.java b/car-lib/src/android/car/CarAppFocusManager.java
index 3d4d90d..cc0d10b 100644
--- a/car-lib/src/android/car/CarAppFocusManager.java
+++ b/car-lib/src/android/car/CarAppFocusManager.java
@@ -45,8 +45,11 @@
/**
* Application focus has changed. Note that {@link CarAppFocusManager} instance
* causing the change will not get this notification.
- * @param appType
- * @param active
+ *
+ * <p>Note that this call can happen for app focus grant, release, and ownership change.
+ *
+ * @param appType appType where the focus change has happened.
+ * @param active {@code true} if there is an active owner for the focus.
*/
void onAppFocusChanged(@AppFocusType int appType, boolean active);
}
diff --git a/car-lib/src/android/car/ICarUserService.aidl b/car-lib/src/android/car/ICarUserService.aidl
index 921c146..b3823b3 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -17,6 +17,8 @@
package android.car;
import android.content.pm.UserInfo;
+import android.car.user.UserCreationResult;
+import android.car.user.UserRemovalResult;
import android.car.user.UserIdentificationAssociationResponse;
import android.car.user.UserSwitchResult;
import com.android.internal.infra.AndroidFuture;
@@ -24,10 +26,14 @@
/** @hide */
interface ICarUserService {
- UserInfo createDriver(in String name, boolean admin);
- UserInfo createPassenger(in String name, int driverId);
+ AndroidFuture<UserCreationResult> createDriver(@nullable String name, boolean admin);
+ UserInfo createPassenger(@nullable String name, int driverId);
void switchDriver(int driverId, in AndroidFuture<UserSwitchResult> receiver);
void switchUser(int tagerUserId, int timeoutMs, in AndroidFuture<UserSwitchResult> receiver);
+ void setUserSwitchUiCallback(in IResultReceiver callback);
+ void createUser(@nullable String name, String userType, int flags, int timeoutMs,
+ in AndroidFuture<UserCreationResult> receiver);
+ UserRemovalResult removeUser(int userId);
List<UserInfo> getAllDrivers();
List<UserInfo> getPassengers(int driverId);
boolean startPassenger(int passengerId, int zoneId);
@@ -38,5 +44,4 @@
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/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index 5272548..cdf0cca 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -412,13 +412,13 @@
startActivityAsUser(intent, mActivityOptions.toBundle(), UserHandle.CURRENT);
Log.i(TAG, String.format("Activity launched: %s (options: %s, displayId: %d)",
mActivityOptions, intent, mActivityOptions.getLaunchDisplayId()));
- } catch (ActivityNotFoundException ex) {
+ } catch (ActivityNotFoundException e) {
Log.w(TAG, "Unable to find activity for intent: " + intent);
return false;
- } catch (Exception ex) {
+ } catch (RuntimeException e) {
// Catch all other possible exception to prevent service disruption by misbehaving
// applications.
- Log.e(TAG, "Error trying to launch intent: " + intent + ". Ignored", ex);
+ Log.e(TAG, "Error trying to launch intent: " + intent + ". Ignored", e);
return false;
}
return true;
diff --git a/car-lib/src/android/car/content/pm/AppBlockingPackageInfo.java b/car-lib/src/android/car/content/pm/AppBlockingPackageInfo.java
index dbabc50..001c1d2 100644
--- a/car-lib/src/android/car/content/pm/AppBlockingPackageInfo.java
+++ b/car-lib/src/android/car/content/pm/AppBlockingPackageInfo.java
@@ -123,10 +123,13 @@
public static final Parcelable.Creator<AppBlockingPackageInfo> CREATOR =
new Parcelable.Creator<AppBlockingPackageInfo>() {
+
+ @Override
public AppBlockingPackageInfo createFromParcel(Parcel in) {
return new AppBlockingPackageInfo(in);
}
+ @Override
public AppBlockingPackageInfo[] newArray(int size) {
return new AppBlockingPackageInfo[size];
}
diff --git a/car-lib/src/android/car/content/pm/CarPackageManager.java b/car-lib/src/android/car/content/pm/CarPackageManager.java
index 98c5822..6a8c7fb 100644
--- a/car-lib/src/android/car/content/pm/CarPackageManager.java
+++ b/car-lib/src/android/car/content/pm/CarPackageManager.java
@@ -123,6 +123,8 @@
/**
* Restarts the requested task. If task with {@code taskId} does not exist, do nothing.
*
+ * <p>This requires {@code android.permission.REAL_GET_TASKS} permission.
+ *
* @hide
*/
public void restartTask(int taskId) {
diff --git a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
index f6a2d53..971f1fc 100644
--- a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
+++ b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
@@ -29,6 +29,8 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -45,12 +47,15 @@
@Deprecated
@SystemApi
public final class CarHvacManager extends CarManagerBase {
- private final static boolean DBG = false;
- private final static String TAG = "CarHvacManager";
+ private static final boolean DBG = false;
+ private static final String TAG = "CarHvacManager";
private final CarPropertyManager mCarPropertyMgr;
- private final ArraySet<CarHvacEventCallback> mCallbacks = new ArraySet<>();
private CarPropertyEventListenerToBase mListenerToBase = null;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final ArraySet<CarHvacEventCallback> mCallbacks = new ArraySet<>();
/**
* HVAC property IDs for get/set methods
@@ -252,7 +257,7 @@
private static class CarPropertyEventListenerToBase implements CarPropertyEventCallback {
private final WeakReference<CarHvacManager> mManager;
- public CarPropertyEventListenerToBase(CarHvacManager manager) {
+ CarPropertyEventListenerToBase(CarHvacManager manager) {
mManager = new WeakReference<>(manager);
}
@@ -275,7 +280,7 @@
private void handleOnChangeEvent(CarPropertyValue value) {
Collection<CarHvacEventCallback> callbacks;
- synchronized (this) {
+ synchronized (mLock) {
callbacks = new ArraySet<>(mCallbacks);
}
if (!callbacks.isEmpty()) {
@@ -287,7 +292,7 @@
private void handleOnErrorEvent(int propertyId, int zone) {
Collection<CarHvacEventCallback> callbacks;
- synchronized (this) {
+ synchronized (mLock) {
callbacks = new ArraySet<>(mCallbacks);
}
if (!callbacks.isEmpty()) {
@@ -315,16 +320,18 @@
* Implement wrappers for contained CarPropertyManager object
* @param callback
*/
- public synchronized void registerCallback(CarHvacEventCallback callback) {
- if (mCallbacks.isEmpty()) {
- mListenerToBase = new CarPropertyEventListenerToBase(this);
- }
- List<CarPropertyConfig> configs = getPropertyList();
- for (CarPropertyConfig c : configs) {
+ public void registerCallback(CarHvacEventCallback callback) {
+ synchronized (mLock) {
+ if (mCallbacks.isEmpty()) {
+ mListenerToBase = new CarPropertyEventListenerToBase(this);
+ }
+ List<CarPropertyConfig> configs = getPropertyList();
+ for (CarPropertyConfig c : configs) {
// Register each individual propertyId
- mCarPropertyMgr.registerCallback(mListenerToBase, c.getPropertyId(), 0);
+ mCarPropertyMgr.registerCallback(mListenerToBase, c.getPropertyId(), 0);
+ }
+ mCallbacks.add(callback);
}
- mCallbacks.add(callback);
}
/**
@@ -332,21 +339,23 @@
* this listener, all listening will be stopped.
* @param callback
*/
- public synchronized void unregisterCallback(CarHvacEventCallback callback) {
- mCallbacks.remove(callback);
- try {
- List<CarPropertyConfig> configs = getPropertyList();
- for (CarPropertyConfig c : configs) {
- // Register each individual propertyId
- mCarPropertyMgr.unregisterCallback(mListenerToBase, c.getPropertyId());
+ public void unregisterCallback(CarHvacEventCallback callback) {
+ synchronized (mLock) {
+ mCallbacks.remove(callback);
+ try {
+ List<CarPropertyConfig> configs = getPropertyList();
+ for (CarPropertyConfig c : configs) {
+ // Register each individual propertyId
+ mCarPropertyMgr.unregisterCallback(mListenerToBase, c.getPropertyId());
+ }
+ } catch (RuntimeException e) {
+ Log.e(TAG, "getPropertyList exception ", e);
}
- } catch (Exception e) {
- Log.e(TAG, "getPropertyList exception ", e);
- }
- if (mCallbacks.isEmpty()) {
- mCarPropertyMgr.unregisterCallback(mListenerToBase);
- mListenerToBase = null;
+ if (mCallbacks.isEmpty()) {
+ mCarPropertyMgr.unregisterCallback(mListenerToBase);
+ mListenerToBase = null;
+ }
}
}
@@ -435,7 +444,7 @@
/** @hide */
public void onCarDisconnected() {
// TODO(b/142730482) Fix synchronization to use separate mLock
- synchronized (this) {
+ synchronized (mLock) {
mCallbacks.clear();
}
mCarPropertyMgr.onCarDisconnected();
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index 31eb173..9f57617 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -32,8 +32,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
-import android.view.Display;
-import android.view.DisplayAddress;
import java.util.ArrayList;
import java.util.Collections;
@@ -538,43 +536,6 @@
}
/**
- * Get the zone id for the display
- *
- * @param display display to query
- * @return zone id for display or
- * CarAudioManager.PRIMARY_AUDIO_ZONE if no match is found.
- * @hide
- */
- @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
- public int getZoneIdForDisplay(Display display) {
- DisplayAddress address = display.getAddress();
- if (address instanceof DisplayAddress.Physical) {
- DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;
- if (physicalAddress != null) {
- return getZoneIdForDisplayPortId(physicalAddress.getPort());
- }
- }
- return PRIMARY_AUDIO_ZONE;
- }
-
- /**
- * Get the zone id for the display port id passed in
- *
- * @param displayPortId display port id to query
- * @return zone id for display port id or
- * CarAudioManager.PRIMARY_AUDIO_ZONE if no match is found.
- * @hide
- */
- @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
- public int getZoneIdForDisplayPortId(byte displayPortId) {
- try {
- return mService.getZoneIdForDisplayPortId(displayPortId);
- } catch (RemoteException e) {
- return handleRemoteExceptionFromCarService(e, 0);
- }
- }
-
- /**
* Gets the output device for a given {@link AudioAttributes} usage in zoneId.
*
* <p><b>Note:</b> To be used for routing to a specific device. Most applications should
diff --git a/car-lib/src/android/car/media/ICarAudio.aidl b/car-lib/src/android/car/media/ICarAudio.aidl
index 7491a19..a8997ce 100644
--- a/car-lib/src/android/car/media/ICarAudio.aidl
+++ b/car-lib/src/android/car/media/ICarAudio.aidl
@@ -48,8 +48,6 @@
boolean setZoneIdForUid(int zoneId, int uid);
boolean clearZoneIdForUid(int uid);
- int getZoneIdForDisplayPortId(byte displayPortId);
-
String getOutputDeviceAddressForUsage(int zoneId, int usage);
List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId);
diff --git a/car-lib/src/android/car/settings/CarSettings.java b/car-lib/src/android/car/settings/CarSettings.java
index 932eec0..3f5a6c3 100644
--- a/car-lib/src/android/car/settings/CarSettings.java
+++ b/car-lib/src/android/car/settings/CarSettings.java
@@ -23,6 +23,7 @@
*
* @hide
*/
+@SystemApi
public class CarSettings {
private CarSettings() {
@@ -73,6 +74,22 @@
"android.car.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE";
/**
+ * User id of the last foreground user
+ *
+ * @hide
+ */
+ public static final String LAST_ACTIVE_USER_ID =
+ "android.car.LAST_ACTIVE_USER_ID";
+
+ /**
+ * User id of the last persistent (i.e, not counting ephemeral guests) foreground user
+ *
+ * @hide
+ */
+ public static final String LAST_ACTIVE_PERSISTENT_USER_ID =
+ "android.car.LAST_ACTIVE_PERSISTENT_USER_ID";
+
+ /**
* Defines global runtime overrides to system bar policy.
*
* See {@link com.android.systemui.wm.BarControlPolicy} for value format.
diff --git a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
index ac59bac..6f993e3 100644
--- a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
+++ b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
@@ -47,9 +47,17 @@
private final SingleMessageHandler<IoStats> mMessageHandler;
private final Set<IoStatsListener> mListeners = new HashSet<>();
+ /**
+ * Implementers will be notified on every new I/O activity calculated stats.
+ */
public interface IoStatsListener {
+
+ /**
+ * Invoked when a new periodic snapshot delta of I/O activities is calculated.
+ */
void onSnapshot(IoStats snapshot);
}
+
private static final class ListenerToService extends IIoStatsListener.Stub {
private final WeakReference<CarStorageMonitoringManager> mManager;
@@ -109,7 +117,7 @@
* It will return either PRE_EOL_INFO_UNKNOWN if the value can't be determined,
* or one of PRE_EOL_INFO_{NORMAL|WARNING|URGENT} depending on the device state.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public int getPreEolIndicatorStatus() {
try {
return mService.getPreEolIndicatorStatus();
@@ -127,7 +135,7 @@
*
* If either or both indicators are not available, they will be reported as UNKNOWN.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public WearEstimate getWearEstimate() {
try {
return mService.getWearEstimate();
@@ -147,7 +155,7 @@
*
* If no indicators are available, an empty list will be returned.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public List<WearEstimateChange> getWearEstimateHistory() {
try {
return mService.getWearEstimateHistory();
@@ -166,7 +174,7 @@
*
* If the information is not available, an empty list will be returned.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public List<IoStatsEntry> getBootIoStats() {
try {
return mService.getBootIoStats();
@@ -196,7 +204,7 @@
*
* <p>If the information is not available, SHUTDOWN_COST_INFO_MISSING will be returned.</p>s
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public long getShutdownDiskWriteAmount() {
try {
return mService.getShutdownDiskWriteAmount();
@@ -213,7 +221,7 @@
*
* If the information is not available, an empty list will be returned.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public List<IoStatsEntry> getAggregateIoStats() {
try {
return mService.getAggregateIoStats();
@@ -233,7 +241,7 @@
*
* If the information is not available, an empty list will be returned.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public List<IoStats> getIoStatsDeltas() {
try {
return mService.getIoStatsDeltas();
@@ -250,7 +258,7 @@
*
* The timing of availability of the deltas is configurable by the OEM.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public void registerListener(IoStatsListener listener) {
try {
if (mListeners.isEmpty()) {
@@ -268,7 +276,7 @@
/**
* This method removes a registered listener of I/O stats deltas.
*/
- @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+ @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
public void unregisterListener(IoStatsListener listener) {
try {
if (!mListeners.remove(listener)) {
diff --git a/car-lib/src/android/car/storagemonitoring/WearEstimate.java b/car-lib/src/android/car/storagemonitoring/WearEstimate.java
index ee6aa31..07ef4c7 100644
--- a/car-lib/src/android/car/storagemonitoring/WearEstimate.java
+++ b/car-lib/src/android/car/storagemonitoring/WearEstimate.java
@@ -47,11 +47,10 @@
public static final WearEstimate UNKNOWN_ESTIMATE = new WearEstimate(UNKNOWN, UNKNOWN);
public static final Parcelable.Creator<WearEstimate> CREATOR =
- new Parcelable.Creator<WearEstimate>() {
- public WearEstimate createFromParcel(Parcel in) {
+ new Parcelable.Creator<WearEstimate>() {
+ public WearEstimate createFromParcel(Parcel in) {
return new WearEstimate(in);
}
-
public WearEstimate[] newArray(int size) {
return new WearEstimate[size];
}
@@ -60,16 +59,16 @@
/**
* Wear estimate data for "type A" storage.
*/
- @IntRange(from=-1, to=100)
+ @IntRange(from = -1, to = 100)
public final int typeA;
/**
* Wear estimate data for "type B" storage.
*/
- @IntRange(from=-1, to=100)
+ @IntRange(from = -1, to = 100)
public final int typeB;
- private static final int validateWearValue(int value) {
+ private static int validateWearValue(int value) {
if (value == UNKNOWN) return value;
if ((value >= 0) && (value <= 100)) return value;
throw new IllegalArgumentException(value + " is not a valid wear estimate");
@@ -137,7 +136,7 @@
@Override
public boolean equals(Object other) {
if (other instanceof WearEstimate) {
- WearEstimate wo = (WearEstimate)other;
+ WearEstimate wo = (WearEstimate) other;
return wo.typeA == typeA && wo.typeB == typeB;
}
return false;
@@ -148,7 +147,7 @@
return Objects.hash(typeA, typeB);
}
- private static final String wearValueToString(int value) {
+ private static String wearValueToString(int value) {
if (value == UNKNOWN) return "unknown";
return value + "%";
}
diff --git a/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java b/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
index 2d454b4..a2839d5 100644
--- a/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
+++ b/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
@@ -15,15 +15,16 @@
*/
package android.car.storagemonitoring;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+
import java.time.Instant;
import java.util.Objects;
-import static java.util.Objects.requireNonNull;
-
/**
* Change in wear-out information.
*
@@ -35,15 +36,15 @@
@SystemApi
public final class WearEstimateChange implements Parcelable {
public static final Parcelable.Creator<WearEstimateChange> CREATOR =
- new Parcelable.Creator<WearEstimateChange>() {
- public WearEstimateChange createFromParcel(Parcel in) {
- return new WearEstimateChange(in);
- }
+ new Parcelable.Creator<WearEstimateChange>() {
+ public WearEstimateChange createFromParcel(Parcel in) {
+ return new WearEstimateChange(in);
+ }
- public WearEstimateChange[] newArray(int size) {
+ public WearEstimateChange[] newArray(int size) {
return new WearEstimateChange[size];
}
- };
+ };
/**
* The previous wear estimate.
@@ -110,12 +111,12 @@
@Override
public boolean equals(Object other) {
if (other instanceof WearEstimateChange) {
- WearEstimateChange wo = (WearEstimateChange)other;
- return wo.isAcceptableDegradation == isAcceptableDegradation &&
- wo.uptimeAtChange == uptimeAtChange &&
- wo.dateAtChange.equals(dateAtChange) &&
- wo.oldEstimate.equals(oldEstimate) &&
- wo.newEstimate.equals(newEstimate);
+ WearEstimateChange wo = (WearEstimateChange) other;
+ return wo.isAcceptableDegradation == isAcceptableDegradation
+ && wo.uptimeAtChange == uptimeAtChange
+ && wo.dateAtChange.equals(dateAtChange)
+ && wo.oldEstimate.equals(oldEstimate)
+ && wo.newEstimate.equals(newEstimate);
}
return false;
}
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index 048ae0b..a4daa42 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -20,6 +20,8 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.os.Process.myUid;
+import static com.android.internal.util.FunctionalUtils.getLambdaName;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -32,6 +34,7 @@
import android.car.CarManagerBase;
import android.car.ICarUserService;
import android.content.pm.UserInfo;
+import android.content.pm.UserInfo.UserInfoFlag;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -56,6 +59,7 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
/**
* API to manage users related to car.
@@ -208,7 +212,7 @@
@Override
protected void onCompleted(UserSwitchResult result, Throwable err) {
if (result != null) {
- EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_RESPONSE, uid,
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_RESP, uid,
result.getStatus(), result.getErrorMessage());
} else {
Log.w(TAG, "switchUser(" + targetUserId + ") failed: " + err);
@@ -216,7 +220,7 @@
super.onCompleted(result, err);
};
};
- EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_REQUEST, uid, targetUserId);
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_REQ, uid, targetUserId);
mService.switchUser(targetUserId, HAL_TIMEOUT_MS, future);
return future;
} catch (RemoteException e) {
@@ -228,6 +232,64 @@
}
/**
+ * Creates a new Android user.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
+ public AndroidFuture<UserCreationResult> createUser(@Nullable String name,
+ @NonNull String userType, @UserInfoFlag int flags) {
+ int uid = myUid();
+ try {
+ AndroidFuture<UserCreationResult> future = new AndroidFuture<UserCreationResult>() {
+ @Override
+ protected void onCompleted(UserCreationResult result, Throwable err) {
+ if (result != null) {
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_CREATE_USER_RESP, uid,
+ result.getStatus(), result.getErrorMessage());
+ } else {
+ Log.w(TAG, "createUser(" + userType + "," + UserInfo.flagsToString(flags)
+ + ") failed: " + err);
+ }
+ super.onCompleted(result, err);
+ };
+ };
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_CREATE_USER_REQ, uid,
+ safeName(name), userType, flags);
+ mService.createUser(name, userType, flags, HAL_TIMEOUT_MS, future);
+ return future;
+ } catch (RemoteException e) {
+ AndroidFuture<UserCreationResult> future = new AndroidFuture<>();
+ future.complete(new UserCreationResult(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE,
+ null, null));
+ return handleRemoteExceptionFromCarService(e, future);
+ }
+ }
+
+ /**
+ * Removes a user.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public UserRemovalResult removeUser(@UserIdInt int userId) {
+ int uid = myUid();
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_REMOVE_USER_REQ, uid, userId);
+ int status = UserRemovalResult.STATUS_HAL_INTERNAL_FAILURE;
+ try {
+ UserRemovalResult result = mService.removeUser(userId);
+ status = result.getStatus();
+ return result;
+ } catch (RemoteException e) {
+ return handleRemoteExceptionFromCarService(e,
+ new UserRemovalResult(UserRemovalResult.STATUS_HAL_INTERNAL_FAILURE));
+ } finally {
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_REMOVE_USER_RESP, uid, status);
+ }
+ }
+
+ /**
* Adds a listener for {@link UserLifecycleEvent user lifecycle events}.
*
* @throws IllegalStateException if the listener was already added.
@@ -261,6 +323,12 @@
if (mListeners == null) {
mListeners = new ArrayMap<>(1); // Most likely app will have just one listener
+ } else if (DBG) {
+ Log.d(TAG, "addListener(" + getLambdaName(listener) + "): context " + getContext()
+ + " already has " + mListeners.size() + " listeners: "
+ + mListeners.keySet().stream()
+ .map((l) -> getLambdaName(l))
+ .collect(Collectors.toList()), new Exception());
}
if (DBG) Log.d(TAG, "Adding listener: " + listener);
mListeners.put(listener, executor);
@@ -443,10 +511,15 @@
Log.w(TAG, "No listeners for event " + event);
return;
}
- for (int i = 0; i < listeners.size(); i++) {
+ int size = listeners.size();
+ EventLog.writeEvent(EventLogTags.CAR_USER_MGR_NOTIFY_LIFECYCLE_LISTENER,
+ size, eventType, from, to);
+ for (int i = 0; i < size; i++) {
UserLifecycleListener listener = listeners.keyAt(i);
Executor executor = listeners.valueAt(i);
- if (DBG) Log.d(TAG, "Calling listener " + listener + " for event " + event);
+ if (DBG) {
+ Log.d(TAG, "Calling " + getLambdaName(listener) + " for event " + event);
+ }
executor.execute(() -> listener.onEvent(event));
}
}
@@ -504,7 +577,7 @@
* @hide
*/
public boolean isValidUser(@UserIdInt int userId) {
- List<UserInfo> allUsers = mUserManager.getUsers();
+ List<UserInfo> allUsers = mUserManager.getUsers(/* excludeDying= */ true);
for (int i = 0; i < allUsers.size(); i++) {
UserInfo user = allUsers.get(i);
if (user.id == userId && (userId != UserHandle.USER_SYSTEM
@@ -515,6 +588,13 @@
return false;
}
+ // TODO(b/150413515): use from UserHelper instead (would require a new make target, otherwise it
+ // would include the whole car-user-lib)
+ @Nullable
+ private static String safeName(@Nullable String name) {
+ return name == null ? name : name.length() + "_chars";
+ }
+
/**
* Defines a lifecycle event for an Android user.
*
diff --git a/car-lib/src/android/car/user/CommonResults.java b/car-lib/src/android/car/user/CommonResults.java
new file mode 100644
index 0000000..9238a0b
--- /dev/null
+++ b/car-lib/src/android/car/user/CommonResults.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.user;
+
+/**
+ * Defines status for common result objects.
+ */
+final class CommonResults {
+
+ /**
+ * Operation was is successful for both HAL and Android.
+ */
+ public static final int STATUS_SUCCESSFUL = 1;
+
+ /**
+ * Operation failed on Android.
+ */
+ public static final int STATUS_ANDROID_FAILURE = 2;
+
+ /**
+ * Operation failed on HAL.
+ */
+ public static final int STATUS_HAL_FAILURE = 3;
+
+ /**
+ * Operation failed due to an error communication with HAL (like timeout).
+ */
+ public static final int STATUS_HAL_INTERNAL_FAILURE = 4;
+
+ /**
+ * Operation failed due to invalid request.
+ */
+ public static final int STATUS_INVALID_REQUEST = 5;
+
+ /**
+ * Reference for common status - anything higher than this can be used for custom status
+ */
+ static final int LAST_COMMON_STATUS = 100;
+
+ private CommonResults() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/car-lib/src/android/car/user/ExperimentalCarUserManager.java b/car-lib/src/android/car/user/ExperimentalCarUserManager.java
index 1396971..fa7eb72 100644
--- a/car-lib/src/android/car/user/ExperimentalCarUserManager.java
+++ b/car-lib/src/android/car/user/ExperimentalCarUserManager.java
@@ -72,19 +72,21 @@
*
* @param name The name of the driver to be created.
* @param admin Whether the created driver will be an admin.
- * @return user id of the created driver, or {@code INVALID_USER_ID} if the driver could
- * not be created.
+ * @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)
- @Nullable
- public int createDriver(@NonNull String name, boolean admin) {
+ public AndroidFuture<UserCreationResult> createDriver(@NonNull String name, boolean admin) {
try {
- UserInfo ui = mService.createDriver(name, admin);
- return ui != null ? ui.id : INVALID_USER_ID;
+ return mService.createDriver(name, admin);
} catch (RemoteException e) {
- return handleRemoteExceptionFromCarService(e, null);
+ AndroidFuture<UserCreationResult> future = new AndroidFuture<>();
+ future.complete(new UserCreationResult(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE,
+ null, null));
+ handleRemoteExceptionFromCarService(e);
+ return future;
}
}
diff --git a/car-lib/src/android/car/user/UserCreationResult.aidl b/car-lib/src/android/car/user/UserCreationResult.aidl
new file mode 100644
index 0000000..a735202
--- /dev/null
+++ b/car-lib/src/android/car/user/UserCreationResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.user;
+
+parcelable UserCreationResult;
diff --git a/car-lib/src/android/car/user/UserCreationResult.java b/car-lib/src/android/car/user/UserCreationResult.java
new file mode 100644
index 0000000..328908f
--- /dev/null
+++ b/car-lib/src/android/car/user/UserCreationResult.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.user;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.pm.UserInfo;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * User creation results.
+ *
+ * @hide
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstructor = true,
+ genHiddenConstDefs = true)
+public final class UserCreationResult implements Parcelable {
+
+ /**
+ * {@link Status} called when user creation is successful for both HAL and Android.
+ *
+ * @hide
+ */
+ public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
+
+ /**
+ * {@link Status} called when user creation failed on Android - HAL is not even called in this
+ * case.
+ *
+ * @hide
+ */
+ public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
+
+ /**
+ * {@link Status} called when user was created on Android but HAL returned a failure - the
+ * Android user is automatically removed.
+ *
+ * @hide
+ */
+ public static final int STATUS_HAL_FAILURE = CommonResults.STATUS_HAL_FAILURE;
+
+ /**
+ * {@link Status} called when user creation is failed for HAL for some internal error - the
+ * Android user is not automatically removed.
+ *
+ * @hide
+ */
+ public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
+
+ /**
+ * {@link Status} called when given parameters or environment states are invalid for creating
+ * user - HAL or Android user creation is not requested.
+ */
+ public static final int STATUS_INVALID_REQUEST = CommonResults.STATUS_INVALID_REQUEST;
+
+ /**
+ * Gets the user creation result status.
+ *
+ * @return either {@link UserCreationResult#STATUS_SUCCESSFUL},
+ * {@link UserCreationResult#STATUS_ANDROID_FAILURE},
+ * {@link UserCreationResult#STATUS_HAL_FAILURE},
+ * {@link UserCreationResult#STATUS_HAL_INTERNAL_FAILURE}, or
+ * {@link UserCreationResult#STATUS_INVALID_REQUEST}.
+ */
+ private final @Status int mStatus;
+
+ /**
+ * Gets the created user.
+ */
+ @Nullable
+ private final UserInfo mUser;
+
+ /**
+ * Gets the error message sent by HAL, if any.
+ */
+ @Nullable
+ private final String mErrorMessage;
+
+ /**
+ * Checks if this result is successful.
+ */
+ public boolean isSuccess() {
+ return mStatus == STATUS_SUCCESSFUL;
+ }
+
+ // TODO(b/158195639): if you change any status constant, you need to manually assign its 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/UserCreationResult.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @IntDef(prefix = "STATUS_", value = {
+ STATUS_SUCCESSFUL,
+ STATUS_ANDROID_FAILURE,
+ STATUS_HAL_FAILURE,
+ STATUS_HAL_INTERNAL_FAILURE,
+ STATUS_INVALID_REQUEST
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Status {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String statusToString(@Status int value) {
+ switch (value) {
+ case STATUS_SUCCESSFUL:
+ return "STATUS_SUCCESSFUL";
+ case STATUS_ANDROID_FAILURE:
+ return "STATUS_ANDROID_FAILURE";
+ case STATUS_HAL_FAILURE:
+ return "STATUS_HAL_FAILURE";
+ case STATUS_HAL_INTERNAL_FAILURE:
+ return "STATUS_HAL_INTERNAL_FAILURE";
+ case STATUS_INVALID_REQUEST:
+ return "STATUS_INVALID_REQUEST";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * Creates a new UserCreationResult.
+ *
+ * @param status
+ * Gets the user creation result status.
+ *
+ * @return either {@link UserCreationResult#STATUS_SUCCESSFUL},
+ * {@link UserCreationResult#STATUS_ANDROID_FAILURE},
+ * {@link UserCreationResult#STATUS_HAL_FAILURE},
+ * {@link UserCreationResult#STATUS_HAL_INTERNAL_FAILURE}, or
+ * {@link UserCreationResult#STATUS_INVALID_REQUEST}.
+ * @param user
+ * Gets the created user.
+ * @param errorMessage
+ * Gets the error message sent by HAL, if any.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public UserCreationResult(
+ @Status int status,
+ @Nullable UserInfo user,
+ @Nullable String errorMessage) {
+ this.mStatus = status;
+
+ if (!(mStatus == STATUS_SUCCESSFUL)
+ && !(mStatus == STATUS_ANDROID_FAILURE)
+ && !(mStatus == STATUS_HAL_FAILURE)
+ && !(mStatus == STATUS_HAL_INTERNAL_FAILURE)
+ && !(mStatus == STATUS_INVALID_REQUEST)) {
+ throw new java.lang.IllegalArgumentException(
+ "status was " + mStatus + " but must be one of: "
+ + "STATUS_SUCCESSFUL(" + STATUS_SUCCESSFUL + "), "
+ + "STATUS_ANDROID_FAILURE(" + STATUS_ANDROID_FAILURE + "), "
+ + "STATUS_HAL_FAILURE(" + STATUS_HAL_FAILURE + "), "
+ + "STATUS_HAL_INTERNAL_FAILURE(" + STATUS_HAL_INTERNAL_FAILURE + "), "
+ + "STATUS_INVALID_REQUEST(" + STATUS_INVALID_REQUEST + ")");
+ }
+
+ this.mUser = user;
+ this.mErrorMessage = errorMessage;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Gets the user creation result status.
+ *
+ * @return either {@link UserCreationResult#STATUS_SUCCESSFUL},
+ * {@link UserCreationResult#STATUS_ANDROID_FAILURE},
+ * {@link UserCreationResult#STATUS_HAL_FAILURE},
+ * {@link UserCreationResult#STATUS_HAL_INTERNAL_FAILURE}, or
+ * {@link UserCreationResult#STATUS_INVALID_REQUEST}.
+ */
+ @DataClass.Generated.Member
+ public @Status int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Gets the created user.
+ */
+ @DataClass.Generated.Member
+ public @Nullable UserInfo getUser() {
+ return mUser;
+ }
+
+ /**
+ * Gets the error message sent by HAL, if any.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "UserCreationResult { " +
+ "status = " + statusToString(mStatus) + ", " +
+ "user = " + mUser + ", " +
+ "errorMessage = " + mErrorMessage +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mUser != null) flg |= 0x2;
+ if (mErrorMessage != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeInt(mStatus);
+ if (mUser != null) dest.writeTypedObject(mUser, flags);
+ if (mErrorMessage != null) dest.writeString(mErrorMessage);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ UserCreationResult(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int status = in.readInt();
+ UserInfo user = (flg & 0x2) == 0 ? null : (UserInfo) in.readTypedObject(UserInfo.CREATOR);
+ String errorMessage = (flg & 0x4) == 0 ? null : in.readString();
+
+ this.mStatus = status;
+
+ if (!(mStatus == STATUS_SUCCESSFUL)
+ && !(mStatus == STATUS_ANDROID_FAILURE)
+ && !(mStatus == STATUS_HAL_FAILURE)
+ && !(mStatus == STATUS_HAL_INTERNAL_FAILURE)
+ && !(mStatus == STATUS_INVALID_REQUEST)) {
+ throw new java.lang.IllegalArgumentException(
+ "status was " + mStatus + " but must be one of: "
+ + "STATUS_SUCCESSFUL(" + STATUS_SUCCESSFUL + "), "
+ + "STATUS_ANDROID_FAILURE(" + STATUS_ANDROID_FAILURE + "), "
+ + "STATUS_HAL_FAILURE(" + STATUS_HAL_FAILURE + "), "
+ + "STATUS_HAL_INTERNAL_FAILURE(" + STATUS_HAL_INTERNAL_FAILURE + "), "
+ + "STATUS_INVALID_REQUEST(" + STATUS_INVALID_REQUEST + ")");
+ }
+
+ this.mUser = user;
+ this.mErrorMessage = errorMessage;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<UserCreationResult> CREATOR
+ = new Parcelable.Creator<UserCreationResult>() {
+ @Override
+ public UserCreationResult[] newArray(int size) {
+ return new UserCreationResult[size];
+ }
+
+ @Override
+ public UserCreationResult createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new UserCreationResult(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1591401523007L,
+ codegenVersion = "1.0.15",
+ sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserCreationResult.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_INVALID_REQUEST\nprivate final @android.car.user.UserCreationResult.Status int mStatus\nprivate final @android.annotation.Nullable android.content.pm.UserInfo mUser\nprivate final @android.annotation.Nullable java.lang.String mErrorMessage\npublic boolean isSuccess()\nclass UserCreationResult 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/UserRemovalResult.aidl b/car-lib/src/android/car/user/UserRemovalResult.aidl
new file mode 100644
index 0000000..00a0eba
--- /dev/null
+++ b/car-lib/src/android/car/user/UserRemovalResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.user;
+
+parcelable UserRemovalResult;
diff --git a/car-lib/src/android/car/user/UserRemovalResult.java b/car-lib/src/android/car/user/UserRemovalResult.java
new file mode 100644
index 0000000..dbfd8a6
--- /dev/null
+++ b/car-lib/src/android/car/user/UserRemovalResult.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.user;
+
+import android.annotation.IntDef;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * User remove result.
+ *
+ * @hide
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstructor = true,
+ genHiddenConstDefs = true)
+public final class UserRemovalResult implements Parcelable {
+
+ /**
+ * When user remove is successful.
+ *
+ * @hide
+ */
+ public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
+
+ /**
+ * When user remove fails for android. Hal user is not removed.
+ *
+ * @hide
+ */
+ public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
+
+ /**
+ * When remove user fails for unknown error.
+ *
+ * @hide
+ */
+ public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
+
+ /**
+ * When user to remove is same as current user.
+ *
+ * @hide
+ */
+ public static final int STATUS_TARGET_USER_IS_CURRENT_USER =
+ CommonResults.LAST_COMMON_STATUS + 1;
+
+ /**
+ * When user to remove doesn't exits.
+ *
+ * @hide
+ */
+ public static final int STATUS_USER_DOES_NOT_EXIST = CommonResults.LAST_COMMON_STATUS + 2;
+
+ /**
+ * When user to remove is last admin user.
+ *
+ * @hide
+ */
+ public static final int STATUS_TARGET_USER_IS_LAST_ADMIN_USER =
+ CommonResults.LAST_COMMON_STATUS + 3;
+
+ /**
+ * Gets the user switch result status.
+ *
+ * @return either {@link UserRemovalResult#STATUS_SUCCESSFUL},
+ * {@link UserRemovalResult#STATUS_ANDROID_FAILURE},
+ * {@link UserRemovalResult#STATUS_HAL_INTERNAL_FAILURE},
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_CURRENT_USER},
+ * {@link UserRemovalResult#STATUS_USER_DOES_NOT_EXIST}, or
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_LAST_ADMIN_USER}.
+ */
+ private final @Status int mStatus;
+
+ public boolean isSuccess() {
+ return mStatus == STATUS_SUCCESSFUL;
+ }
+
+ // TODO(b/158195639): if you change any status constant, you need to manually assign its values
+ // (rather than using CommonResults) before running codegen to regenerate the class
+
+
+ // Code below generated by codegen v1.0.15.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/user/UserRemovalResult.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @IntDef(prefix = "STATUS_", value = {
+ STATUS_SUCCESSFUL,
+ STATUS_ANDROID_FAILURE,
+ STATUS_HAL_INTERNAL_FAILURE,
+ STATUS_TARGET_USER_IS_CURRENT_USER,
+ STATUS_USER_DOES_NOT_EXIST,
+ STATUS_TARGET_USER_IS_LAST_ADMIN_USER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Status {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String statusToString(@Status int value) {
+ switch (value) {
+ case STATUS_SUCCESSFUL:
+ return "STATUS_SUCCESSFUL";
+ case STATUS_ANDROID_FAILURE:
+ return "STATUS_ANDROID_FAILURE";
+ case STATUS_HAL_INTERNAL_FAILURE:
+ return "STATUS_HAL_INTERNAL_FAILURE";
+ case STATUS_TARGET_USER_IS_CURRENT_USER:
+ return "STATUS_TARGET_USER_IS_CURRENT_USER";
+ case STATUS_USER_DOES_NOT_EXIST:
+ return "STATUS_USER_DOES_NOT_EXIST";
+ case STATUS_TARGET_USER_IS_LAST_ADMIN_USER:
+ return "STATUS_TARGET_USER_IS_LAST_ADMIN_USER";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * Creates a new UserRemovalResult.
+ *
+ * @param status
+ * Gets the user switch result status.
+ *
+ * @return either {@link UserRemovalResult#STATUS_SUCCESSFUL},
+ * {@link UserRemovalResult#STATUS_ANDROID_FAILURE},
+ * {@link UserRemovalResult#STATUS_HAL_INTERNAL_FAILURE},
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_CURRENT_USER},
+ * {@link UserRemovalResult#STATUS_USER_DOES_NOT_EXIST}, or
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_LAST_ADMIN_USER}.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public UserRemovalResult(
+ int status) {
+ this.mStatus = status;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Gets the user switch result status.
+ *
+ * @return either {@link UserRemovalResult#STATUS_SUCCESSFUL},
+ * {@link UserRemovalResult#STATUS_ANDROID_FAILURE},
+ * {@link UserRemovalResult#STATUS_HAL_INTERNAL_FAILURE},
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_CURRENT_USER},
+ * {@link UserRemovalResult#STATUS_USER_DOES_NOT_EXIST}, or
+ * {@link UserRemovalResult#STATUS_TARGET_USER_IS_LAST_ADMIN_USER}.
+ */
+ @DataClass.Generated.Member
+ public int getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "UserRemovalResult { " +
+ "status = " + mStatus +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mStatus);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ UserRemovalResult(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int status = in.readInt();
+
+ this.mStatus = status;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<UserRemovalResult> CREATOR
+ = new Parcelable.Creator<UserRemovalResult>() {
+ @Override
+ public UserRemovalResult[] newArray(int size) {
+ return new UserRemovalResult[size];
+ }
+
+ @Override
+ public UserRemovalResult createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new UserRemovalResult(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1591259644931L,
+ codegenVersion = "1.0.15",
+ sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserRemovalResult.java",
+ inputSignatures = "public static final int STATUS_SUCCESSFUL\npublic static final int STATUS_ANDROID_FAILURE\npublic static final int STATUS_HAL_INTERNAL_FAILURE\npublic static final int STATUS_TARGET_USER_IS_CURRENT_USER\npublic static final int STATUS_USER_DOES_NOT_EXIST\npublic static final int STATUS_TARGET_USER_IS_LAST_ADMIN_USER\nprivate final int mStatus\npublic boolean isSuccess()\nclass UserRemovalResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/car-lib/src/android/car/user/UserSwitchResult.java b/car-lib/src/android/car/user/UserSwitchResult.java
index 5792bf6..55e2dbc 100644
--- a/car-lib/src/android/car/user/UserSwitchResult.java
+++ b/car-lib/src/android/car/user/UserSwitchResult.java
@@ -37,66 +37,51 @@
public final class UserSwitchResult implements Parcelable {
/**
- * {@link UserSwitchStatus} called when user switch is successful for both HAL and Android.
- *
- * @hide
+ * When user switch is successful for both HAL and Android.
*/
- public static final int STATUS_SUCCESSFUL = 1;
+ public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
/**
- * {@link UserSwitchStatus} called when user switch is only successful for Hal but not for
- * Android. Hal user switch rollover message have been sent.
- *
- * @hide
+ * When user switch is only successful for Hal but not for Android. Hal user switch rollover
+ * message have been sent.
*/
- public static final int STATUS_ANDROID_FAILURE = 2;
+ public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
/**
- * {@link UserSwitchStatus} called when user switch is failed for HAL. User switch for Android
- * is not called.
- *
- * @hide
+ * When user switch fails for HAL. User switch for Android is not called.
*/
- public static final int STATUS_HAL_FAILURE = 3;
+ public static final int STATUS_HAL_FAILURE = CommonResults.STATUS_HAL_FAILURE;
/**
- * {@link UserSwitchStatus} called when user switch is failed for HAL for some internal error.
- * User switch for Android is not called.
- *
- * @hide
+ * When user switch fails for HAL for some internal error. User switch for Android is not
+ * called.
*/
- public static final int STATUS_HAL_INTERNAL_FAILURE = 4;
+ public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
/**
- * {@link UserSwitchStatus} called when target user is same as current user.
- *
- * @hide
+ * When given parameters or environment states are invalid for switching user. HAL or Android
+ * user switch is not requested.
*/
- public static final int STATUS_ALREADY_REQUESTED_USER = 5;
+ public static final int STATUS_INVALID_REQUEST = CommonResults.STATUS_INVALID_REQUEST;
/**
- * {@link UserSwitchStatus} called when another user switch request for the same target user is
- * in process.
- *
- * @hide
+ * When target user is same as current user.
*/
- public static final int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO = 6;
+ public static final int STATUS_ALREADY_REQUESTED_USER =
+ CommonResults.LAST_COMMON_STATUS + 1;
/**
- * {@link UserSwitchStatus} called when another user switch request for a new different target
- * user is received. Previous request is abandoned.
- *
- * @hide
+ * When another user switch request for the same target user is in process.
*/
- public static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST = 7;
+ public static final int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO =
+ CommonResults.LAST_COMMON_STATUS + 2;
/**
- * {@link UserSwitchStatus} called when given parameters or environment states are invalid for
- * switching user. HAL or Android user switch is not requested.
- *
- * @hide
+ * When another user switch request for a new different target user is received. Previous
+ * request is abandoned.
*/
- public static final int STATUS_INVALID_REQUEST = 8;
+ public static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST =
+ CommonResults.LAST_COMMON_STATUS + 3;
/**
* Gets the user switch result status.
@@ -106,11 +91,11 @@
* {@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}
+ * {@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;
+ private final @Status int mStatus;
/**
* Gets the error message, if any.
@@ -118,6 +103,15 @@
@Nullable
private final String mErrorMessage;
+ /**
+ * Check if {@link UserSwitchResult} is successful.
+ */
+ public boolean isSuccess() {
+ return mStatus == STATUS_SUCCESSFUL || mStatus == STATUS_ALREADY_REQUESTED_USER;
+ }
+
+ // TODO(b/158195639): if you change any status constant, you need to manually assign its values
+ // (rather than using CommonResults) before running codegen to regenerate the class
// Code below generated by codegen v1.0.15.
@@ -183,7 +177,7 @@
* {@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}
+ * {@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
@@ -208,7 +202,7 @@
* {@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}
+ * {@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}.
*/
@@ -286,10 +280,10 @@
};
@DataClass.Generated(
- time = 1589580732431L,
+ time = 1590737883648L,
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\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)")
+ 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\npublic boolean isSuccess()\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/VmsOperationRecorder.java b/car-lib/src/android/car/vms/VmsOperationRecorder.java
index f56c475..aa3f09d 100644
--- a/car-lib/src/android/car/vms/VmsOperationRecorder.java
+++ b/car-lib/src/android/car/vms/VmsOperationRecorder.java
@@ -61,68 +61,127 @@
// VMS Client operations.
+ /**
+ * Records {@code subscribe} operation with the {@link VmsLayer} layer passed as parameter.
+ */
public void subscribe(VmsLayer layer) {
recordOp("subscribe", layer);
}
+ /**
+ * Records {@code unsubscribe} operation with the {@link VmsLayer} layer passed as parameter.
+ */
public void unsubscribe(VmsLayer layer) {
recordOp("unsubscribe", layer);
}
+ /**
+ * Records {@code subscribe} operation with the {@link VmsLayer} layer and publisher id passed
+ * both as parameter.
+ */
public void subscribe(VmsLayer layer, int publisherId) {
recordOp("subscribe", "publisherId", publisherId, layer);
}
+ /**
+ * Record {@code unsubscribe} operation with the {@link VmsLayer} layer and publisher id passed
+ * both as parameter.
+ */
public void unsubscribe(VmsLayer layer, int publisherId) {
recordOp("unsubscribe", "publisherId", publisherId, layer);
}
+ /**
+ * Records {@code startMonitoring} operation.
+ */
public void startMonitoring() {
recordOp("startMonitoring");
}
+ /**
+ * Records {@code stopMonitoring} operation.
+ */
public void stopMonitoring() {
recordOp("stopMonitoring");
}
+ /**
+ * Records {@code setLayerOffering} operation with the {@link VmsLayerOffering} offering
+ * passed as parameter.
+ */
public void setLayersOffering(VmsLayersOffering layersOffering) {
recordOp("setLayersOffering", layersOffering);
}
+ /**
+ * Records {@code getPublisherId} operation with the publisher id passed as parameter.
+ */
public void getPublisherId(int publisherId) {
recordOp("getPublisherId", "publisherId", publisherId);
}
// VMS Service operations.
+ /**
+ * Records {@code addSubscription} operation with the {@link VmsLayer} and the sequenceNumber
+ * passed as parameter.
+ */
public void addSubscription(int sequenceNumber, VmsLayer layer) {
recordOp("addSubscription", "sequenceNumber", sequenceNumber, layer);
}
+ /**
+ * Records {@code addPromiscuousSubscription} operation with the {@link VmsLayer} and the
+ * sequenceNumber passed as parameter.
+ */
public void removeSubscription(int sequenceNumber, VmsLayer layer) {
recordOp("removeSubscription", "sequenceNumber", sequenceNumber, layer);
}
+ /**
+ * Records {@code addPromiscuousSubscription} operation with the sequenceNumber passed as
+ * parameter.
+ */
public void addPromiscuousSubscription(int sequenceNumber) {
recordOp("addPromiscuousSubscription", "sequenceNumber", sequenceNumber);
}
+ /**
+ * Records {@code removePromiscuousSubscription} operation with the sequenceNumber passed as
+ * parameter.
+ */
public void removePromiscuousSubscription(int sequenceNumber) {
recordOp("removePromiscuousSubscription", "sequenceNumber", sequenceNumber);
}
+ /**
+ * Records {@code addHalSubscription} operation with the {@link VmsLayer} layer and
+ * sequenceNumber both passed as parameter.
+ */
public void addHalSubscription(int sequenceNumber, VmsLayer layer) {
recordOp("addHalSubscription", "sequenceNumber", sequenceNumber, layer);
}
+ /**
+ * Records {@code removeHalSubscription} operation with the {@link VmsLayer} layer and
+ * sequenceNumber both passed as parameter.
+ */
public void removeHalSubscription(int sequenceNumber, VmsLayer layer) {
recordOp("removeHalSubscription", "sequenceNumber", sequenceNumber, layer);
}
+ /**
+ * Records {@code setPublisherLayersOffering} operation with the {@link VmsLayersOffering}
+ * layersOffering passed as parameter.
+ */
public void setPublisherLayersOffering(VmsLayersOffering layersOffering) {
recordOp("setPublisherLayersOffering", layersOffering);
}
+ /**
+ * Records {@code setHalPublisherLayersOffering} operation with the {@link VmsLayersOffering}
+ * layersOffering passed as parameter.
+ */
public void setHalPublisherLayersOffering(VmsLayersOffering layersOffering) {
recordOp("setHalPublisherLayersOffering", layersOffering);
}
diff --git a/car-test-lib/Android.bp b/car-test-lib/Android.bp
index 965ea7b..4522982 100644
--- a/car-test-lib/Android.bp
+++ b/car-test-lib/Android.bp
@@ -43,5 +43,6 @@
libs: [
"android.hardware.automotive.vehicle-V2.0-java",
"mockito-target-extended",
+ "compatibility-device-util-axt",
],
}
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 35f0274..c89a337 100644
--- a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
+++ b/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
@@ -31,6 +31,8 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Trace;
import android.os.UserManager;
import android.provider.Settings;
@@ -59,6 +61,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
/**
* Base class for tests that must use {@link com.android.dx.mockito.inline.extended.ExtendedMockito}
@@ -83,11 +86,11 @@
*/
public abstract class AbstractExtendedMockitoTestCase {
- private static final boolean TRACE = false;
-
- private static final boolean VERBOSE = false;
private static final String TAG = AbstractExtendedMockitoTestCase.class.getSimpleName();
+ private static final boolean TRACE = false;
+ private static final boolean VERBOSE = false;
+
private final List<Class<?>> mStaticSpiedClasses = new ArrayList<>();
// Tracks (S)Log.wtf() calls made during code execution, then used on verifyWtfNeverLogged()
@@ -128,6 +131,7 @@
@After
public final void finishSession() {
beginTrace("finishSession()");
+ completeAllHandlerThreadTasks();
if (mSession != null) {
beginTrace("finishMocking()");
mSession.finishMocking();
@@ -139,6 +143,38 @@
}
/**
+ * Waits for completion of all pending Handler tasks for all HandlerThread in the process.
+ *
+ * <p>This can prevent pending Handler tasks of one test from affecting another. This does not
+ * work if the message is posted with delay.
+ */
+ protected void completeAllHandlerThreadTasks() {
+ beginTrace("completeAllHandlerThreadTasks");
+ Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
+ ArrayList<HandlerThread> handlerThreads = new ArrayList<>(threadSet.size());
+ Thread currentThread = Thread.currentThread();
+ for (Thread t : threadSet) {
+ if (t != currentThread && t instanceof HandlerThread) {
+ handlerThreads.add((HandlerThread) t);
+ }
+ }
+ ArrayList<SyncRunnable> syncs = new ArrayList<>(handlerThreads.size());
+ Log.i(TAG, "will wait for " + handlerThreads.size() + " HandlerThreads");
+ for (int i = 0; i < handlerThreads.size(); i++) {
+ Handler handler = new Handler(handlerThreads.get(i).getLooper());
+ SyncRunnable sr = new SyncRunnable(() -> { });
+ handler.post(sr);
+ syncs.add(sr);
+ }
+ beginTrace("waitForComplete");
+ for (int i = 0; i < syncs.size(); i++) {
+ syncs.get(i).waitForComplete();
+ }
+ endTrace(); // waitForComplete
+ endTrace(); // completeAllHandlerThreadTasks
+ }
+
+ /**
* Adds key-value(int) pair in mocked Settings.Global and Settings.Secure
*/
protected void putSettingsInt(@NonNull String key, int value) {
@@ -167,6 +203,13 @@
}
/**
+ * Asserts that the giving settings was not set.
+ */
+ protected void assertSettingsNotSet(String key) {
+ mSettings.assertDoesNotContainsKey(key);
+ }
+
+ /**
* Subclasses can use this method to initialize the Mockito session that's started before every
* test on {@link #startSession()}.
*
@@ -486,6 +529,13 @@
public int getInt(String key) {
return get(key, null, Integer.class);
}
+
+ public void assertDoesNotContainsKey(String key) {
+ if (mSettingsMapping.containsKey(key)) {
+ throw new AssertionError("Should not have key " + key + ", but has: "
+ + mSettingsMapping.get(key));
+ }
+ }
}
/**
@@ -496,4 +546,33 @@
@Target({METHOD})
public static @interface ExpectWtf {
}
+
+ private static final class SyncRunnable implements Runnable {
+ private final Runnable mTarget;
+ private volatile boolean mComplete = false;
+
+ private SyncRunnable(Runnable target) {
+ mTarget = target;
+ }
+
+ @Override
+ public void run() {
+ mTarget.run();
+ synchronized (this) {
+ mComplete = true;
+ notifyAll();
+ }
+ }
+
+ private void waitForComplete() {
+ synchronized (this) {
+ while (!mComplete) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ }
}
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 d05b013..20925e8 100644
--- a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
@@ -21,9 +21,11 @@
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.car.test.util.UserTestingHelper;
+import android.car.test.util.UserTestingHelper.UserInfoBuilder;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -78,7 +80,7 @@
}
/**
- * Mocks {@code UserManager.getUserInfo(userId)} to return a {@link UserInfo} with the given
+ * Mocks {@code UserManager#getUserInfo(userId)} to return a {@link UserInfo} with the given
* {@code flags}.
*/
@NonNull
@@ -99,7 +101,7 @@
}
/**
- * Mocks {@code UserManager.getUserInfo(userId)} when the {@code userId} is the system user's.
+ * Mocks {@code UserManager#getUserInfo(userId)} when the {@code userId} is the system user's.
*/
@NonNull
public static void mockUmGetSystemUser(@NonNull UserManager um) {
@@ -109,7 +111,7 @@
}
/**
- * Mocks {@code UserManager.getUsers(excludeDying)} to return the given users.
+ * Mocks {@code UserManager#getUsers(excludeDying)} to return the given users.
*/
public static void mockUmGetUsers(@NonNull UserManager um, @NonNull UserInfo... users) {
Objects.requireNonNull(um);
@@ -118,7 +120,7 @@
}
/**
- * Mocks {@code UserManager.getUsers(excludeDying)} to return simple users with the given ids.
+ * Mocks {@code UserManager#getUsers(excludeDying)} to return simple users with the given ids.
*/
public static void mockUmGetUsers(@NonNull UserManager um, @NonNull @UserIdInt int... userIds) {
List<UserInfo> users = UserTestingHelper.newUsers(userIds);
@@ -126,14 +128,22 @@
}
/**
- * Mocks a call to {@code UserManager.getUsers()}.
+ * Mocks a call to {@code UserManager#getUsers()}, which includes dying users.
*/
- public static void mockUmGetUsers(@NonNull UserManager um, @NonNull List<UserInfo> userInfos) {
+ public static void mockUmGetAllUsers(@NonNull UserManager um,
+ @NonNull List<UserInfo> userInfos) {
when(um.getUsers()).thenReturn(userInfos);
}
/**
- * Mocks a call to {@code UserManager.isUserRunning(userId)}.
+ * Mocks a call to {@code UserManager#getUsers(true)}, which excludes dying users.
+ */
+ public static void mockUmGetUsers(@NonNull UserManager um, @NonNull List<UserInfo> userInfos) {
+ when(um.getUsers(/* excludeDying= */ true)).thenReturn(userInfos);
+ }
+
+ /**
+ * Mocks a call to {@code UserManager#isUserRunning(userId)}.
*/
public static void mockUmIsUserRunning(@NonNull UserManager um, @UserIdInt int userId,
boolean isRunning) {
@@ -141,6 +151,32 @@
}
/**
+ * Mocks a successful call to {@code UserManager#createUser(String, String, int)}, returning
+ * a user with the passed arguments.
+ */
+ @NonNull
+ public static UserInfo mockUmCreateUser(@NonNull UserManager um, @Nullable String name,
+ @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId) {
+ UserInfo userInfo = new UserInfoBuilder(userId)
+ .setName(name)
+ .setType(userType)
+ .setFlags(flags)
+ .build();
+ when(um.createUser(name, userType, flags)).thenReturn(userInfo);
+ return userInfo;
+ }
+
+ /**
+ * Mocks a call to {@code UserManager#createUser(String, String, int)} that throws the given
+ * runtime exception.
+ */
+ @NonNull
+ public static void mockUmCreateUser(@NonNull UserManager um, @Nullable String name,
+ @NonNull String userType, @UserInfoFlag int flags, @NonNull RuntimeException e) {
+ when(um.createUser(name, userType, flags)).thenThrow(e);
+ }
+
+ /**
* Mocks a call to {@link ServiceManager#getService(name)}.
*
* <p><b>Note: </b>it must be made inside a
diff --git a/car-test-lib/src/android/car/test/util/UserTestingHelper.java b/car-test-lib/src/android/car/test/util/UserTestingHelper.java
index 92a612c..d1c2f13 100644
--- a/car-test-lib/src/android/car/test/util/UserTestingHelper.java
+++ b/car-test-lib/src/android/car/test/util/UserTestingHelper.java
@@ -15,6 +15,8 @@
*/
package android.car.test.util;
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -84,6 +86,27 @@
}
/**
+ * Sets the property that defines the maximum number of uses allowed in the device.
+ */
+ public static void setMaxSupportedUsers(int max) {
+ runShellCommand("setprop fw.max_users %d", max);
+ }
+
+ /**
+ * Configures the user to use PIN credentials.
+ */
+ public static void setUserLockCredentials(@UserIdInt int userId, int pin) {
+ runShellCommand("locksettings set-pin %s --user %d ", pin, userId);
+ }
+
+ /**
+ * Clears the user credentials using current PIN.
+ */
+ public static void clearUserLockCredentials(@UserIdInt int userId, int pin) {
+ runShellCommand("locksettings clear --old %d --user %d ", pin, userId);
+ }
+
+ /**
* Builder for {@link UserInfo} objects.
*/
public static final class UserInfoBuilder {
diff --git a/car-usb-handler/res/values-ur/strings.xml b/car-usb-handler/res/values-ur/strings.xml
index 0fe5553..109dd4c 100644
--- a/car-usb-handler/res/values-ur/strings.xml
+++ b/car-usb-handler/res/values-ur/strings.xml
@@ -19,9 +19,7 @@
<string name="app_name" msgid="6963366455471441257">"USB ہینڈلر"</string>
<string name="usb_saved_devices" msgid="2829442070749964872">"محفوظ کردہ آلات"</string>
<string name="usb_pref_delete_title" msgid="3885061814853467483">"USB آلہ کو ہینڈل کرنے والی اپپ کو ہٹائیں"</string>
- <!-- String.format failed for translation -->
- <!-- no translation found for usb_pref_delete_message (5849493572520646218) -->
- <skip />
+ <string name="usb_pref_delete_message" msgid="5849493572520646218">"کیا آپ واقعی %1$s اپپ کی ڈیفالٹ ہینڈلنگ کو حذف کرنا چاہتے ہیں؟"</string>
<string name="usb_pref_delete_yes" msgid="7803356145103146036">"ہاں"</string>
<string name="usb_pref_delete_cancel" msgid="5999791462730255929">"منسوخ کریں"</string>
<string name="usb_resolving_handlers" msgid="1943100136172948686">"تعاون یافتہ ہینڈلرز حاصل کر رہے ہیں"</string>
diff --git a/car-usb-handler/src/android/car/usb/handler/AoapInterface.java b/car-usb-handler/src/android/car/usb/handler/AoapInterface.java
index 21aa64e..3b7fbfe 100644
--- a/car-usb-handler/src/android/car/usb/handler/AoapInterface.java
+++ b/car-usb-handler/src/android/car/usb/handler/AoapInterface.java
@@ -22,6 +22,10 @@
import android.util.Pair;
import java.io.IOException;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.HashSet;
import java.util.Set;
@@ -97,7 +101,7 @@
/**
* Accessory write timeout.
*/
- public static final int AOAP_TIMEOUT_MS = 2000;
+ public static final int AOAP_TIMEOUT_MS = 50;
/**
* Set of VID:PID pairs blacklisted through config_AoapIncompatibleDeviceIds. Only
@@ -107,14 +111,30 @@
private static final String TAG = AoapInterface.class.getSimpleName();
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ ElementType.FIELD, ElementType.PARAMETER })
+ public @interface Direction {}
+
+ @Direction
+ public static final int WRITE = 1;
+ @Direction
+ public static final int READ = 2;
+
+
public static int getProtocol(UsbDeviceConnection conn) {
byte[] buffer = new byte[2];
- int len = conn.controlTransfer(
- UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR,
- ACCESSORY_GET_PROTOCOL, 0, 0, buffer, 2, AOAP_TIMEOUT_MS);
- if (len != 2) {
+
+ int len = transfer(conn, READ, ACCESSORY_GET_PROTOCOL, 0, buffer, buffer.length);
+ if (len == 0) {
return -1;
}
+ if (len < 0) {
+ Log.w(TAG, "getProtocol() failed. Retrying...");
+ len = transfer(conn, READ, ACCESSORY_GET_PROTOCOL, 0, buffer, buffer.length);
+ if (len != buffer.length) {
+ return -1;
+ }
+ }
return (buffer[1] << 8) | buffer[0];
}
@@ -125,21 +145,22 @@
public static void sendString(UsbDeviceConnection conn, int index, String string)
throws IOException {
byte[] buffer = (string + "\0").getBytes();
- int len = conn.controlTransfer(
- UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
- ACCESSORY_SEND_STRING, 0, index,
- buffer, buffer.length, AOAP_TIMEOUT_MS);
+ int len = transfer(conn, WRITE, ACCESSORY_SEND_STRING, index, buffer,
+ buffer.length);
if (len != buffer.length) {
- throw new IOException("Failed to send string " + index + ": \"" + string + "\"");
+ Log.w(TAG, "sendString for " + index + ":" + string + " failed. Retrying...");
+ len = transfer(conn, WRITE, ACCESSORY_SEND_STRING, index, buffer,
+ buffer.length);
+ if (len != buffer.length) {
+ throw new IOException("Failed to send string " + index + ": \"" + string + "\"");
+ }
} else {
Log.i(TAG, "Sent string " + index + ": \"" + string + "\"");
}
}
public static void sendAoapStart(UsbDeviceConnection conn) throws IOException {
- int len = conn.controlTransfer(
- UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
- ACCESSORY_START, 0, 0, null, 0, AOAP_TIMEOUT_MS);
+ int len = transfer(conn, WRITE, ACCESSORY_START, 0, null, 0);
if (len < 0) {
throw new IOException("Control transfer for accessory start failed: " + len);
}
@@ -181,4 +202,22 @@
return vid == USB_ACCESSORY_VENDOR_ID
&& USB_ACCESSORY_MODE_PRODUCT_ID.contains(pid);
}
+
+ private static int transfer(UsbDeviceConnection conn, @Direction int direction, int string,
+ int index, byte[] buffer, int length) {
+ int directionConstant;
+ switch (direction) {
+ case READ:
+ directionConstant = UsbConstants.USB_DIR_IN;
+ break;
+ case WRITE:
+ directionConstant = UsbConstants.USB_DIR_OUT;
+ break;
+ default:
+ Log.w(TAG, "Unknown direction for transfer: " + direction);
+ return -1;
+ }
+ return conn.controlTransfer(directionConstant | UsbConstants.USB_TYPE_VENDOR, string, 0,
+ index, buffer, length, AOAP_TIMEOUT_MS);
+ }
}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java b/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
index 8b2f425..a1be7dd 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
@@ -71,18 +71,16 @@
private final PackageManager mPackageManager;
private final UsbDeviceHandlerResolverCallback mDeviceCallback;
private final Context mContext;
- private final HandlerThread mHandlerThread;
- private final UsbDeviceResolverHandler mHandler;
private final AoapServiceManager mAoapServiceManager;
+ private HandlerThread mHandlerThread;
+ private UsbDeviceResolverHandler mHandler;
public UsbDeviceHandlerResolver(UsbManager manager, Context context,
UsbDeviceHandlerResolverCallback deviceListener) {
mUsbManager = manager;
mContext = context;
mDeviceCallback = deviceListener;
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
- mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
+ createHandlerThread();
mPackageManager = context.getPackageManager();
mAoapServiceManager = new AoapServiceManager(mContext.getApplicationContext());
}
@@ -104,9 +102,22 @@
}
/**
+ * Listener for failed {@code startAosp} command.
+ *
+ * <p>If {@code startAosp} fails, the device could be left in a inconsistent state, that's why
+ * we go back to USB enumeration, instead of just repeating the command.
+ */
+ public interface StartAoapFailureListener {
+
+ /** Called if startAoap fails. */
+ void onFailure();
+ }
+
+ /**
* Dispatches device to component.
*/
- public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap) {
+ public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap,
+ StartAoapFailureListener failureListener) {
if (LOCAL_LOGD) {
Log.d(TAG, "dispatch: " + device + " component: " + component + " inAoap: " + inAoap);
}
@@ -128,10 +139,20 @@
packageMatches(activityInfo, intent.getAction(), device, true);
if (filter != null) {
+ if (!mHandlerThread.isAlive()) {
+ // Start a new thread. Used only when startAoap fails, and we need to
+ // re-enumerate device in order to try again.
+ createHandlerThread();
+ }
mHandlerThread.getThreadHandler().post(() -> {
if (mAoapServiceManager.canSwitchDeviceToAoap(device,
ComponentName.unflattenFromString(filter.mAoapService))) {
- requestAoapSwitch(device, filter);
+ try {
+ requestAoapSwitch(device, filter);
+ } catch (IOException e) {
+ Log.w(TAG, "Start AOAP command failed:" + e);
+ failureListener.onFailure();
+ }
} else {
Log.i(TAG, "Ignore AOAP switch for device " + device
+ " handled by " + filter.mAoapService);
@@ -151,6 +172,12 @@
return true;
}
+ private void createHandlerThread() {
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
+ }
+
private static Intent createDeviceAttachedIntent(UsbDevice device) {
Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
@@ -192,7 +219,7 @@
return settings;
}
- private void requestAoapSwitch(UsbDevice device, UsbDeviceFilter filter) {
+ private void requestAoapSwitch(UsbDevice device, UsbDeviceFilter filter) throws IOException {
UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, device);
if (connection == null) {
Log.e(TAG, "Failed to connect to usb device.");
@@ -209,11 +236,9 @@
filter.mAoapVersion,
filter.mAoapUri,
hashedSerial);
- } catch (IOException e) {
- Log.w(TAG, "Failed to switch device into AOAP mode", e);
+ } finally {
+ connection.close();
}
-
- connection.close();
}
private String getHashed(String serial) {
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
index 3fcb67b..0da71ac 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
@@ -30,6 +30,7 @@
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
/**
@@ -300,6 +301,12 @@
private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
+ // Used to get the device that we are trying to connect to, if mActiveDevice is removed and
+ // startAoap fails afterwards. Used during USB enumeration when retrying to startAoap when
+ // there are multiple devices attached.
+ private int mLastDeviceId = 0;
+ private int mStartAoapRetries = 1;
+
private UsbHostControllerHandler(Looper looper) {
super(looper);
}
@@ -308,6 +315,24 @@
sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
}
+ private void onFailure() {
+ if (mStartAoapRetries == 0) {
+ Log.w(TAG, "Reached maximum retry count for startAoap. Giving up Aoa handshake.");
+ return;
+ }
+ mStartAoapRetries--;
+
+ Log.d(TAG, "Restarting USB enumeration.");
+ Iterator<UsbDevice> deviceIterator = mUsbManager.getDeviceList().values().iterator();
+ while (deviceIterator.hasNext()) {
+ UsbDevice device = deviceIterator.next();
+ if (mLastDeviceId == device.getDeviceId()) {
+ processDevice(device);
+ return;
+ }
+ }
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -318,8 +343,10 @@
UsbHostControllerHandlerDispatchData data =
(UsbHostControllerHandlerDispatchData) msg.obj;
UsbDevice device = data.getUsbDevice();
+ mLastDeviceId = device.getDeviceId();
UsbDeviceSettings settings = data.getUsbDeviceSettings();
- if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.getAoap())) {
+ if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.getAoap(),
+ this::onFailure)) {
if (data.mRetries > 0) {
--data.mRetries;
Message nextMessage = Message.obtain(msg);
diff --git a/car_product/build/preinstalled-packages-product-car-base.xml b/car_product/build/preinstalled-packages-product-car-base.xml
index e888eb5..9587c83 100644
--- a/car_product/build/preinstalled-packages-product-car-base.xml
+++ b/car_product/build/preinstalled-packages-product-car-base.xml
@@ -166,6 +166,12 @@
<install-in user-type="SYSTEM" />
</install-in-user-type>
+ <!-- Failed to pass CTS if not installed for system user -->
+ <install-in-user-type package="com.android.car.ui.sharedlibrary">
+ <install-in user-type="FULL" />
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+
<!--
Apps that do need to run on SYSTEM and evaluated by package owner.
Here the apps will have FULL only.
@@ -260,9 +266,6 @@
<install-in-user-type package="com.android.car.calendar">
<install-in user-type="FULL" />
</install-in-user-type>
- <install-in-user-type package="com.android.car.ui.sharedlibrary">
- <install-in user-type="FULL" />
- </install-in-user-type>
<install-in-user-type package="com.android.wifi.inprocess.overlay.car">
<install-in user-type="FULL" />
</install-in-user-type>
diff --git a/car_product/sepolicy/private/carservice_app.te b/car_product/sepolicy/private/carservice_app.te
index d62cb27..4841891 100644
--- a/car_product/sepolicy/private/carservice_app.te
+++ b/car_product/sepolicy/private/carservice_app.te
@@ -76,3 +76,9 @@
get_prop(carservice_app, vehicle_hal_prop)
carwatchdog_client_domain(carservice_app)
+
+# For ActivityBlockingActiviy
+allow carservice_app gpu_device:chr_file rw_file_perms;
+allow carservice_app gpu_device:dir r_dir_perms;
+allow carservice_app gpu_service:service_manager find;
+binder_call(carservice_app, gpuservice)
diff --git a/evs/apps/default/ConfigManager.h b/evs/apps/default/ConfigManager.h
index 9c6d1a2..7c2186d 100644
--- a/evs/apps/default/ConfigManager.h
+++ b/evs/apps/default/ConfigManager.h
@@ -16,8 +16,9 @@
#ifndef CONFIG_MANAGER_H
#define CONFIG_MANAGER_H
-#include <vector>
+#include <cerrno>
#include <string>
+#include <vector>
#include <system/graphics-base.h>
@@ -78,16 +79,27 @@
const std::vector<CameraInfo>& getCameras() const { return mCameras; };
- bool setActiveDisplayId(int displayId) {
- if (displayId < 0 or displayId > mDisplays.size()) {
- printf("Display %d is invalid. Current active display is display %d.",
- displayId, mActiveDisplayId);
- return false;
+ int setActiveDisplayId(int displayId) {
+ if (displayId == -1) {
+ // -1 is reserved for the default display, which is the first
+ // display in config.json's display list
+ printf("Uses a display with id %d", mDisplays[0].port);
+ mActiveDisplayId = mDisplays[0].port;
+ return mActiveDisplayId;
+ } else if (displayId < 0) {
+ printf("Display %d is invalid.", displayId);
+ return -ENOENT;
+ } else {
+ for (auto display : mDisplays) {
+ if (display.port == displayId) {
+ mActiveDisplayId = displayId;
+ return mActiveDisplayId;
+ }
+ }
+
+ printf("Display %d does not exist.", displayId);
+ return -ENOENT;
}
-
- mActiveDisplayId = displayId;
-
- return true;
}
const std::vector<DisplayInfo>& getDisplays() const { return mDisplays; };
const DisplayInfo& getActiveDisplay() const { return mDisplays[mActiveDisplayId]; };
diff --git a/evs/apps/default/config.json.readme b/evs/apps/default/config.json.readme
index 7d1ec9d..561adcc 100644
--- a/evs/apps/default/config.json.readme
+++ b/evs/apps/default/config.json.readme
@@ -19,7 +19,7 @@
"rearExtent" : 40 // The extent of the car body behind the read axel
},
"displays" : [ // This configures the dimensions of the surround view display
- {
+ { // The first display will be used as the default display
"displayPort" : 1 // Display port number, the target display is connected to.
"frontRange" : 100, // How far to render the view in front of the front bumper
"rearRange" : 100 // How far the view extends behind the rear bumper
diff --git a/evs/apps/default/evs_app.cpp b/evs/apps/default/evs_app.cpp
index 9f2f2c8..b30bcc7 100644
--- a/evs/apps/default/evs_app.cpp
+++ b/evs/apps/default/evs_app.cpp
@@ -94,7 +94,7 @@
bool useVehicleHal = true;
bool printHelp = false;
const char* evsServiceName = "default";
- int displayId = 1;
+ int displayId = -1;
bool useExternalMemory = false;
android_pixel_format_t extMemoryFormat = HAL_PIXEL_FORMAT_RGBA_8888;
for (int i=1; i< argc; i++) {
@@ -133,7 +133,8 @@
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(" --display\n\tSpecify the display to use. If this is not set, the first"
+ "display in config.json's list will be used.\n");
printf(" --extmem <format>\n\t"
"Application allocates buffers to capture camera frames. "
"Available format strings are (case insensitive):\n");
@@ -153,7 +154,7 @@
ConfigManager config;
if (!config.initialize("/system/etc/automotive/evs/config.json")) {
LOG(ERROR) << "Missing or improper configuration for the EVS application. Exiting.";
- return 1;
+ return EXIT_FAILURE;
}
// Set thread pool size to one to avoid concurrent events from the HAL.
@@ -171,19 +172,25 @@
if (pEvs.get() == nullptr) {
LOG(ERROR) << "getService(" << evsServiceName
<< ") returned NULL. Exiting.";
- return 1;
+ return EXIT_FAILURE;
}
// Request exclusive access to the EVS display
LOG(INFO) << "Acquiring EVS Display";
// We'll use an available display device.
+ displayId = config.setActiveDisplayId(displayId);
+ if (displayId < 0) {
+ PLOG(ERROR) << "EVS Display is unknown. Exiting.";
+ return EXIT_FAILURE;
+ }
+
android::sp<IEvsDisplay> pDisplay = pEvs->openDisplay_1_1(displayId);
if (pDisplay.get() == nullptr) {
LOG(ERROR) << "EVS Display unavailable. Exiting.";
- return 1;
+ return EXIT_FAILURE;
}
- config.setActiveDisplayId(displayId);
+
config.useExternalMemory(useExternalMemory);
config.setExternalMemoryFormat(extMemoryFormat);
@@ -194,13 +201,13 @@
pVnet = IVehicle::getService();
if (pVnet.get() == nullptr) {
LOG(ERROR) << "Vehicle HAL getService returned NULL. Exiting.";
- return 1;
+ return EXIT_FAILURE;
} else {
// Register for vehicle state change callbacks we care about
// Changes in these values are what will trigger a reconfiguration of the EVS pipeline
if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::GEAR_SELECTION)) {
LOG(ERROR) << "Without gear notification, we can't support EVS. Exiting.";
- return 1;
+ return EXIT_FAILURE;
}
if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::TURN_SIGNAL_STATE)) {
LOG(WARNING) << "Didn't get turn signal notifications, so we'll ignore those.";
@@ -215,7 +222,7 @@
EvsStateControl *pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config);
if (!pStateController->startUpdateLoop()) {
LOG(ERROR) << "Initial configuration failed. Exiting.";
- return 1;
+ return EXIT_FAILURE;
}
// Run forever, reacting to events as necessary
@@ -226,5 +233,5 @@
// One known example is if another process preempts our registration for our service name.
LOG(ERROR) << "EVS Listener stopped. Exiting.";
- return 0;
+ return EXIT_SUCCESS;
}
diff --git a/evs/manager/1.1/HalCamera.cpp b/evs/manager/1.1/HalCamera.cpp
index a90e83b..6e6f77d 100644
--- a/evs/manager/1.1/HalCamera.cpp
+++ b/evs/manager/1.1/HalCamera.cpp
@@ -92,6 +92,10 @@
// Add this virtualCamera to our ownership list via weak pointer
mClients.emplace_back(virtualCamera);
+
+ // Update statistics
+ mUsageStats->updateNumClients(mClients.size());
+
return true;
}
@@ -114,6 +118,9 @@
if (!changeFramesInFlight(0)) {
LOG(ERROR) << "Error when trying to reduce the in flight buffer count";
}
+
+ // Update statistics
+ mUsageStats->updateNumClients(mClients.size());
}
@@ -352,7 +359,7 @@
mHwCamera->doneWithFrame_1_1(returnedBuffers);
// Counts a returned buffer
- mUsageStats->framesReturned();
+ mUsageStats->framesReturned(returnedBuffers);
}
}
@@ -412,7 +419,7 @@
}
// Reports the number of received buffers
- mUsageStats->framesReceived(buffer.size());
+ mUsageStats->framesReceived(buffer);
// Frames are being forwarded to active v1.0 clients and v1.1 clients if we
// failed to create a timeline.
@@ -437,7 +444,7 @@
mHwCamera->doneWithFrame_1_1(buffer);
// Reports a returned buffer
- mUsageStats->framesReturned();
+ mUsageStats->framesReturned(buffer);
} else {
// Add an entry for this frame in our tracking list.
unsigned i;
diff --git a/evs/manager/1.1/stats/CameraUsageStats.cpp b/evs/manager/1.1/stats/CameraUsageStats.cpp
index 11f8229..593577e 100644
--- a/evs/manager/1.1/stats/CameraUsageStats.cpp
+++ b/evs/manager/1.1/stats/CameraUsageStats.cpp
@@ -18,6 +18,15 @@
#include <statslog.h>
+#include <android-base/logging.h>
+
+namespace {
+
+ // Length of frame roundtrip history
+ const int kMaxHistoryLength = 100;
+
+}
+
namespace android {
namespace automotive {
namespace evs {
@@ -26,6 +35,50 @@
using ::android::base::Result;
using ::android::base::StringAppendF;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::automotive::evs::V1_1::BufferDesc;
+
+void CameraUsageStats::updateFrameStatsOnArrival(
+ const hidl_vec<BufferDesc>& bufs) {
+ const auto now = android::uptimeMillis();
+ for (const auto& b : bufs) {
+ auto it = mBufferHistory.find(b.bufferId);
+ if (it == mBufferHistory.end()) {
+ mBufferHistory.emplace(b.bufferId, now);
+ } else {
+ it->second.timestamp = now;
+ }
+ }
+}
+
+
+void CameraUsageStats::updateFrameStatsOnReturn(
+ const hidl_vec<BufferDesc>& bufs) {
+ const auto now = android::uptimeMillis();
+ for (auto& b : bufs) {
+ auto it = mBufferHistory.find(b.bufferId);
+ if (it == mBufferHistory.end()) {
+ LOG(WARNING) << "Buffer " << b.bufferId << " from "
+ << b.deviceId << " is unknown.";
+ } else {
+ const auto roundtrip = now - it->second.timestamp;
+ it->second.history.emplace(roundtrip);
+ it->second.sum += roundtrip;
+ if (it->second.history.size() > kMaxHistoryLength) {
+ it->second.sum -= it->second.history.front();
+ it->second.history.pop();
+ }
+
+ if (roundtrip > it->second.peak) {
+ it->second.peak = roundtrip;
+ }
+
+ if (mStats.framesFirstRoundtripLatency == 0) {
+ mStats.framesFirstRoundtripLatency = roundtrip;
+ }
+ }
+ }
+}
void CameraUsageStats::framesReceived(int n) {
@@ -34,12 +87,30 @@
}
+void CameraUsageStats::framesReceived(
+ const hidl_vec<BufferDesc>& bufs) {
+ AutoMutex lock(mMutex);
+ mStats.framesReceived += bufs.size();
+
+ updateFrameStatsOnArrival(bufs);
+}
+
+
void CameraUsageStats::framesReturned(int n) {
AutoMutex lock(mMutex);
mStats.framesReturned += n;
}
+void CameraUsageStats::framesReturned(
+ const hidl_vec<BufferDesc>& bufs) {
+ AutoMutex lock(mMutex);
+ mStats.framesReturned += bufs.size();
+
+ updateFrameStatsOnReturn(bufs);
+}
+
+
void CameraUsageStats::framesIgnored(int n) {
AutoMutex lock(mMutex);
mStats.framesIgnored += n;
@@ -58,6 +129,14 @@
}
+void CameraUsageStats::updateNumClients(size_t n) {
+ AutoMutex lock(mMutex);
+ if (n > mStats.peakClientsCount) {
+ mStats.peakClientsCount = n;
+ }
+}
+
+
int64_t CameraUsageStats::getTimeCreated() const {
AutoMutex lock(mMutex);
return mTimeCreatedMs;
@@ -76,16 +155,33 @@
}
-CameraUsageStatsRecord CameraUsageStats::snapshot() const {
+CameraUsageStatsRecord CameraUsageStats::snapshot() {
AutoMutex lock(mMutex);
+
+ int32_t sum = 0;
+ int32_t peak = 0;
+ int32_t len = 0;
+ for (auto& [id, rec] : mBufferHistory) {
+ sum += rec.sum;
+ len += rec.history.size();
+ if (peak < rec.peak) {
+ peak = rec.peak;
+ }
+ }
+
+ mStats.framesPeakRoundtripLatency = peak;
+ mStats.framesAvgRoundtripLatency = (double)sum / len;
return mStats;
}
Result<void> CameraUsageStats::writeStats() const {
AutoMutex lock(mMutex);
+
+ // Reports the usage statistics before the destruction
+ // EvsUsageStatsReported atom is defined in
+ // frameworks/base/cmds/statsd/src/atoms.proto
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,
diff --git a/evs/manager/1.1/stats/CameraUsageStats.h b/evs/manager/1.1/stats/CameraUsageStats.h
index 4bc4ea8..7a7224d 100644
--- a/evs/manager/1.1/stats/CameraUsageStats.h
+++ b/evs/manager/1.1/stats/CameraUsageStats.h
@@ -17,8 +17,12 @@
#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAUSAGESTATS_H
#define ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAUSAGESTATS_H
+#include <queue>
+#include <unordered_map>
+
#include <inttypes.h>
+#include <android/hardware/automotive/evs/1.1/types.h>
#include <android-base/result.h>
#include <android-base/stringprintf.h>
#include <utils/Mutex.h>
@@ -89,18 +93,46 @@
"%sFrames Received: %" PRId64 "\n"
"%sFrames Returned: %" PRId64 "\n"
"%sFrames Ignored : %" PRId64 "\n"
- "%sFrames Skipped To Sync: %" PRId64 "\n\n",
+ "%sFrames Skipped To Sync: %" PRId64 "\n"
+ "%sFrames First Roundtrip: %" PRId64 "\n"
+ "%sFrames Peak Roundtrip: %" PRId64 "\n"
+ "%sFrames Average Roundtrip: %f\n"
+ "%sPeak Number of Clients: %" PRId32 "\n\n",
indent, ns2ms(timestamp),
indent, framesReceived,
indent, framesReturned,
indent, framesIgnored,
- indent, framesSkippedToSync);
+ indent, framesSkippedToSync,
+ indent, framesFirstRoundtripLatency,
+ indent, framesPeakRoundtripLatency,
+ indent, framesAvgRoundtripLatency,
+ indent, peakClientsCount);
return buffer;
}
};
+struct BufferRecord {
+ BufferRecord(int64_t timestamp) :
+ timestamp(timestamp),
+ sum(0),
+ peak(0) {}
+
+ // Recent processing time
+ std::queue<int32_t> history;
+
+ // Timestamp on the buffer arrival
+ int64_t timestamp;
+
+ // Sum of processing times
+ int64_t sum;
+
+ // Peak processing time
+ int64_t peak;
+};
+
+
class CameraUsageStats : public RefBase {
public:
CameraUsageStats(int32_t id)
@@ -122,18 +154,34 @@
// Usage statistics to collect
CameraUsageStatsRecord mStats GUARDED_BY(mMutex);
+ // Frame buffer histories
+ std::unordered_map<int, BufferRecord> mBufferHistory GUARDED_BY(mMutex);
+
public:
void framesReceived(int n = 1) EXCLUDES(mMutex);
void framesReturned(int n = 1) EXCLUDES(mMutex);
+ void framesReceived(
+ const hardware::hidl_vec<::android::hardware::automotive::evs::V1_1::BufferDesc>& bufs
+ ) EXCLUDES(mMutex);
+ void framesReturned(
+ const hardware::hidl_vec<::android::hardware::automotive::evs::V1_1::BufferDesc>& bufs
+ ) 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);
+ void updateNumClients(size_t n) EXCLUDES(mMutex);
+ void updateFrameStatsOnArrival(
+ const hardware::hidl_vec<::android::hardware::automotive::evs::V1_1::BufferDesc>& bufs
+ ) REQUIRES(mMutex);
+ void updateFrameStatsOnReturn(
+ const hardware::hidl_vec<::android::hardware::automotive::evs::V1_1::BufferDesc>& bufs
+ ) REQUIRES(mMutex);
// Returns the statistics collected so far
- CameraUsageStatsRecord snapshot() const EXCLUDES(mMutex);
+ CameraUsageStatsRecord snapshot() EXCLUDES(mMutex);
// Reports the usage statistics
android::base::Result<void> writeStats() const EXCLUDES(mMutex);
diff --git a/evs/manager/1.1/stats/StatsCollector.cpp b/evs/manager/1.1/stats/StatsCollector.cpp
index de85e5a..b57f928 100644
--- a/evs/manager/1.1/stats/StatsCollector.cpp
+++ b/evs/manager/1.1/stats/StatsCollector.cpp
@@ -205,11 +205,6 @@
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));
@@ -227,6 +222,11 @@
}
});
+ auto ret = pthread_setname_np(mCollectionThread.native_handle(), "EvsUsageCollect");
+ if (ret != 0) {
+ PLOG(WARNING) << "Failed to name a collection thread";
+ }
+
return {};
}
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 0193408..e40ac97 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -44,6 +44,11 @@
<!-- Service responsible for displaying information on the car instrument cluster. -->
<string name="instrumentClusterRendererService" translatable="false">android.car.cluster/.ClusterRenderingService</string>
+ <!-- Service responsible for handling the rotary controller input. This service will start
+ on boot or on user switch. Set this string to empty if you don't want to start this
+ service. -->
+ <string name="rotaryService" translatable="false">com.android.car.rotary/com.android.car.rotary.RotaryService</string>
+
<!-- Whether to enable Activity blocking for safety. When Activity blocking is enabled,
only whitelisted safe Activities will be allowed while car is not parked. -->
<bool name="enableActivityBlockingForSafety">true</bool>
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
index d97bec8..cc1a530 100644
--- a/service/src/com/android/car/AppFocusService.java
+++ b/service/src/com/android/car/AppFocusService.java
@@ -161,7 +161,7 @@
ownerInfo.binderInterface, appType);
if (DBG) {
Log.i(CarLog.TAG_APP_FOCUS, "losing app type "
- + appType + "," + ownerInfo.toString());
+ + appType + "," + ownerInfo);
}
}
updateFocusOwner(appType, info);
@@ -169,20 +169,20 @@
info.addOwnedAppType(appType);
mDispatchHandler.requestAppFocusOwnershipGrantDispatch(
info.binderInterface, appType);
- if (mActiveAppTypes.add(appType)) {
- if (DBG) {
- Log.i(CarLog.TAG_APP_FOCUS, "adding active app type " + appType + ","
- + info.toString());
- }
- for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
- mAllChangeClients.getInterfaces()) {
- ClientInfo clientInfo = (ClientInfo) client;
- // dispatch events only when there is change after filter and the listener
- // is not coming from the current caller.
- if (clientInfo.getAppTypes().contains(appType)) {
- mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
- appType, true);
- }
+ mActiveAppTypes.add(appType);
+ if (DBG) {
+ Log.i(CarLog.TAG_APP_FOCUS, "updating active app type " + appType + ","
+ + info);
+ }
+ // Always dispatch.
+ for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
+ mAllChangeClients.getInterfaces()) {
+ ClientInfo clientInfo = (ClientInfo) client;
+ // dispatch events only when there is change after filter and the listener
+ // is not coming from the current caller.
+ if (clientInfo.getAppTypes().contains(appType)) {
+ mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
+ appType, true);
}
}
}
@@ -212,8 +212,7 @@
mActiveAppTypes.remove(appType);
info.removeOwnedAppType(appType);
if (DBG) {
- Log.i(CarLog.TAG_APP_FOCUS, "abandoning focus " + appType
- + "," + info.toString());
+ Log.i(CarLog.TAG_APP_FOCUS, "abandoning focus " + appType + "," + info);
}
for (FocusOwnershipCallback ownershipCallback : mFocusOwnershipCallbacks) {
ownershipCallback.onFocusAbandoned(appType, info.mUid, info.mPid);
@@ -270,7 +269,7 @@
for (BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> client :
mAllOwnershipClients.getInterfaces()) {
OwnershipClientInfo clientInfo = (OwnershipClientInfo) client;
- writer.println(clientInfo.toString());
+ writer.println(clientInfo);
}
}
}
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 65c616f..222e760 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -19,6 +19,7 @@
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_PUSH_TO_TALK;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -32,6 +33,8 @@
import android.car.input.ICarInputCallback;
import android.car.input.ICarInputListener;
import android.car.input.RotaryEvent;
+import android.car.user.CarUserManager;
+import android.car.userlib.UserHelper;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -58,6 +61,7 @@
import android.view.ViewConfiguration;
import com.android.car.hal.InputHalService;
+import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
@@ -159,6 +163,7 @@
private final Context mContext;
private final InputHalService mInputHalService;
+ private final CarUserService mUserService;
private final TelecomManager mTelecomManager;
private final AssistUtils mAssistUtils;
// The ComponentName of the CarInputListener service. Can be changed via resource overlay,
@@ -175,6 +180,8 @@
// from Settings.Secure for the current user, falling back to the system-wide default
// long-press delay defined in ViewConfiguration. May be overridden for testing.
private final IntSupplier mLongPressDelaySupplier;
+ // ComponentName of the RotaryService.
+ private final String mRotaryServiceComponentName;
private final Object mLock = new Object();
@@ -270,6 +277,13 @@
}
};
+ private final CarUserManager.UserLifecycleListener mUserLifecycleListener = event -> {
+ Log.d(CarLog.TAG_INPUT, "CarInputService.onEvent(" + event + ")");
+ if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
+ updateRotaryServiceSettings(event.getUserId());
+ }
+ };
+
@Nullable
private static ComponentName getDefaultInputComponent(Context context) {
String carInputService = context.getString(R.string.inputService);
@@ -285,8 +299,9 @@
UserHandle.USER_CURRENT);
}
- public CarInputService(Context context, InputHalService inputHalService) {
- this(context, inputHalService, new Handler(Looper.getMainLooper()),
+ public CarInputService(Context context, InputHalService inputHalService,
+ CarUserService userService) {
+ this(context, inputHalService, userService, new Handler(Looper.getMainLooper()),
context.getSystemService(TelecomManager.class), new AssistUtils(context),
event ->
context.getSystemService(InputManager.class)
@@ -297,14 +312,15 @@
}
@VisibleForTesting
- CarInputService(Context context, InputHalService inputHalService, Handler handler,
- TelecomManager telecomManager, AssistUtils assistUtils,
+ CarInputService(Context context, InputHalService inputHalService, CarUserService userService,
+ Handler handler, TelecomManager telecomManager, AssistUtils assistUtils,
KeyEventListener mainDisplayHandler, Supplier<String> lastCalledNumberSupplier,
@Nullable ComponentName customInputServiceComponent,
IntSupplier longPressDelaySupplier) {
mContext = context;
mCaptureController = new InputCaptureClientController(context);
mInputHalService = inputHalService;
+ mUserService = userService;
mTelecomManager = telecomManager;
mAssistUtils = assistUtils;
mMainDisplayHandler = mainDisplayHandler;
@@ -317,6 +333,8 @@
handler, longPressDelaySupplier, this::handleVoiceAssistLongPress);
mCallKeyTimer =
new KeyPressTimer(handler, longPressDelaySupplier, this::handleCallLongPress);
+
+ mRotaryServiceComponentName = mContext.getString(R.string.rotaryService);
}
@VisibleForTesting
@@ -367,6 +385,9 @@
mBluetoothAdapter.getProfileProxy(
mContext, mBluetoothProfileServiceListener, BluetoothProfile.HEADSET_CLIENT);
}
+ if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
+ mUserService.addUserLifecycleListener(mUserLifecycleListener);
+ }
}
@Override
@@ -385,6 +406,9 @@
mBluetoothHeadsetClient = null;
}
}
+ if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
+ mUserService.removeUserLifecycleListener(mUserLifecycleListener);
+ }
}
@Override
@@ -705,4 +729,19 @@
intent.setComponent(mCustomInputServiceComponent);
return mContext.bindService(intent, mInputServiceConnection, Context.BIND_AUTO_CREATE);
}
+
+ private void updateRotaryServiceSettings(@UserIdInt int userId) {
+ if (UserHelper.isHeadlessSystemUser(userId)) {
+ return;
+ }
+ ContentResolver contentResolver = mContext.getContentResolver();
+ Settings.Secure.putStringForUser(contentResolver,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ mRotaryServiceComponentName,
+ userId);
+ Settings.Secure.putStringForUser(contentResolver,
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ "1",
+ userId);
+ }
}
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index 5a2aee2..aef68cd 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -44,6 +44,7 @@
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -59,6 +60,7 @@
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractionManagerService;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -137,6 +139,8 @@
private final CarUserService mUserService;
private final InitialUserSetter mInitialUserSetter;
+ private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
// TODO: Make this OEM configurable.
private static final int SHUTDOWN_POLLING_INTERVAL_MS = 2000;
private static final int SHUTDOWN_EXTEND_MAX_MS = 5000;
@@ -168,13 +172,16 @@
this(context, context.getResources(), powerHal, systemInterface, UserManager.get(context),
carUserService, new InitialUserSetter(context,
(u) -> carUserService.setInitialUser(u),
- context.getString(R.string.default_guest_name)));
+ context.getString(R.string.default_guest_name)),
+ IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)));
}
@VisibleForTesting
public CarPowerManagementService(Context context, Resources resources, PowerHalService powerHal,
SystemInterface systemInterface, UserManager userManager, CarUserService carUserService,
- InitialUserSetter initialUserSetter) {
+ InitialUserSetter initialUserSetter,
+ IVoiceInteractionManagerService voiceInteractionService) {
mContext = context;
mHal = powerHal;
mSystemInterface = systemInterface;
@@ -194,6 +201,7 @@
}
mUserService = carUserService;
mInitialUserSetter = initialUserSetter;
+ mVoiceInteractionManagerService = voiceInteractionService;
}
@VisibleForTesting
@@ -408,6 +416,7 @@
mSystemInterface.setDisplayState(true);
sendPowerManagerEvent(CarPowerStateListener.ON);
+
mHal.sendOn();
try {
@@ -415,6 +424,8 @@
} catch (Exception e) {
Log.e(CarLog.TAG_POWER, "Could not switch user on resume", e);
}
+
+ setVoiceInteractionDisabled(false);
}
@VisibleForTesting // Ideally it should not be exposed, but it speeds up the unit tests
@@ -552,6 +563,7 @@
}
private void handleShutdownPrepare(CpmsState newState) {
+ setVoiceInteractionDisabled(true);
mSystemInterface.setDisplayState(false);
// Shutdown on finish if the system doesn't support deep sleep or doesn't allow it.
synchronized (mLock) {
@@ -619,6 +631,8 @@
throw new AssertionError("Should not return from PowerManager.reboot()");
}
}
+ setVoiceInteractionDisabled(true);
+
if (mustShutDown) {
// shutdown HU
mSystemInterface.shutdown();
@@ -628,6 +642,14 @@
mShutdownOnNextSuspend = false;
}
+ private void setVoiceInteractionDisabled(boolean disabled) {
+ try {
+ mVoiceInteractionManagerService.setDisabled(disabled);
+ } catch (RemoteException e) {
+ Log.w(TAG, "setVoiceIntefactionDisabled(" + disabled + ") failed", e);
+ }
+ }
+
@GuardedBy("mLock")
private void releaseTimerLocked() {
if (mTimer != null) {
@@ -737,18 +759,18 @@
simulateSleepByWaiting();
nextListenerState = CarPowerStateListener.SHUTDOWN_CANCELLED;
} else {
- boolean sleepSucceeded = mSystemInterface.enterDeepSleep();
+ boolean sleepSucceeded = suspendWithRetries();
if (!sleepSucceeded) {
- // Suspend failed! VHAL should transition CPMS to shutdown.
- Log.e(CarLog.TAG_POWER, "Sleep did not succeed. Now attempting to shut down.");
- mSystemInterface.shutdown();
+ // Suspend failed and we shut down instead.
+ // We either won't get here at all or we will power off very soon.
return;
}
+ // We suspended and have now resumed
nextListenerState = CarPowerStateListener.SUSPEND_EXIT;
}
- // On resume, reset nextWakeup time. If not set again, system will suspend/shutdown forever.
synchronized (mLock) {
mIsResuming = true;
+ // Any wakeup time from before is no longer valid.
mNextWakeupSec = 0;
}
Log.i(CarLog.TAG_POWER, "Resuming after suspending");
@@ -1074,6 +1096,37 @@
}
}
+ // Send the command to enter Suspend to RAM.
+ // If the command is not successful, try again.
+ // If it fails repeatedly, send the command to shut down.
+ // Returns true if we successfully suspended.
+ private boolean suspendWithRetries() {
+ final int maxTries = 3;
+ final long retryIntervalMs = 10;
+ int tryCount = 0;
+
+ while (true) {
+ Log.i(CarLog.TAG_POWER, "Entering Suspend to RAM");
+ boolean suspendSucceeded = mSystemInterface.enterDeepSleep();
+ if (suspendSucceeded) {
+ return true;
+ }
+ tryCount++;
+ if (tryCount >= maxTries) {
+ break;
+ }
+ // We failed to suspend. Block the thread briefly and try again.
+ Log.w(CarLog.TAG_POWER, "Failed to Suspend; will retry later.");
+ try {
+ Thread.sleep(retryIntervalMs);
+ } catch (InterruptedException ignored) { }
+ }
+ // Too many failures trying to suspend. Shut down.
+ Log.w(CarLog.TAG_POWER, "Could not Suspend to RAM. Shutting down.");
+ mSystemInterface.shutdown();
+ return false;
+ }
+
private static class CpmsState {
// NOTE: When modifying states below, make sure to update CarPowerStateChanged.State in
// frameworks/base/cmds/statsd/src/atoms.proto also.
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 7f7818d..0a8c420 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -34,7 +34,9 @@
import android.car.input.CarInputManager;
import android.car.input.RotaryEvent;
import android.car.user.CarUserManager;
+import android.car.user.UserCreationResult;
import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
import android.car.user.UserSwitchResult;
import android.car.userlib.HalCallback;
import android.car.userlib.UserHalHelper;
@@ -45,7 +47,9 @@
import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
@@ -90,6 +94,7 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
final class CarShellCommand extends ShellCommand {
@@ -122,6 +127,7 @@
private static final String COMMAND_INJECT_ROTARY = "inject-rotary";
private static final String COMMAND_GET_INITIAL_USER_INFO = "get-initial-user-info";
private static final String COMMAND_SWITCH_USER = "switch-user";
+ private static final String COMMAND_REMOVE_USER = "remove-user";
private static final String COMMAND_CREATE_USER = "create-user";
private static final String COMMAND_GET_INITIAL_USER = "get-initial-user";
private static final String COMMAND_SET_USER_ID_TO_OCCUPANT_ZONE =
@@ -152,6 +158,8 @@
android.Manifest.permission.MANAGE_USERS);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SWITCH_USER,
android.Manifest.permission.MANAGE_USERS);
+ USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_REMOVE_USER,
+ android.Manifest.permission.MANAGE_USERS);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_CREATE_USER,
android.Manifest.permission.MANAGE_USERS);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_GET_USER_AUTH_ASSOCIATION,
@@ -371,6 +379,10 @@
pw.println("\t The --hal-only option only calls HAL, without switching the user,");
pw.println("\t while the --timeout defines how long to wait for the HAL response.");
+ pw.printf("\t%s <USER_ID> [--hal-only]\n", COMMAND_REMOVE_USER);
+ pw.println("\t Removes user with USER_ID using the HAL integration.");
+ pw.println("\t The --hal-only option only calls HAL, without removing the user,");
+
pw.printf("\t%s [--hal-only] [--timeout TIMEOUT_MS] [--type TYPE] [--flags FLAGS] [NAME]\n",
COMMAND_CREATE_USER);
pw.println("\t Creates a new user using the HAL integration.");
@@ -606,6 +618,9 @@
case COMMAND_SWITCH_USER:
switchUser(args, writer);
break;
+ case COMMAND_REMOVE_USER:
+ removeUser(args, writer);
+ break;
case COMMAND_CREATE_USER:
createUser(args, writer);
break;
@@ -951,12 +966,15 @@
if (halOnly) {
CountDownLatch latch = new CountDownLatch(1);
UserHalService userHal = mHal.getUserHal();
- UsersInfo usersInfo = generateUsersInfo();
UserInfo targetUserInfo = new UserInfo();
targetUserInfo.userId = targetUserId;
targetUserInfo.flags = getUserHalFlags(targetUserId);
- userHal.switchUser(targetUserInfo, timeout, usersInfo, (status, resp) -> {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.targetUser = targetUserInfo;
+ request.usersInfo = generateUsersInfo();
+
+ userHal.switchUser(request, timeout, (status, resp) -> {
try {
Log.d(TAG, "SwitchUserResponse: status=" + status + ", resp=" + resp);
writer.printf("Call Status: %s\n",
@@ -976,7 +994,7 @@
// Android error. This is to "rollback" the HAL switch.
if (status == HalCallback.STATUS_OK
&& resp.status == SwitchUserStatus.SUCCESS) {
- userHal.postSwitchResponse(resp.requestId, targetUserInfo, usersInfo);
+ userHal.postSwitchResponse(request);
}
} finally {
latch.countDown();
@@ -989,12 +1007,13 @@
AndroidFuture<UserSwitchResult> future = carUserManager.switchUser(targetUserId);
UserSwitchResult result = waitForFuture(writer, future, timeout);
if (result == null) return;
- writer.printf("UserSwitchResult: status = %s\n",
+ writer.printf("UserSwitchResult: status=%s",
UserSwitchResult.statusToString(result.getStatus()));
String msg = result.getErrorMessage();
- if (msg != null && !msg.isEmpty()) {
- writer.printf("UserSwitchResult: Message = %s\n", msg);
+ if (!TextUtils.isEmpty(msg)) {
+ writer.printf(", errorMessage=%s", msg);
}
+ writer.println();
}
private void createUser(String[] args, PrintWriter writer) {
@@ -1037,8 +1056,23 @@
+ ", halOnly=" + halOnly + ", timeout=" + timeout);
if (!halOnly) {
- // TODO(b/150408921): implement it
- throw new UnsupportedOperationException("must pass --hal-only for now");
+ CarUserManager carUserManager = getCarUserManager(mContext);
+ AndroidFuture<UserCreationResult> future = carUserManager
+ .createUser(name, userType, flags);
+
+ UserCreationResult result = waitForFuture(writer, future, timeout);
+ if (result == null) return;
+
+ android.content.pm.UserInfo user = result.getUser();
+ writer.printf("UserCreationResult: status=%s, user=%s",
+ UserCreationResult.statusToString(result.getStatus()),
+ user == null ? "N/A" : user.toFullString());
+ String msg = result.getErrorMessage();
+ if (!TextUtils.isEmpty(msg)) {
+ writer.printf(", errorMessage=%s", msg);
+ }
+ writer.println();
+ return;
}
CountDownLatch latch = new CountDownLatch(1);
@@ -1047,8 +1081,11 @@
CreateUserRequest request = new CreateUserRequest();
UserManager um = UserManager.get(mContext);
- android.content.pm.UserInfo newUser =
- um.createUser(name, userType, flags);
+ android.content.pm.UserInfo newUser = um.createUser(name, userType, flags);
+ if (newUser == null) {
+ writer.printf("Failed to create user");
+ return;
+ }
writer.printf("New user: %s\n", newUser.toFullString());
Log.i(TAG, "Created new user: " + newUser.toFullString());
@@ -1057,31 +1094,79 @@
request.usersInfo = generateUsersInfo();
- userHal.createUser(request, timeout, (status, resp) -> {
- Log.d(TAG, "CreateUserResponse: status=" + status + ", resp=" + resp);
- try {
+ AtomicBoolean halOk = new AtomicBoolean(false);
+ try {
+ userHal.createUser(request, timeout, (status, resp) -> {
+ Log.d(TAG, "CreateUserResponse: status=" + status + ", resp=" + resp);
writer.printf("Call Status: %s\n",
UserHalHelper.halCallbackStatusToString(status));
- if (status != HalCallback.STATUS_OK) {
- return;
+ if (status == HalCallback.STATUS_OK) {
+ halOk.set(resp.status == CreateUserStatus.SUCCESS);
+ writer.printf("Request id: %d\n", resp.requestId);
+ writer.printf("Create Status: %s\n", CreateUserStatus.toString(resp.status));
+ String errorMessage = resp.errorMessage;
+ if (!TextUtils.isEmpty(errorMessage)) {
+ writer.printf("Error message: %s", errorMessage);
+ }
}
- writer.printf("Request id: %d\n", resp.requestId);
- writer.printf("Create Status: %s\n", CreateUserStatus.toString(resp.status));
- String errorMessage = resp.errorMessage;
- if (!TextUtils.isEmpty(errorMessage)) {
- writer.printf("Error message: %s", errorMessage);
- }
-
- if (resp.status == CreateUserStatus.FAILURE) {
- Log.i(TAG, "Removing new user due to HAL failure");
- boolean removed = um.removeUser(newUser.id);
- writer.printf("User removed: %b\n", removed);
- }
- } finally {
latch.countDown();
+ });
+ waitForHal(writer, latch, timeout);
+ } catch (Exception e) {
+ writer.printf("HAL failed: %s\n", e);
+ } finally {
+ if (!halOk.get()) {
+ writer.printf("Removing user %d due to HAL failure\n", newUser.id);
+ boolean removed = um.removeUser(newUser.id);
+ writer.printf("User removed: %b\n", removed);
}
- });
- waitForHal(writer, latch, timeout);
+ }
+ }
+
+ private void removeUser(String[] args, PrintWriter writer) {
+ if (args.length < 2) {
+ writer.println("Insufficient number of args");
+ return;
+ }
+
+ int userId = Integer.parseInt(args[1]);
+ boolean halOnly = false;
+
+ for (int i = 2; i < args.length; i++) {
+ String arg = args[i];
+ switch (arg) {
+ case "--hal-only":
+ halOnly = true;
+ break;
+ default:
+ writer.println("Invalid option at index " + i + ": " + arg);
+ return;
+ }
+ }
+
+ Log.d(TAG, "handleRemoveUser(): User to remove=" + userId + ", halOnly=" + halOnly);
+
+ if (halOnly) {
+ UserHalService userHal = mHal.getUserHal();
+ UsersInfo usersInfo = generateUsersInfo();
+ UserInfo userInfo = new UserInfo();
+ userInfo.userId = userId;
+ userInfo.flags = getUserHalFlags(userId);
+
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.removedUserInfo = userInfo;
+ request.usersInfo = usersInfo;
+
+ userHal.removeUser(request);
+ writer.printf("User removal sent for HAL only.\n");
+ return;
+ }
+
+ CarUserManager carUserManager = getCarUserManager(mContext);
+ UserRemovalResult result = carUserManager.removeUser(userId);
+ if (result == null) return;
+ writer.printf("UserRemovalResult: status = %s\n",
+ UserRemovalResult.statusToString(result.getStatus()));
}
private static <T> T waitForFuture(@NonNull PrintWriter writer,
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 53e76fa..4d78dad 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -208,7 +208,7 @@
mCarUserService);
mPerUserCarServiceHelper = new PerUserCarServiceHelper(serviceContext, mCarUserService);
mCarBluetoothService = new CarBluetoothService(serviceContext, mPerUserCarServiceHelper);
- mCarInputService = new CarInputService(serviceContext, mHal.getInputHal());
+ mCarInputService = new CarInputService(serviceContext, mHal.getInputHal(), mCarUserService);
mCarProjectionService = new CarProjectionService(
serviceContext, null /* handler */, mCarInputService, mCarBluetoothService);
mGarageModeService = new GarageModeService(mContext);
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index b0dd41d..3efbd18 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -56,7 +56,6 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
-import android.view.DisplayAddress;
import android.view.KeyEvent;
import com.android.car.CarLocalServices;
@@ -1046,31 +1045,6 @@
return true;
}
- /**
- * Gets the zone id for the display port id.
- * @param displayPortId display port id to match
- * @return zone id for the display port id or
- * CarAudioManager.PRIMARY_AUDIO_ZONE if none are found
- */
- @Override
- public int getZoneIdForDisplayPortId(byte displayPortId) {
- enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
- requireDynamicRouting();
- synchronized (mImplLock) {
- for (int index = 0; index < mCarAudioZones.length; index++) {
- CarAudioZone zone = mCarAudioZones[index];
- List<DisplayAddress.Physical> displayAddresses = zone.getPhysicalDisplayAddresses();
- if (displayAddresses.stream().anyMatch(displayAddress->
- displayAddress.getPort() == displayPortId)) {
- return index;
- }
- }
-
- // Everything else defaults to primary audio zone
- return CarAudioManager.PRIMARY_AUDIO_ZONE;
- }
- }
-
@Override
public void registerVolumeCallback(@NonNull IBinder binder) {
synchronized (mImplLock) {
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
index a87b4b1..f4a4a33 100644
--- a/service/src/com/android/car/audio/CarAudioZone.java
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -19,7 +19,6 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.util.Log;
-import android.view.DisplayAddress;
import com.android.car.CarLog;
import com.android.internal.util.Preconditions;
@@ -45,14 +44,12 @@
private final int mId;
private final String mName;
private final List<CarVolumeGroup> mVolumeGroups;
- private final List<DisplayAddress.Physical> mPhysicalDisplayAddresses;
private List<AudioDeviceAttributes> mInputAudioDevice;
CarAudioZone(int id, String name) {
mId = id;
mName = name;
mVolumeGroups = new ArrayList<>();
- mPhysicalDisplayAddresses = new ArrayList<>();
mInputAudioDevice = new ArrayList<>();
}
@@ -96,23 +93,6 @@
}
/**
- * Associates a new display physical port with this audio zone. This can be used to
- * identify what zone an activity should produce sound in when launching on a particular display
- * @param physicalDisplayAddress port to associate with this zone
- */
- void addPhysicalDisplayAddress(DisplayAddress.Physical physicalDisplayAddress) {
- mPhysicalDisplayAddresses.add(physicalDisplayAddress);
- }
-
- /**
- * Gets list of ports for displays associated with this audio zone
- * @return list of Physical ports for displays associated with this audio zone
- */
- List<DisplayAddress.Physical> getPhysicalDisplayAddresses() {
- return mPhysicalDisplayAddresses;
- }
-
- /**
* @return Snapshot of available {@link CarVolumeGroup}s in array.
*/
CarVolumeGroup[] getVolumeGroups() {
@@ -173,11 +153,6 @@
void dump(String indent, PrintWriter writer) {
String internalIndent = indent + "\t";
writer.printf("%sCarAudioZone(%s:%d) isPrimary? %b\n", indent, mName, mId, isPrimaryZone());
- for (DisplayAddress.Physical physical: mPhysicalDisplayAddresses) {
- long port = (long) physical.getPort();
- writer.printf("%sDisplayAddress.Physical(%d)\n", internalIndent, port);
- }
- writer.println();
for (CarVolumeGroup group : mVolumeGroups) {
group.dump(internalIndent, writer);
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index 3ff25e9..1a4e0ad 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -22,7 +22,6 @@
import android.text.TextUtils;
import android.util.SparseIntArray;
import android.util.Xml;
-import android.view.DisplayAddress;
import com.android.car.audio.CarAudioContext.AudioContext;
import com.android.internal.util.Preconditions;
@@ -55,14 +54,11 @@
private static final String TAG_VOLUME_GROUP = "group";
private static final String TAG_AUDIO_DEVICE = "device";
private static final String TAG_CONTEXT = "context";
- private static final String TAG_DISPLAYS = "displays";
- private static final String TAG_DISPLAY = "display";
private static final String ATTR_VERSION = "version";
private static final String ATTR_IS_PRIMARY = "isPrimary";
private static final String ATTR_ZONE_NAME = "name";
private static final String ATTR_DEVICE_ADDRESS = "address";
private static final String ATTR_CONTEXT_NAME = "context";
- private static final String ATTR_PHYSICAL_PORT = "port";
private static final String ATTR_ZONE_ID = "audioZoneId";
private static final String ATTR_OCCUPANT_ZONE_ID = "occupantZoneId";
private static final String TAG_INPUT_DEVICES = "inputDevices";
@@ -132,7 +128,6 @@
private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
private final Map<String, AudioDeviceInfo> mAddressToInputAudioDeviceInfo;
private final InputStream mInputStream;
- private final Set<Long> mPortIds;
private final SparseIntArray mZoneIdToOccupantZoneIdMapping;
private final Set<Integer> mAudioZoneIds;
private final Set<String> mInputAudioDevices;
@@ -158,7 +153,6 @@
mAddressToInputAudioDeviceInfo =
CarAudioZonesHelper.generateAddressToInputAudioDeviceInfoMap(inputDeviceInfo);
mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
- mPortIds = new HashSet<>();
mZoneIdToOccupantZoneIdMapping = new SparseIntArray();
mAudioZoneIds = new HashSet<>();
mInputAudioDevices = new HashSet<>();
@@ -258,8 +252,6 @@
// Expect one <volumeGroups> in one audio zone
if (TAG_VOLUME_GROUPS.equals(parser.getName())) {
parseVolumeGroups(parser, zone);
- } else if (TAG_DISPLAYS.equals(parser.getName())) {
- parseDisplays(parser, zone);
} else if (TAG_INPUT_DEVICES.equals(parser.getName())) {
parseInputAudioDevices(parser, zone);
} else {
@@ -364,37 +356,6 @@
mInputAudioDevices.add(audioDeviceAddress);
}
- private void parseDisplays(XmlPullParser parser, CarAudioZone zone)
- throws IOException, XmlPullParserException {
- while (parser.next() != XmlPullParser.END_TAG) {
- if (parser.getEventType() != XmlPullParser.START_TAG) continue;
- if (TAG_DISPLAY.equals(parser.getName())) {
- zone.addPhysicalDisplayAddress(parsePhysicalDisplayAddress(parser));
- }
- skip(parser);
- }
- }
-
- private DisplayAddress.Physical parsePhysicalDisplayAddress(XmlPullParser parser) {
- String port = parser.getAttributeValue(NAMESPACE, ATTR_PHYSICAL_PORT);
- long portId;
- try {
- portId = Long.parseLong(port);
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException(String.format("Port %s is not a number", port), e);
- }
- validatePortIsUnique(portId);
- return DisplayAddress.fromPhysicalDisplayId(portId);
- }
-
- private void validatePortIsUnique(Long portId) {
- if (mPortIds.contains(portId)) {
- throw new IllegalArgumentException(
- String.format("Port Id %d is already associated with a zone", portId));
- }
- mPortIds.add(portId);
- }
-
private void validateOccupantZoneIdIsUnique(int occupantZoneId) {
if (mZoneIdToOccupantZoneIdMapping.indexOfValue(occupantZoneId) > -1) {
throw new IllegalArgumentException(ATTR_OCCUPANT_ZONE_ID + " " + occupantZoneId
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
index 33cf970..645f563 100644
--- a/service/src/com/android/car/hal/UserHalService.java
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -17,6 +17,7 @@
import static android.car.VehiclePropertyIds.CREATE_USER;
import static android.car.VehiclePropertyIds.INITIAL_USER_INFO;
+import static android.car.VehiclePropertyIds.REMOVE_USER;
import static android.car.VehiclePropertyIds.SWITCH_USER;
import static android.car.VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION;
@@ -34,7 +35,9 @@
import android.hardware.automotive.vehicle.V2_0.CreateUserResponse;
import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
@@ -165,6 +168,9 @@
mHandler.sendMessage(obtainMessage(
UserHalService::handleOnCreateUserResponse, this, value));
break;
+ case REMOVE_USER:
+ Log.w(TAG, "Received REMOVE_USER HAL event: " + value);
+ break;
case USER_IDENTIFICATION_ASSOCIATION:
mHandler.sendMessage(obtainMessage(
UserHalService::handleOnUserIdentificationAssociation, this, value));
@@ -270,33 +276,30 @@
/**
* Calls HAL to asynchronously switch user.
*
- * @param targetInfo target user for user switching
+ * @param request metadata
* @param timeoutMs how long to wait (in ms) for the property change event.
- * @param usersInfo current state of Android users.
* @param callback to handle the response.
*
* @throws IllegalStateException if the HAL does not support user management (callers should
* call {@link #isSupported()} first to avoid this exception).
*/
- public void switchUser(@NonNull UserInfo targetInfo, int timeoutMs,
- @NonNull UsersInfo usersInfo, @NonNull HalCallback<SwitchUserResponse> callback) {
- if (DBG) Log.d(TAG, "switchUser(" + targetInfo + ")");
-
- Objects.requireNonNull(targetInfo);
+ public void switchUser(@NonNull SwitchUserRequest request, int timeoutMs,
+ @NonNull HalCallback<SwitchUserResponse> callback) {
Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
- Objects.requireNonNull(callback);
- UserHalHelper.checkValid(usersInfo);
+ Objects.requireNonNull(callback, "callback cannot be null");
+ if (DBG) Log.d(TAG, "switchUser(" + request + ")");
VehiclePropValue propRequest;
int requestId;
synchronized (mLock) {
checkSupportedLocked();
if (hasPendingRequestLocked(SwitchUserResponse.class, callback)) return;
requestId = getNextRequestId();
+ request.requestId = requestId;
+ request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+ propRequest = UserHalHelper.toVehiclePropValue(request);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_REQ, requestId,
- targetInfo.userId, timeoutMs);
- propRequest = getPropRequestForSwitchUserLocked(requestId,
- SwitchUserMessageType.ANDROID_SWITCH, targetInfo, usersInfo);
+ request.targetUser.userId, timeoutMs);
addPendingRequestLocked(requestId, SwitchUserResponse.class, callback);
}
@@ -304,6 +307,34 @@
}
/**
+ * Calls HAL to remove user.
+ *
+ * @throws IllegalStateException if the HAL does not support user management (callers should
+ * call {@link #isSupported()} first to avoid this exception).
+ */
+ public void removeUser(@NonNull RemoveUserRequest request) {
+ Objects.requireNonNull(request, "request cannot be null");
+
+ if (DBG) Log.d(TAG, "removeUser(" + request.removedUserInfo.userId + ")");
+ EventLog.writeEvent(EventLogTags.CAR_USER_HAL_REMOVE_USER_REQ,
+ request.removedUserInfo.userId, request.usersInfo.currentUser.userId);
+
+ VehiclePropValue propRequest;
+ synchronized (mLock) {
+ checkSupportedLocked();
+ request.requestId = getNextRequestId();
+ propRequest = UserHalHelper.toVehiclePropValue(request);
+
+ }
+ try {
+ if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
+ mHal.set(propRequest);
+ } catch (ServiceSpecificException e) {
+ Log.w(TAG, "Failed to set REMOVE USER", e);
+ }
+ }
+
+ /**
* Calls HAL to indicate an Android user was created.
*
* @param request info agout the created user.
@@ -337,23 +368,17 @@
/**
* Calls HAL after android user switch.
- *
- * @param requestId for which switch response is sent.
- * @param targetInfo target user info.
- * @param usersInfo current state of Android users.
*/
- public void postSwitchResponse(int requestId, @NonNull UserInfo targetInfo,
- @NonNull UsersInfo usersInfo) {
- EventLog.writeEvent(EventLogTags.CAR_USER_HAL_POST_SWITCH_USER_REQ, requestId,
- targetInfo.userId, usersInfo.currentUser.userId);
- if (DBG) Log.d(TAG, "postSwitchResponse(" + targetInfo + ")");
- UserHalHelper.checkValid(usersInfo);
+ public void postSwitchResponse(@NonNull SwitchUserRequest request) {
+ EventLog.writeEvent(EventLogTags.CAR_USER_HAL_POST_SWITCH_USER_REQ, request.requestId,
+ request.targetUser.userId, request.usersInfo.currentUser.userId);
+ if (DBG) Log.d(TAG, "postSwitchResponse(" + request.targetUser.userId + ")");
VehiclePropValue propRequest;
synchronized (mLock) {
checkSupportedLocked();
- propRequest = getPropRequestForSwitchUserLocked(requestId,
- SwitchUserMessageType.ANDROID_POST_SWITCH, targetInfo, usersInfo);
+ request.messageType = SwitchUserMessageType.ANDROID_POST_SWITCH;
+ propRequest = UserHalHelper.toVehiclePropValue(request);
}
try {
@@ -368,22 +393,18 @@
* Calls HAL to switch user after legacy Android user switch. Legacy Android user switch means
* user switch is not requested by {@link CarUserManager} or OEM, and user switch is directly
* requested by {@link ActivityManager}
- *
- * @param targetInfo target user info.
- * @param usersInfo current state of Android users.
*/
- public void legacyUserSwitch(@NonNull UserInfo targetInfo, @NonNull UsersInfo usersInfo) {
- if (DBG) Log.d(TAG, "userSwitchLegacy(" + targetInfo + ")");
- UserHalHelper.checkValid(usersInfo);
+ public void legacyUserSwitch(@NonNull SwitchUserRequest request) {
+ if (DBG) Log.d(TAG, "userSwitchLegacy(" + request + ")");
VehiclePropValue propRequest;
synchronized (mLock) {
checkSupportedLocked();
int requestId = getNextRequestId();
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_LEGACY_SWITCH_USER_REQ, requestId,
- targetInfo.userId, usersInfo.currentUser.userId);
- propRequest = getPropRequestForSwitchUserLocked(requestId,
- SwitchUserMessageType.LEGACY_ANDROID_SWITCH, targetInfo, usersInfo);
+ request.targetUser.userId, request.usersInfo.currentUser.userId);
+ request.messageType = SwitchUserMessageType.LEGACY_ANDROID_SWITCH;
+ propRequest = UserHalHelper.toVehiclePropValue(request);
}
try {
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 1d1bbb6..9f66e49 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -224,6 +224,11 @@
*/
@Override
public void restartTask(int taskId) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.REAL_GET_TASKS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "requires permission " + android.Manifest.permission.REAL_GET_TASKS);
+ }
mSystemActivityMonitoringService.restartTask(taskId);
}
diff --git a/service/src/com/android/car/stats/CarStatsService.java b/service/src/com/android/car/stats/CarStatsService.java
index 20b451c..bf79ee8 100644
--- a/service/src/com/android/car/stats/CarStatsService.java
+++ b/service/src/com/android/car/stats/CarStatsService.java
@@ -27,7 +27,7 @@
import com.android.car.CarStatsLog;
import com.android.car.stats.VmsClientLogger.ConnectionState;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ConcurrentUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -102,7 +102,7 @@
mStatsManager.setPullAtomCallback(
CarStatsLog.VMS_CLIENT_STATS,
metadata,
- BackgroundThread.getExecutor(),
+ ConcurrentUtils.DIRECT_EXECUTOR,
(atomTag, data) -> pullVmsClientStats(atomTag, data)
);
}
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index d7de501..8e08b28 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -33,7 +33,9 @@
import android.car.user.CarUserManager.UserLifecycleEvent;
import android.car.user.CarUserManager.UserLifecycleEventType;
import android.car.user.CarUserManager.UserLifecycleListener;
+import android.car.user.UserCreationResult;
import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
import android.car.user.UserSwitchResult;
import android.car.userlib.CarUserManagerHelper;
import android.car.userlib.CommonConstants.CarUserServiceConstants;
@@ -45,10 +47,15 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
+import android.content.pm.UserInfo.UserInfoFlag;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.hardware.automotive.vehicle.V2_0.CreateUserRequest;
+import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
@@ -297,6 +304,7 @@
writer.printf("EnablePassengerSupport: %s\n", mEnablePassengerSupport);
writer.printf("User HAL timeout: %dms\n", mHalTimeoutMs);
writer.printf("Initial user: %s\n", mInitialUser);
+
writer.println("Relevant overlayable properties");
Resources res = mContext.getResources();
writer.printf("%sowner_name=%s\n", indent,
@@ -308,9 +316,18 @@
mRequestIdForUserSwitchInProcess);
writer.printf("System UI package name=%s\n", getSystemUiPackageName());
+ writer.println("Relevant Global settings");
+ dumpGlobalProperty(writer, indent, CarSettings.Global.LAST_ACTIVE_USER_ID);
+ dumpGlobalProperty(writer, indent, CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+
dumpUserMetrics(writer);
}
+ private void dumpGlobalProperty(PrintWriter writer, String indent, String property) {
+ String value = Settings.Global.getString(mContext.getContentResolver(), property);
+ writer.printf("%s%s=%s\n", indent, property, value);
+ }
+
/**
* Dumps user metrics.
*/
@@ -328,7 +345,7 @@
private void handleDumpListeners(@NonNull PrintWriter writer, String indent) {
CountDownLatch latch = new CountDownLatch(1);
mHandler.post(() -> {
- handleDumpUserLifecycleListeners(writer);
+ handleDumpServiceLifecycleListeners(writer);
handleDumpAppLifecycleListeners(writer, indent);
latch.countDown();
});
@@ -344,57 +361,69 @@
}
}
- private void handleDumpUserLifecycleListeners(@NonNull PrintWriter writer) {
+ private void handleDumpServiceLifecycleListeners(@NonNull PrintWriter writer) {
if (mUserLifecycleListeners.isEmpty()) {
- writer.println("No user lifecycle listeners");
+ writer.println("No lifecycle listeners for internal services");
return;
}
- writer.printf("%d user lifecycle listeners\n", mUserLifecycleListeners.size());
+ int size = mUserLifecycleListeners.size();
+ writer.printf("%d lifecycle listener%s for services\n", size, size == 1 ? "" : "s");
+ String indent = " ";
for (UserLifecycleListener listener : mUserLifecycleListeners) {
- writer.printf("Listener %s\n", listener);
+ writer.printf("%s%s\n", indent, FunctionalUtils.getLambdaName(listener));
}
}
private void handleDumpAppLifecycleListeners(@NonNull PrintWriter writer, String indent) {
- int numberListeners = mAppLifecycleListeners.size();
- if (numberListeners == 0) {
- writer.println("No lifecycle listeners");
+ int size = mAppLifecycleListeners.size();
+ if (size == 0) {
+ writer.println("No lifecycle listeners for apps");
return;
}
- writer.printf("%d lifecycle listeners\n", numberListeners);
- for (int i = 0; i < numberListeners; i++) {
+ writer.printf("%d lifecycle listener%s for apps \n", size, size == 1 ? "" : "s");
+ for (int i = 0; i < size; i++) {
int uid = mAppLifecycleListeners.keyAt(i);
IResultReceiver listener = mAppLifecycleListeners.valueAt(i);
- writer.printf("%suid: %d Listener %s\n", indent, uid, listener);
+ writer.printf("%suid: %d\n", indent, uid);
}
}
/**
- * Creates a driver who is a regular user and is allowed to login to the driving occupant zone.
- *
- * @param name The name of the driver to be created.
- * @param admin Whether the created driver will be an admin.
- * @return {@link UserInfo} object of the created driver, or {@code null} if the driver could
- * not be created.
+ * @see ExperimentalCarUserManager.createDriver
*/
@Override
- @Nullable
- public UserInfo createDriver(@NonNull String name, boolean admin) {
+ public AndroidFuture<UserCreationResult> createDriver(@NonNull String name, boolean admin) {
checkManageUsersPermission("createDriver");
Objects.requireNonNull(name, "name cannot be null");
+
+ AndroidFuture<UserCreationResult> future = new AndroidFuture<UserCreationResult>() {
+ @Override
+ protected void onCompleted(UserCreationResult result, Throwable err) {
+ if (result == null) {
+ Log.w(TAG, "createDriver(" + name + "," + admin + ") failed: " + err);
+ } else {
+ if (result.getStatus() == UserCreationResult.STATUS_SUCCESSFUL) {
+ assignDefaultIcon(result.getUser());
+ }
+ }
+ super.onCompleted(result, err);
+ };
+ };
+ int flags = 0;
if (admin) {
- return createNewAdminUser(name);
+ if (!(mUserManager.isAdminUser() || mUserManager.isSystemUser())) {
+ Log.e(TAG_USER, "Only admin users and system user can create other admins.");
+ sendUserCreationResultFailure(future, UserCreationResult.STATUS_INVALID_REQUEST);
+ return future;
+ }
+ flags = UserInfo.FLAG_ADMIN;
}
- return mCarUserManagerHelper.createNewNonAdminUser(name);
+ createUser(name, UserInfo.getDefaultUserType(flags), flags, mHalTimeoutMs, future);
+ return future;
}
/**
- * Creates a passenger who is a profile of the given driver.
- *
- * @param name The name of the passenger to be created.
- * @param driverId User id of the driver under whom a passenger is created.
- * @return {@link UserInfo} object of the created passenger, or {@code null} if the passenger
- * could not be created.
+ * @see ExperimentalCarUserManager.createPassenger
*/
@Override
@Nullable
@@ -410,6 +439,7 @@
Log.w(TAG_USER, "a guest driver cannot create a passenger");
return null;
}
+ // createPassenger doesn't use user HAL because user HAL doesn't support profile user yet.
UserInfo user = mUserManager.createProfileForUser(name,
UserManager.USER_TYPE_PROFILE_MANAGED, /* flags */ 0, driverId);
if (user == null) {
@@ -424,7 +454,7 @@
}
/**
- * @see CarUserManager.switchDriver
+ * @see ExperimentalCarUserManager.switchDriver
*/
@Override
public void switchDriver(@UserIdInt int driverId, AndroidFuture<UserSwitchResult> receiver) {
@@ -432,13 +462,13 @@
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");
- sendResult(receiver, UserSwitchResult.STATUS_INVALID_REQUEST);
+ sendUserSwitchResult(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");
- sendResult(receiver, UserSwitchResult.STATUS_INVALID_REQUEST);
+ sendUserSwitchResult(receiver, UserSwitchResult.STATUS_INVALID_REQUEST);
return;
}
switchUser(driverId, mHalTimeoutMs, receiver);
@@ -597,7 +627,7 @@
timeoutMs);
Objects.requireNonNull(receiver, "receiver cannot be null");
checkManageUsersPermission("getInitialInfo");
- UsersInfo usersInfo = getUsersInfo();
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
mHal.getInitialUserInfo(requestType, timeoutMs, usersInfo, (status, resp) -> {
Bundle resultData = null;
if (resp != null) {
@@ -699,7 +729,7 @@
mHalTimeoutMs);
Objects.requireNonNull(callback, "callback cannot be null");
checkManageUsersPermission("getInitialUserInfo");
- UsersInfo usersInfo = getUsersInfo();
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
mHal.getInitialUserInfo(requestType, mHalTimeoutMs, usersInfo, callback);
}
@@ -756,7 +786,7 @@
Log.d(TAG_USER, "Current user is same as requested target user: " + targetUserId);
}
int resultStatus = UserSwitchResult.STATUS_ALREADY_REQUESTED_USER;
- sendResult(receiver, resultStatus);
+ sendUserSwitchResult(receiver, resultStatus);
return;
}
@@ -779,7 +809,7 @@
}
int resultStatus = UserSwitchResult.STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO;
- sendResult(receiver, resultStatus);
+ sendUserSwitchResult(receiver, resultStatus);
return;
}
else {
@@ -788,12 +818,10 @@
}
}
- UsersInfo usersInfo = getUsersInfo();
- android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
- new android.hardware.automotive.vehicle.V2_0.UserInfo();
- halTargetUser.userId = targetUser.id;
- halTargetUser.flags = UserHalHelper.convertFlags(targetUser);
- mHal.switchUser(halTargetUser, timeoutMs, usersInfo, (status, resp) -> {
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+ SwitchUserRequest request = createUserSwitchRequest(targetUserId, usersInfo);
+
+ mHal.switchUser(request, timeoutMs, (status, resp) -> {
if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
Log.d(TAG, "switch response: status="
+ UserHalHelper.halCallbackStatusToString(status) + ", resp=" + resp);
@@ -807,7 +835,7 @@
Log.w(TAG, "invalid callback status ("
+ UserHalHelper.halCallbackStatusToString(status) + ") for response "
+ resp);
- sendResult(receiver, resultStatus);
+ sendUserSwitchResult(receiver, resultStatus);
mUserIdForUserSwitchInProcess = UserHandle.USER_NULL;
return;
}
@@ -825,7 +853,7 @@
}
resultStatus =
UserSwitchResult.STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST;
- sendResult(receiver, resultStatus);
+ sendUserSwitchResult(receiver, resultStatus);
mUserIdForUserSwitchInProcess = UserHandle.USER_NULL;
return;
}
@@ -853,24 +881,83 @@
// HAL failed to switch user
resultStatus = UserSwitchResult.STATUS_HAL_FAILURE;
break;
+ default:
+ // Shouldn't happen because UserHalService validates the status
+ Log.wtf(TAG, "Received invalid user switch status from HAL: " + resp);
}
if (mRequestIdForUserSwitchInProcess == 0) {
mUserIdForUserSwitchInProcess = UserHandle.USER_NULL;
}
}
- sendResult(receiver, resultStatus, resp.errorMessage);
+ sendUserSwitchResult(receiver, resultStatus, resp.errorMessage);
});
}
+ @Override
+ public UserRemovalResult removeUser(@UserIdInt int userId) {
+ checkManageUsersPermission("removeUser");
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_REMOVE_USER_REQ, userId);
+ // If the requested user is the current user, return error.
+ if (ActivityManager.getCurrentUser() == userId) {
+ return logAndGetResults(userId,
+ UserRemovalResult.STATUS_TARGET_USER_IS_CURRENT_USER);
+ }
+
+ // If requested user is the only admin user, return error.
+ UserInfo userInfo = mUserManager.getUserInfo(userId);
+ if (userInfo == null) {
+ return logAndGetResults(userId, UserRemovalResult.STATUS_USER_DOES_NOT_EXIST);
+ }
+
+ android.hardware.automotive.vehicle.V2_0.UserInfo halUser =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ halUser.userId = userInfo.id;
+ halUser.flags = UserHalHelper.convertFlags(userInfo);
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+
+ // Do not delete last admin user.
+ if (UserHalHelper.isAdmin(halUser.flags)) {
+ int size = usersInfo.existingUsers.size();
+ int totalAdminUsers = 0;
+ for (int i = 0; i < size; i++) {
+ if (UserHalHelper.isAdmin(usersInfo.existingUsers.get(i).flags)) {
+ totalAdminUsers++;
+ }
+ }
+ if (totalAdminUsers == 1) {
+ return logAndGetResults(userId,
+ UserRemovalResult.STATUS_TARGET_USER_IS_LAST_ADMIN_USER);
+ }
+ }
+
+ // First remove user from android and then remove from HAL because HAL remove user is one
+ // way call.
+ if (!mUserManager.removeUser(userId)) {
+ return logAndGetResults(userId, UserRemovalResult.STATUS_ANDROID_FAILURE);
+ }
+
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.removedUserInfo = halUser;
+ request.usersInfo = usersInfo;
+ mHal.removeUser(request);
+ return logAndGetResults(userId, UserRemovalResult.STATUS_SUCCESSFUL);
+ }
+
+ private UserRemovalResult logAndGetResults(@UserIdInt int userId,
+ @UserRemovalResult.Status int result) {
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_REMOVE_USER_RESP, userId, result);
+ return new UserRemovalResult(result);
+ }
+
private void sendUserSwitchUiCallback(@UserIdInt int targetUserId) {
if (mUserSwitchUiReceiver == null) {
Log.w(TAG_USER, "No User switch UI receiver.");
return;
}
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_SWITCH_USER_UI_REQ, targetUserId);
try {
- EventLog.writeEvent(EventLogTags.CAR_USER_SVC_SWITCH_USER_UI_REQ, targetUserId);
mUserSwitchUiReceiver.send(targetUserId, null);
} catch (RemoteException e) {
Log.e(TAG_USER, "Error calling user switch UI receiver.", e);
@@ -878,6 +965,106 @@
}
@Override
+ public void createUser(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags,
+ int timeoutMs, @NonNull AndroidFuture<UserCreationResult> receiver) {
+ Objects.requireNonNull(userType, "user type cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+ checkManageOrCreateUsersPermission("createUser");
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_REQ, UserHelper.safeName(name),
+ userType, flags, timeoutMs);
+
+ UserInfo newUser;
+ try {
+ newUser = mUserManager.createUser(name, userType, flags);
+ if (newUser == null) {
+ Log.w(TAG, "um.createUser() returned null for user of type " + userType
+ + " and flags " + UserInfo.flagsToString(flags));
+ sendUserCreationResultFailure(receiver, UserCreationResult.STATUS_ANDROID_FAILURE);
+ return;
+ }
+ if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+ Log.d(TAG, "Created user: " + newUser.toFullString());
+ }
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_USER_CREATED, newUser.id,
+ UserHelper.safeName(newUser.name), newUser.userType, newUser.flags);
+ } catch (RuntimeException e) {
+ Log.e(TAG_USER, "Error creating user of type " + userType + " and flags"
+ + UserInfo.flagsToString(flags), e);
+ sendUserCreationResultFailure(receiver, UserCreationResult.STATUS_ANDROID_FAILURE);
+ return;
+ }
+
+ CreateUserRequest request = new CreateUserRequest();
+ request.usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+ if (!TextUtils.isEmpty(name)) {
+ request.newUserName = name;
+ }
+ request.newUserInfo.userId = newUser.id;
+ request.newUserInfo.flags = UserHalHelper.convertFlags(newUser);
+ if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+ Log.d(TAG, "Create user request: " + request);
+ }
+
+ try {
+ mHal.createUser(request, timeoutMs, (status, resp) -> {
+ int resultStatus = UserCreationResult.STATUS_HAL_INTERNAL_FAILURE;
+ if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+ Log.d(TAG, "createUserResponse: status="
+ + UserHalHelper.halCallbackStatusToString(status) + ", resp=" + resp);
+ }
+ UserInfo user = null; // user returned in the result
+ if (status != HalCallback.STATUS_OK) {
+ Log.w(TAG, "invalid callback status ("
+ + UserHalHelper.halCallbackStatusToString(status) + ") for response "
+ + resp);
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_RESP, status,
+ resultStatus, resp.errorMessage);
+ removeUser(newUser, "HAL call failed with "
+ + UserHalHelper.halCallbackStatusToString(status));
+ sendUserCreationResult(receiver, resultStatus, user, /* errorMsg= */ null);
+ return;
+ }
+
+ switch (resp.status) {
+ case CreateUserStatus.SUCCESS:
+ resultStatus = UserCreationResult.STATUS_SUCCESSFUL;
+ user = newUser;
+ break;
+ case CreateUserStatus.FAILURE:
+ // HAL failed to switch user
+ resultStatus = UserCreationResult.STATUS_HAL_FAILURE;
+ break;
+ default:
+ // Shouldn't happen because UserHalService validates the status
+ Log.wtf(TAG, "Received invalid user switch status from HAL: " + resp);
+ }
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_RESP, status,
+ resultStatus, resp.errorMessage);
+ if (user == null) {
+ removeUser(newUser, "HAL returned "
+ + UserCreationResult.statusToString(resultStatus));
+ }
+ sendUserCreationResult(receiver, resultStatus, user, resp.errorMessage);
+ });
+ } catch (Exception e) {
+ Log.w(TAG, "mHal.createUser(" + request + ") failed", e);
+ removeUser(newUser, "mHal.createUser() failed");
+ sendUserCreationResultFailure(receiver, UserCreationResult.STATUS_HAL_INTERNAL_FAILURE);
+ }
+ }
+
+ private void removeUser(@NonNull UserInfo user, @NonNull String reason) {
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_USER_REMOVED, user.id, reason);
+ try {
+ if (!mUserManager.removeUser(user.id)) {
+ Log.w(TAG, "Failed to remove user " + user.toFullString());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to remove user " + user.toFullString(), e);
+ }
+ }
+
+ @Override
public UserIdentificationAssociationResponse getUserIdentificationAssociation(int[] types) {
Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
checkManageUsersPermission("getUserIdentificationAssociation");
@@ -993,16 +1180,30 @@
}
}
- private void sendResult(@NonNull AndroidFuture<UserSwitchResult> receiver,
+ private void sendUserSwitchResult(@NonNull AndroidFuture<UserSwitchResult> receiver,
@UserSwitchResult.Status int status) {
- sendResult(receiver, status, /* errorMessage= */ null);
+ sendUserSwitchResult(receiver, status, /* errorMessage= */ null);
}
- private void sendResult(@NonNull AndroidFuture<UserSwitchResult> receiver,
+ private void sendUserSwitchResult(@NonNull AndroidFuture<UserSwitchResult> receiver,
@UserSwitchResult.Status int status, @Nullable String errorMessage) {
receiver.complete(new UserSwitchResult(status, errorMessage));
}
+ private void sendUserCreationResultFailure(@NonNull AndroidFuture<UserCreationResult> receiver,
+ @UserCreationResult.Status int status) {
+ sendUserCreationResult(receiver, status, /* user= */ null, /* errorMessage= */ null);
+ }
+
+ private void sendUserCreationResult(@NonNull AndroidFuture<UserCreationResult> receiver,
+ @UserCreationResult.Status int status, @NonNull UserInfo user,
+ @Nullable String errorMessage) {
+ if (TextUtils.isEmpty(errorMessage)) {
+ errorMessage = null;
+ }
+ receiver.complete(new UserCreationResult(status, user, errorMessage));
+ }
+
/**
* Calls activity manager for user switch.
*
@@ -1045,17 +1246,26 @@
mRequestIdForUserSwitchInProcess = requestId;
}
}
-
private void postSwitchHalResponse(int requestId, @UserIdInt int targetUserId) {
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_POST_SWITCH_USER_REQ, requestId,
+ targetUserId, usersInfo.currentUser.userId);
+ SwitchUserRequest request = createUserSwitchRequest(targetUserId, usersInfo);
+ request.requestId = requestId;
+ mHal.postSwitchResponse(request);
+ }
+
+ private SwitchUserRequest createUserSwitchRequest(@UserIdInt int targetUserId,
+ @NonNull UsersInfo usersInfo) {
UserInfo targetUser = mUserManager.getUserInfo(targetUserId);
- UsersInfo usersInfo = getUsersInfo();
android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
new android.hardware.automotive.vehicle.V2_0.UserInfo();
halTargetUser.userId = targetUser.id;
halTargetUser.flags = UserHalHelper.convertFlags(targetUser);
- EventLog.writeEvent(EventLogTags.CAR_USER_SVC_POST_SWITCH_USER_REQ, requestId,
- targetUserId, usersInfo.currentUser.userId);
- mHal.postSwitchResponse(requestId, halTargetUser, usersInfo);
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.targetUser = halTargetUser;
+ request.usersInfo = usersInfo;
+ return request;
}
/**
@@ -1112,40 +1322,6 @@
}
}
- // TODO(b/150413515): use helper to generate UsersInfo
- private UsersInfo getUsersInfo() {
- UserInfo currentUser;
- try {
- currentUser = mAm.getCurrentUser();
- } catch (RemoteException e) {
- // shouldn't happen
- throw new IllegalStateException("Could not get current user: ", e);
- }
- return getUsersInfo(currentUser);
- }
-
- // TODO(b/150413515): use helper to generate UsersInfo
- private UsersInfo getUsersInfo(@NonNull UserInfo currentUser) {
- List<UserInfo> existingUsers = mUserManager.getUsers();
- int size = existingUsers.size();
-
- UsersInfo usersInfo = new UsersInfo();
- usersInfo.numberUsers = size;
- usersInfo.currentUser.userId = currentUser.id;
- usersInfo.currentUser.flags = UserHalHelper.convertFlags(currentUser);
-
- for (int i = 0; i < size; i++) {
- UserInfo androidUser = existingUsers.get(i);
- android.hardware.automotive.vehicle.V2_0.UserInfo halUser =
- new android.hardware.automotive.vehicle.V2_0.UserInfo();
- halUser.userId = androidUser.id;
- halUser.flags = UserHalHelper.convertFlags(androidUser);
- usersInfo.existingUsers.add(halUser);
- }
-
- return usersInfo;
- }
-
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.
@@ -1345,8 +1521,10 @@
handleNotifyAppUserLifecycleListeners(event);
});
- // Finally, update metrics.
- mUserMetrics.onEvent(eventType, timestampMs, fromUserId, toUserId);
+ if (timestampMs != 0) {
+ // Finally, update metrics.
+ mUserMetrics.onEvent(eventType, timestampMs, fromUserId, toUserId);
+ }
}
/**
@@ -1385,23 +1563,27 @@
}
int userId = event.getUserId();
TimingsTraceLog t = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
- t.traceBegin("notify-app-listeners-user-" + userId + "-event-" + event.getEventType());
+ int eventType = event.getEventType();
+ t.traceBegin("notify-app-listeners-user-" + userId + "-event-" + eventType);
for (int i = 0; i < listenersSize; i++) {
int uid = mAppLifecycleListeners.keyAt(i);
+
IResultReceiver listener = mAppLifecycleListeners.valueAt(i);
Bundle data = new Bundle();
- data.putInt(CarUserManager.BUNDLE_PARAM_ACTION, event.getEventType());
+ data.putInt(CarUserManager.BUNDLE_PARAM_ACTION, eventType);
- int fromUid = event.getPreviousUserId();
- if (fromUid != UserHandle.USER_NULL) {
- data.putInt(CarUserManager.BUNDLE_PARAM_PREVIOUS_USER_ID, fromUid);
+ int fromUserId = event.getPreviousUserId();
+ if (fromUserId != UserHandle.USER_NULL) {
+ data.putInt(CarUserManager.BUNDLE_PARAM_PREVIOUS_USER_ID, fromUserId);
}
if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
Log.d(TAG_USER, "Notifying listener for uid " + uid);
}
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_NOTIFY_APP_LIFECYCLE_LISTENER,
+ uid, eventType, fromUserId, userId);
try {
- t.traceBegin("notify-app-listener-" + uid);
+ t.traceBegin("notify-app-listener-uid-" + uid);
listener.send(userId, data);
} catch (RemoteException e) {
Log.e(TAG_USER, "Error calling lifecycle listener", e);
@@ -1422,10 +1604,13 @@
+ event);
}
- t.traceBegin("notify-listeners-user-" + event.getUserId() + "-event-"
- + event.getEventType());
+ int userId = event.getUserId();
+ int eventType = event.getEventType();
+ t.traceBegin("notify-listeners-user-" + userId + "-event-" + eventType);
for (UserLifecycleListener listener : mUserLifecycleListeners) {
String listenerName = FunctionalUtils.getLambdaName(listener);
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_NOTIFY_INTERNAL_LIFECYCLE_LISTENER,
+ listenerName, eventType, event.getPreviousUserId(), userId);
try {
t.traceBegin("notify-listener-" + listenerName);
listener.onEvent(event);
@@ -1447,9 +1632,8 @@
// Switch HAL users if user switch is not requested by CarUserService
notifyHalLegacySwitch(fromUserId, toUserId);
- if (!UserHelper.isHeadlessSystemUser(toUserId)) {
- mCarUserManagerHelper.setLastActiveUser(toUserId);
- }
+ mCarUserManagerHelper.setLastActiveUser(toUserId);
+
if (mLastPassengerId != UserHandle.USER_NULL) {
stopPassengerInternal(mLastPassengerId, false);
}
@@ -1462,18 +1646,19 @@
private void notifyHalLegacySwitch(@UserIdInt int fromUserId, @UserIdInt int toUserId) {
synchronized (mLockUser) {
- if (mUserIdForUserSwitchInProcess != UserHandle.USER_NULL) return;
+ if (mUserIdForUserSwitchInProcess != UserHandle.USER_NULL) {
+ if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+ Log.d(TAG, "notifyHalLegacySwitch(" + fromUserId + ", " + toUserId
+ + "): not needed, normal switch for " + mUserIdForUserSwitchInProcess);
+ }
+ return;
+ }
}
// switch HAL user
- UserInfo targetUser = mUserManager.getUserInfo(toUserId);
- android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
- new android.hardware.automotive.vehicle.V2_0.UserInfo();
- halTargetUser.userId = targetUser.id;
- halTargetUser.flags = UserHalHelper.convertFlags(targetUser);
- UserInfo currentUser = mUserManager.getUserInfo(fromUserId);
- UsersInfo usersInfo = getUsersInfo(currentUser);
- mHal.legacyUserSwitch(halTargetUser, usersInfo);
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager, fromUserId);
+ SwitchUserRequest request = createUserSwitchRequest(toUserId, usersInfo);
+ mHal.legacyUserSwitch(request);
}
/**
@@ -1516,42 +1701,15 @@
}
/**
- * Creates a new user on the system, the created user would be granted admin role.
- *
- * @param name Name to be given to the newly created user.
- * @return newly created admin user, {@code null} if it fails to create a user.
- */
- @Nullable
- private UserInfo createNewAdminUser(String name) {
- if (!(mUserManager.isAdminUser() || mUserManager.isSystemUser())) {
- // Only admins or system user can create other privileged users.
- Log.e(TAG_USER, "Only admin users and system user can create other admins.");
- return null;
- }
-
- UserInfo user = mUserManager.createUser(name, UserInfo.FLAG_ADMIN);
- if (user == null) {
- // Couldn't create user, most likely because there are too many.
- Log.w(TAG_USER, "can't create admin user.");
- return null;
- }
- assignDefaultIcon(user);
-
- return user;
- }
-
- /**
* Assigns a default icon to a user according to the user's id.
*
* @param userInfo User whose avatar is set to default icon.
- * @return Bitmap of the user icon.
*/
- private Bitmap assignDefaultIcon(UserInfo userInfo) {
+ private void assignDefaultIcon(UserInfo userInfo) {
int idForIcon = userInfo.isGuest() ? UserHandle.USER_NULL : userInfo.id;
Bitmap bitmap = UserIcons.convertToBitmap(
UserIcons.getDefaultUserIcon(mContext.getResources(), idForIcon, false));
mUserManager.setUserIcon(userInfo.id, bitmap);
- return bitmap;
}
private interface UserFilter {
@@ -1583,6 +1741,12 @@
checkAtLeastOnePermission(message, android.Manifest.permission.MANAGE_USERS);
}
+ private static void checkManageOrCreateUsersPermission(String message) {
+ checkAtLeastOnePermission(message,
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS);
+ }
+
private static void checkManageUsersOrDumpPermission(String message) {
checkAtLeastOnePermission(message,
android.Manifest.permission.MANAGE_USERS,
diff --git a/surround_view/service-impl/Android.bp b/surround_view/service-impl/Android.bp
index 8ae7bba..80c77a3 100644
--- a/surround_view/service-impl/Android.bp
+++ b/surround_view/service-impl/Android.bp
@@ -14,19 +14,89 @@
// limitations under the License.
//
-cc_binary {
- name: "android.automotive.sv.service@1.0-impl",
- vendor: true,
+cc_library {
+ name : "libobj_reader",
+ vendor : true,
srcs: [
+ "MtlReader.cpp",
+ "ObjReader.cpp",
+ ],
+ shared_libs : [
+ "libbase",
+ ]
+}
+
+cc_test{
+ name : "obj_reader_tests",
+ test_suites : ["device-tests"],
+ vendor : true,
+ srcs : ["ObjReaderTests.cpp"],
+ shared_libs : [
+ "libobj_reader",
+ "libcutils",
+ "libbase",
+ "libutils",
+ ],
+ required: [
+ "VolvoXC40_low.obj",
+ "VolvoXC40_low.mtl",
+ ],
+}
+
+cc_library{
+ name : "libvhal_handler",
+ vendor : true,
+ srcs : [
+ "VhalHandler.cpp",
+ ],
+ shared_libs : [
+ "android.hardware.automotive.vehicle@2.0",
+ "android.hidl.memory@1.0",
+ "libcutils",
+ "libbase",
+ "libbinder",
+ "libhidlbase",
+ "libhardware",
+ "libhidlmemory",
+ "libui",
+ "libutils",
+ ],
+}
+
+cc_test{
+ name : "vhal_handler_tests",
+ test_suites : ["device-tests"],
+ vendor : true,
+ srcs : ["VhalHandlerTests.cpp"],
+ shared_libs : [
+ "android.hardware.automotive.vehicle@2.0",
+ "libvhal_handler",
+ "libcutils",
+ "libbase",
+ "libbinder",
+ "libhidlbase",
+ "libhardware",
+ "libhidlmemory",
+ "libui",
+ "libutils",
+ ],
+}
+
+cc_binary{
+ name : "android.automotive.sv.service@1.0-impl",
+ vendor : true,
+ srcs : [
"CoreLibSetupHelper.cpp",
"SurroundViewService.cpp",
"SurroundView2dSession.cpp",
"SurroundView3dSession.cpp",
"service.cpp",
+
],
- init_rc: ["android.automotive.sv.service@1.0-impl.rc"],
- shared_libs: [
+ init_rc : ["android.automotive.sv.service@1.0-impl.rc"],
+ shared_libs : [
"android.hardware.automotive.sv@1.0",
+ "android.hardware.automotive.vehicle@2.0",
"android.hidl.memory@1.0",
"libbase",
"libbinder",
@@ -38,49 +108,43 @@
"libui",
"libutils",
],
- required: [
+ required : [
"cam0.png",
"cam1.png",
"cam2.png",
"cam3.png",
],
// Disable builds except for arm64 and emulator devices
- enabled: false,
- arch: {
- arm64: {
- enabled: true,
+ enabled : false,
+ arch : {
+ arm64 : {
+ enabled : true,
},
- x86: {
- enabled: true,
+ x86 : {
+ enabled : true,
},
- x86_64: {
- enabled: true,
+ x86_64 : {
+ enabled : true,
},
},
- vintf_fragments: [
+ vintf_fragments : [
"manifest_android.hardware.automotive.sv@1.0.xml",
],
}
-cc_prebuilt_library_shared {
- name: "libcore_lib_shared",
- proprietary: true,
- arch: {
- arm64: {
- srcs: ["lib/arm64/libcore_lib_shared.so"]
- },
- x86: {
- srcs: ["lib/x86/libcore_lib_shared.so"]
- },
- x86_64: {
- srcs: ["lib/x86-64/libcore_lib_shared.so"]
- },
+cc_prebuilt_library_shared{
+ name : "libcore_lib_shared",
+ proprietary : true,
+ arch : {
+ arm64 : {srcs : ["lib/arm64/libcore_lib_shared.so"]},
+ x86 : {srcs : ["lib/x86/libcore_lib_shared.so"]},
+ x86_64 : {srcs : ["lib/x86-64/libcore_lib_shared.so"]},
},
- shared_libs: [
- "libutils",
- "libcutils",
- "libbase",
- "libEGL",
+ shared_libs : [
+ "libutils",
+ "libcutils",
+ "libbase",
+ "libEGL",
"libGLESv2",
"libGLESv3",
"libc",
@@ -91,27 +155,37 @@
],
}
+prebuilt_etc{
+ name : "cam0.png",
+ src : "test_data/0.png",
+ sub_dir : "automotive/sv",
+}
+
+prebuilt_etc{
+ name : "cam1.png",
+ src : "test_data/1.png",
+ sub_dir : "automotive/sv",
+}
+
+prebuilt_etc{
+ name : "cam2.png",
+ src : "test_data/2.png",
+ sub_dir : "automotive/sv",
+}
+
prebuilt_etc {
- name: "cam0.png",
- src: "test_data/0.png",
+name:
+ "cam3.png", src : "test_data/3.png", sub_dir : "automotive/sv",
+}
+
+prebuilt_etc {
+ name: "cube.obj",
+ src: "test_data/cube.obj",
sub_dir: "automotive/sv",
}
prebuilt_etc {
- name: "cam1.png",
- src: "test_data/1.png",
+ name: "cube.mtl",
+ src: "test_data/cube.mtl",
sub_dir: "automotive/sv",
}
-
-prebuilt_etc {
- name: "cam2.png",
- src: "test_data/2.png",
- sub_dir: "automotive/sv",
-}
-
-prebuilt_etc {
- name: "cam3.png",
- src: "test_data/3.png",
- sub_dir: "automotive/sv",
-}
-
diff --git a/surround_view/service-impl/MtlReader.cpp b/surround_view/service-impl/MtlReader.cpp
new file mode 100644
index 0000000..d86a15a
--- /dev/null
+++ b/surround_view/service-impl/MtlReader.cpp
@@ -0,0 +1,167 @@
+/*
+ * 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 "MtlReader.h"
+
+#include <android-base/logging.h>
+#include <cstdio>
+
+#define LOG_TAG "MtlReader"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+namespace {
+
+constexpr int kCharBufferSize = 128;
+
+void ReadFloat3(FILE* file, float* value) {
+ float temp[3];
+ int res = fscanf(file, "%f %f %f", &temp[0], &temp[1], &temp[2]);
+ 3 == res ? std::memcpy(value, temp, 3 * sizeof(float)) : nullptr;
+}
+
+void ReadFloat(FILE* file, float* value) {
+ float temp;
+ int res = fscanf(file, "%f", &temp);
+ *value = res > 0 ? temp : -1;
+}
+
+void ReadInt(FILE* file, int* value) {
+ int temp;
+ int res = fscanf(file, "%d", &temp);
+ *value = res > 0 ? temp : -1;
+}
+
+void ReadString(FILE* file, std::string* value) {
+ char temp[kCharBufferSize];
+ fscanf(file, "%s", temp);
+ *value = temp;
+}
+} // namespace
+
+bool ReadMtlFromFile(const std::string& mtlFilename,
+ std::map<std::string, MtlConfigParams>* params) {
+ FILE* file = fopen(mtlFilename.c_str(), "r");
+ if (!file) {
+ LOG(ERROR) << "Failed to open mtl file: " << mtlFilename;
+ return false;
+ }
+
+ std::string currentConfig;
+ while (true) {
+ char lineHeader[kCharBufferSize];
+ // read the first word of the line
+ int res = fscanf(file, "%s", lineHeader);
+
+ if (res == EOF) {
+ break; // EOF = End Of File. Quit the loop.
+ }
+
+ if (strcmp(lineHeader, "#") == 0) {
+ fgets(lineHeader, sizeof(lineHeader), file);
+ continue;
+ }
+ if (strcmp(lineHeader, "newmtl") == 0) {
+ res = fscanf(file, "%s", lineHeader);
+ if (params->find(lineHeader) != params->end()) {
+ fclose(file);
+ LOG(ERROR) << "Duplicated params of : " << lineHeader[0];
+ return false;
+ }
+ currentConfig = lineHeader;
+ continue;
+ }
+
+ if (strcmp(lineHeader, "Ns") == 0) {
+ ReadFloat(file, &((*params)[currentConfig].ns));
+ continue;
+ }
+ if (strcmp(lineHeader, "Ni") == 0) {
+ ReadFloat(file, &((*params)[currentConfig].ni));
+ continue;
+ }
+ if (strcmp(lineHeader, "d") == 0) {
+ ReadFloat(file, &((*params)[currentConfig].d));
+ continue;
+ }
+ if (strcmp(lineHeader, "Tr") == 0) {
+ ReadFloat(file, &((*params)[currentConfig].tr));
+ continue;
+ }
+ if (strcmp(lineHeader, "Tf") == 0) {
+ ReadFloat3(file, (*params)[currentConfig].tf);
+ continue;
+ }
+ if (strcmp(lineHeader, "illum") == 0) {
+ ReadInt(file, &((*params)[currentConfig].illum));
+ continue;
+ }
+ if (strcmp(lineHeader, "Ka") == 0) {
+ ReadFloat3(file, (*params)[currentConfig].ka);
+ continue;
+ }
+ if (strcmp(lineHeader, "Kd") == 0) {
+ ReadFloat3(file, (*params)[currentConfig].kd);
+ continue;
+ }
+ if (strcmp(lineHeader, "Ks") == 0) {
+ ReadFloat3(file, (*params)[currentConfig].ks);
+ continue;
+ }
+ if (strcmp(lineHeader, "Ke") == 0) {
+ ReadFloat3(file, (*params)[currentConfig].ke);
+ continue;
+ }
+ if (strcmp(lineHeader, "map_bump") == 0) {
+ ReadString(file, &((*params)[currentConfig].mapBump));
+ continue;
+ }
+ if (strcmp(lineHeader, "bump") == 0) {
+ ReadString(file, &((*params)[currentConfig].bump));
+ continue;
+ }
+ if (strcmp(lineHeader, "map_Ka") == 0) {
+ ReadString(file, &((*params)[currentConfig].mapKa));
+ continue;
+ }
+ if (strcmp(lineHeader, "map_Kd") == 0) {
+ ReadString(file, &((*params)[currentConfig].mapKd));
+ continue;
+ }
+ if (strcmp(lineHeader, "map_Ks") == 0) {
+ ReadString(file, &((*params)[currentConfig].mapKs));
+ continue;
+ } else {
+ LOG(WARNING) << "Unknown tag " << lineHeader << ". Skipped";
+ fgets(lineHeader, sizeof(lineHeader), file);
+ continue;
+ }
+ }
+
+ fclose(file);
+ return true;
+}
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace sv
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/surround_view/service-impl/MtlReader.h b/surround_view/service-impl/MtlReader.h
new file mode 100644
index 0000000..3e19876
--- /dev/null
+++ b/surround_view/service-impl/MtlReader.h
@@ -0,0 +1,180 @@
+/*
+ * 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 SURROUND_VIEW_SERVICE_IMPL_MTLREADER_H_
+#define SURROUND_VIEW_SERVICE_IMPL_MTLREADER_H_
+
+#include <map>
+#include <string>
+
+#include "core_lib.h"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+// Mtl defined params.
+struct MtlConfigParams {
+ // Ns exponent
+ // Specifies the specular exponent for the current material. This defines
+ // the focus of the specular highlight.
+ // Ns values normally range from 0 to 1000.
+ float ns = -1;
+
+ // optical_density
+ // Specifies the optical density for the surface. This is also known as
+ // index of refraction.
+ // "optical_density" is the value for the optical density. The values can
+ // range from 0.001 to 10. A value of 1.0 means that light does not bend
+ // as it passes through an object. Increasing the optical_density
+ // increases the amount of bending. Glass has an index of refraction of
+ // about 1.5. Values of less than 1.0 produce bizarre results and are not
+ // recommended.
+ float ni = -1;
+
+ // d defines the non-transparency of the material to be alpha.
+ // The default is 1.0 (not transparent at all).
+ // The quantities d and Tr are the opposites of each other.
+ float d = -1;
+
+ // The Tr statement specifies the transparency of the material to be alpha.
+ // The default is 0.0 (not transparent at all).
+ // The quantities d and Tr are the opposites of each other,
+ float tr = -1;
+
+ // The Tf statement specifies the transmission filter using RGB values.
+ // "r g b" are the values for the red, green, and blue components of the
+ // atmosphere. The g and b arguments are optional. If only r is
+ // specified, then g, and b are assumed to be equal to r. The r g b values
+ // are normally in the range of 0.0 to 1.0. Values outside this range
+ // increase or decrease the relectivity accordingly.
+ float tf[3] = {-1, -1, -1};
+
+ // illum_#
+ // The "illum" statement specifies the illumination model to use in the
+ // material. Illumination models are mathematical equations that represent
+ // various material lighting and shading effects.
+ //
+ // "illum_#"can be a number from 0 to 10. The illumination models are
+ // summarized below;
+ //
+ // Illumination Properties that are turned on in the
+ // model Property Editor
+ //
+ // 0 Color on and Ambient off
+ // 1 Color on and Ambient on
+ // 2 Highlight on
+ // 3 Reflection on and Ray trace on
+ // 4 Transparency: Glass on
+ // Reflection: Ray trace on
+ // 5 Reflection: Fresnel on and Ray trace on
+ // 6 Transparency: Refraction on
+ // Reflection: Fresnel off and Ray trace on
+ // 7 Transparency: Refraction on
+ // Reflection: Fresnel on and Ray trace on
+ // 8 Reflection on and Ray trace off
+ // 9 Transparency: Glass on
+ // Reflection: Ray trace off
+ // 10 Casts shadows onto invisible surfaces
+ int illum = -1;
+
+ // The Ka statement specifies the ambient reflectivity using RGB values.
+ // "r g b" are the values for the red, green, and blue components of the
+ // color. The g and b arguments are optional. If only r is specified,
+ // then g, and b are assumed to be equal to r. The r g b values are
+ // normally in the range of 0.0 to 1.0. Values outside this range increase
+ // or decrease the relectivity accordingly.
+ float ka[3] = {-1, -1, -1};
+
+ // The Kd statement specifies the diffuse reflectivity using RGB values.
+ // "r g b" are the values for the red, green, and blue components of the
+ // atmosphere. The g and b arguments are optional. If only r is
+ // specified, then g, and b are assumed to be equal to r. The r g b values
+ // are normally in the range of 0.0 to 1.0. Values outside this range
+ // increase or decrease the relectivity accordingly.
+ float kd[3] = {-1, -1, -1};
+
+ // The Ks statement specifies the specular reflectivity using RGB values.
+ // "r g b" are the values for the red, green, and blue components of the
+ // atmosphere. The g and b arguments are optional. If only r is
+ // specified, then g, and b are assumed to be equal to r. The r g b values
+ // are normally in the range of 0.0 to 1.0. Values outside this range
+ // increase or decrease the relectivity accordingly.
+ float ks[3] = {-1, -1, -1};
+
+ // Emissive coeficient. It goes together with ambient, diffuse and specular
+ // and represents the amount of light emitted by the material.
+ float ke[3] = {-1, -1, -1};
+
+ // Specifies that a color texture file or color procedural texture file is
+ // linked to the specular reflectivity of the material. During rendering,
+ // the map_Ks value is multiplied by the Ks value.
+ std::string mapKs;
+
+ // Specifies that a color texture file or a color procedural texture file
+ // is applied to the ambient reflectivity of the material. During
+ // rendering, the "map_Ka" value is multiplied by the "Ka" value.
+ std::string mapKa;
+
+ // Specifies that a color texture file or color procedural texture file is
+ // linked to the diffuse reflectivity of the material. During rendering,
+ // the map_Kd value is multiplied by the Kd value.
+ std::string mapKd;
+
+ // Same as bump
+ std::string mapBump;
+
+ // Specifies that a bump texture file or a bump procedural texture file is
+ // linked to the material.
+ std::string bump;
+
+ MtlConfigParams& operator=(const MtlConfigParams& rhs) {
+ ns = rhs.ns;
+ ni = rhs.ni;
+ d = rhs.d;
+ tr = rhs.tr;
+ std::memcpy(tf, rhs.tf, 3 * sizeof(float));
+ illum = rhs.illum;
+ std::memcpy(ka, rhs.ka, 3 * sizeof(float));
+ std::memcpy(kd, rhs.kd, 3 * sizeof(float));
+ std::memcpy(ks, rhs.ks, 3 * sizeof(float));
+ std::memcpy(ke, rhs.ke, 3 * sizeof(float));
+ mapKs = rhs.mapKs;
+ mapKa = rhs.mapKa;
+ mapKd = rhs.mapKd;
+ mapBump = rhs.mapBump;
+ bump = rhs.bump;
+
+ return *this;
+ }
+};
+
+// Reads mtl file associated with obj file.
+// |filename| is the full path and name of the obj file.
+bool ReadMtlFromFile(const std::string& mtlFilename,
+ std::map<std::string, MtlConfigParams>* params);
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace sv
+} // namespace automotive
+} // namespace hardware
+} // namespace android
+
+#endif // SURROUND_VIEW_SERVICE_IMPL_MTLREADER_H_
diff --git a/surround_view/service-impl/ObjReader.cpp b/surround_view/service-impl/ObjReader.cpp
new file mode 100644
index 0000000..174ef5b
--- /dev/null
+++ b/surround_view/service-impl/ObjReader.cpp
@@ -0,0 +1,304 @@
+/*
+ * 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 "ObjReader.h"
+
+#include <array>
+#include <cstdio>
+#include <filesystem>
+#include <vector>
+
+#include "MtlReader.h"
+#include "core_lib.h"
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+using android_auto::surround_view::CarMaterial;
+using android_auto::surround_view::CarVertex;
+
+namespace {
+
+constexpr int kNumberOfVerticesPerFace = 3;
+constexpr int kNumberOfAxes = 3;
+constexpr int kCharBufferSize = 128;
+
+const std::array<float, 16> kMat4Identity = {
+ /*row 0*/ 1, 0, 0, 0,
+ /*row 1*/ 0, 1, 0, 0,
+ /*row 2*/ 0, 0, 1, 0,
+ /*row 3*/ 0, 0, 0, 1};
+
+// Copies face vertices parsed from obj to car vertices.
+void CopyFaceToCarVertex(const std::vector<std::array<float, kNumberOfAxes>>& currentVertices,
+ const std::vector<std::array<float, kNumberOfAxes>>& currentTextures,
+ const std::vector<std::array<float, kNumberOfAxes>>& currentNormals,
+ int vertexId, int textureId, int normalId, CarVertex* carVertex) {
+ std::memcpy(carVertex->pos.data(), currentVertices[vertexId - 1].data(),
+ currentVertices[vertexId - 1].size() * sizeof(float));
+
+ if (textureId != -1) {
+ std::memcpy(carVertex->tex_coord.data(), currentTextures[textureId - 1].data(),
+ 2 * sizeof(float));
+ // Set texture coodinates as invalid.
+ carVertex->tex_coord = {-1.0, -1.0};
+ }
+
+ std::memcpy(carVertex->normal.data(), currentNormals[normalId - 1].data(),
+ currentNormals[normalId - 1].size() * sizeof(float));
+}
+
+} // namespace
+
+bool ReadObjFromFile(const std::string& objFilename, std::map<std::string, CarPart>* carPartsMap) {
+ return ReadObjFromFile(objFilename, ReadObjOptions(), carPartsMap);
+}
+
+bool ReadObjFromFile(const std::string& objFilename, const ReadObjOptions& option,
+ std::map<std::string, CarPart>* carPartsMap) {
+ FILE* file = fopen(objFilename.c_str(), "r");
+ if (!file) {
+ LOG(ERROR) << "Failed to open obj file: " << objFilename;
+ return false;
+ }
+
+ for (int i = 0; i < kNumberOfAxes; ++i) {
+ if (option.coordinateMapping[i] >= kNumberOfAxes || option.coordinateMapping[i] < 0) {
+ fclose(file);
+ LOG(ERROR) << "coordinateMapping index must be less than 3 and greater or equal "
+ "to 0.";
+ return false;
+ }
+ }
+
+ std::vector<std::array<float, kNumberOfAxes>> currentVertices;
+ std::vector<std::array<float, kNumberOfAxes>> currentNormals;
+ std::vector<std::array<float, kNumberOfAxes>> currentTextures;
+ std::map<std::string, MtlConfigParams> mtlConfigParamsMap;
+ std::string currentGroupName;
+ MtlConfigParams currentMtlConfig;
+
+ while (true) {
+ char lineHeader[kCharBufferSize];
+ // read the first word of the line
+ int res = fscanf(file, "%s", lineHeader);
+
+ if (res == EOF) {
+ break; // EOF = End Of File. Quit the loop.
+ }
+ if (strcmp(lineHeader, "#") == 0) {
+ fgets(lineHeader, sizeof(lineHeader), file);
+ continue;
+ }
+
+ // TODO(b/156558814): add object type support.
+ // TODO(b/156559272): add document for supported format.
+ // Only single group per line is supported.
+ if (strcmp(lineHeader, "g") == 0) {
+ res = fscanf(file, "%s", lineHeader);
+ currentGroupName = lineHeader;
+ currentMtlConfig = MtlConfigParams();
+
+ if (carPartsMap->find(currentGroupName) != carPartsMap->end()) {
+ LOG(WARNING) << "Duplicate group name: " << currentGroupName
+ << ". using car part name as: " << currentGroupName << "_dup";
+ currentGroupName.append("_dup");
+ }
+ carPartsMap->emplace(
+ std::make_pair(currentGroupName,
+ CarPart((std::vector<CarVertex>()), CarMaterial(), kMat4Identity,
+ std::string(), std::vector<std::string>())));
+ continue;
+ }
+
+ // no "g" case, assign it as default.
+ if (currentGroupName.empty()) {
+ currentGroupName = "default";
+ currentMtlConfig = MtlConfigParams();
+ carPartsMap->emplace(
+ std::make_pair(currentGroupName,
+ CarPart((std::vector<CarVertex>()), CarMaterial(), kMat4Identity,
+ std::string(), std::vector<std::string>())));
+ }
+
+ if (strcmp(lineHeader, "usemtl") == 0) {
+ res = fscanf(file, "%s", lineHeader);
+
+ // If material name not found.
+ if (mtlConfigParamsMap.find(lineHeader) == mtlConfigParamsMap.end()) {
+ carPartsMap->at(currentGroupName).material = CarMaterial();
+ LOG(ERROR) << "Material not found: $0" << lineHeader;
+ return false;
+ }
+
+ currentMtlConfig = mtlConfigParamsMap[lineHeader];
+
+ carPartsMap->at(currentGroupName).material.ka = {currentMtlConfig.ka[0],
+ currentMtlConfig.ka[1],
+ currentMtlConfig.ka[2]};
+
+ carPartsMap->at(currentGroupName).material.kd = {currentMtlConfig.kd[0],
+ currentMtlConfig.kd[1],
+ currentMtlConfig.kd[2]};
+
+ carPartsMap->at(currentGroupName).material.ks = {currentMtlConfig.ks[0],
+ currentMtlConfig.ks[1],
+ currentMtlConfig.ks[2]};
+
+ carPartsMap->at(currentGroupName).material.d = currentMtlConfig.d;
+
+ carPartsMap->at(currentGroupName).material.textures.clear();
+
+ continue;
+ }
+
+ if (strcmp(lineHeader, "mtllib") == 0) {
+ res = fscanf(file, "%s", lineHeader);
+ mtlConfigParamsMap.clear();
+ std::string mtlFilename;
+ if (option.mtlFilename.empty()) {
+ mtlFilename = objFilename.substr(0, objFilename.find_last_of("/"));
+ mtlFilename.append("/");
+ mtlFilename.append(lineHeader);
+ } else {
+ mtlFilename = option.mtlFilename;
+ }
+ if (!ReadMtlFromFile(mtlFilename, &mtlConfigParamsMap)) {
+ LOG(ERROR) << "Parse MTL file " << mtlFilename << " failed.";
+ return false;
+ }
+ continue;
+ }
+
+ if (strcmp(lineHeader, "v") == 0) {
+ std::array<float, kNumberOfAxes> pos;
+ fscanf(file, "%f %f %f\n", &pos[option.coordinateMapping[0]],
+ &pos[option.coordinateMapping[1]], &pos[option.coordinateMapping[2]]);
+ for (int i = 0; i < kNumberOfAxes; ++i) {
+ pos[i] *= option.scales[i];
+ pos[i] += option.offsets[i];
+ }
+ currentVertices.push_back(pos);
+ } else if (strcmp(lineHeader, "vt") == 0) {
+ std::array<float, kNumberOfAxes> texture;
+ fscanf(file, "%f %f %f\n", &texture[0], &texture[1], &texture[2]);
+ currentTextures.push_back(texture);
+ } else if (strcmp(lineHeader, "vn") == 0) {
+ std::array<float, kNumberOfAxes> normal;
+ fscanf(file, "%f %f %f\n", &normal[option.coordinateMapping[0]],
+ &normal[option.coordinateMapping[1]], &normal[option.coordinateMapping[2]]);
+ currentNormals.push_back(normal);
+ } else if (strcmp(lineHeader, "f") == 0) {
+ int vertexId[kNumberOfVerticesPerFace];
+ int textureId[kNumberOfVerticesPerFace] = {-1, -1, -1};
+ int normalId[kNumberOfVerticesPerFace];
+
+ // Face vertices supported formats:
+ // With texture: pos/texture/normal
+ // Without texture: pos//normal
+
+ // Scan first vertex position.
+ int matches = fscanf(file, "%d/", &vertexId[0]);
+
+ if (matches != 1) {
+ LOG(WARNING) << "Face index error. Skipped.";
+ fgets(lineHeader, sizeof(lineHeader), file);
+ continue;
+ }
+
+ // Try scanning first two face 2 vertices with texture format present.
+ bool isTexturePresent = true;
+ matches = fscanf(file, "%d/%d %d/%d/%d", &textureId[0], &normalId[0], &vertexId[1],
+ &textureId[1], &normalId[1]);
+
+ // If 5 matches not found, try scanning first 2 face vertices without
+ // texture format.
+ if (matches != 5) {
+ matches = fscanf(file, "/%d %d//%d", &normalId[0], &vertexId[1], &normalId[1]);
+
+ // If 3 matches not found return with error.
+ if (matches != 3) {
+ LOG(WARNING) << "Face format not supported. Skipped.";
+ fgets(lineHeader, sizeof(lineHeader), file);
+ continue;
+ }
+
+ isTexturePresent = false;
+ }
+
+ // Copy first two face vertices to car vertices.
+ std::array<CarVertex, kNumberOfVerticesPerFace> carVertices;
+ CopyFaceToCarVertex(currentVertices, currentTextures, currentNormals, vertexId[0],
+ textureId[0], normalId[0], &carVertices[0]);
+ CopyFaceToCarVertex(currentVertices, currentTextures, currentNormals, vertexId[1],
+ textureId[1], normalId[1], &carVertices[1]);
+
+ // Add a triangle that the first two vertices make with every subsequent
+ // face vertex 3 and onwards. Note this assumes the face is a convex
+ // polygon.
+ do {
+ if (isTexturePresent) {
+ matches = fscanf(file, " %d/%d/%d", &vertexId[2], &textureId[2], &normalId[2]);
+ // Warn if un-expected number of matches.
+ if (matches != 3 && matches != 0) {
+ LOG(WARNING) << "Face matches, expected 3, read: " << matches;
+ break;
+ }
+ } else {
+ // Warn if un-expected number of matches.
+ matches = fscanf(file, " %d//%d", &vertexId[2], &normalId[2]);
+ if (matches != 2 && matches != 0) {
+ LOG(WARNING) << "Face matches, expected 2, read: " << matches;
+ break;
+ }
+ }
+
+ if (matches == 0) {
+ break;
+ }
+
+ CopyFaceToCarVertex(currentVertices, currentTextures, currentNormals, vertexId[2],
+ textureId[2], normalId[2], &carVertices[2]);
+
+ carPartsMap->at(currentGroupName).vertices.push_back(carVertices[0]);
+ carPartsMap->at(currentGroupName).vertices.push_back(carVertices[1]);
+ carPartsMap->at(currentGroupName).vertices.push_back(carVertices[2]);
+
+ carVertices[1] = carVertices[2];
+ } while (true);
+
+ } else {
+ // LOG(WARNING) << "Unknown tag " << lineHeader << ". Skipped";
+ fgets(lineHeader, sizeof(lineHeader), file);
+ continue;
+ }
+ }
+
+ fclose(file);
+ return true;
+}
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace sv
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/surround_view/service-impl/ObjReader.h b/surround_view/service-impl/ObjReader.h
new file mode 100644
index 0000000..c19be14
--- /dev/null
+++ b/surround_view/service-impl/ObjReader.h
@@ -0,0 +1,78 @@
+/*
+ * 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 SURROUND_VIEW_SERVICE_IMPL_OBJREADER_H_
+#define SURROUND_VIEW_SERVICE_IMPL_OBJREADER_H_
+
+#include <map>
+#include <string>
+
+#include "core_lib.h"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+using android_auto::surround_view::CarPart;
+
+// ReadObjOptions for processing obj's vertex coordinates.
+// Sequence of processing ReadObjOptions:
+// 1. coordinate_mapping
+// 2. scales
+// 3. offsets
+struct ReadObjOptions {
+ // Maps obj coordinates to the output overlay coordinate.
+ // 0 <-> x, 1 <-> y, 2 <-> z
+ // Default is {0, 1, 2}, without coordinate changes.
+ int coordinateMapping[3] = {0, 1, 2};
+
+ // scale of each coordinate (after offsets).
+ float scales[3] = {1.0f, 1.0f, 1.0f};
+
+ // offset of each coordinate (after mapping).
+ float offsets[3] = {0, 0, 0};
+
+ // Optional mtl filename. String name is obj file is used if this is empty.
+ std::string mtlFilename;
+};
+
+// Reads obj file to vector of OverlayVertex.
+// |obj_filename| is the full path and name of the obj file.
+// |car_parts_map| is a map containing all car parts.
+// Now it only supports two face formats:
+// 1. f x/x/x x/x/x x/x/x ...
+// 2. f x//x x//x x//x ...
+// b/
+bool ReadObjFromFile(const std::string& objFilename, std::map<std::string, CarPart>* carPartsMap);
+
+// Reads obj file to vector of OverlayVertex.
+// |obj_filename| is the full path and name of the obj file.
+// |option| provides optional changes on the coordinates.
+// |car_parts_map| is a map containing all car parts.
+bool ReadObjFromFile(const std::string& obFilename, const ReadObjOptions& option,
+ std::map<std::string, CarPart>* carPartsMap);
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace sv
+} // namespace automotive
+} // namespace hardware
+} // namespace android
+
+#endif // SURROUND_VIEW_SERVICE_IMPL_OBJREADER_H_
diff --git a/surround_view/service-impl/ObjReaderTests.cpp b/surround_view/service-impl/ObjReaderTests.cpp
new file mode 100644
index 0000000..9dae171
--- /dev/null
+++ b/surround_view/service-impl/ObjReaderTests.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "ObjReaderTests"
+
+#include "ObjReader.h"
+
+#include "MtlReader.h"
+#include "core_lib.h"
+
+#include <gtest/gtest.h>
+#include <map>
+#include <string>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+namespace {
+
+TEST(ObjParserTests, ReadCubeSuccess) {
+ std::map<std::string, CarPart> carPartsMap;
+ EXPECT_TRUE(ReadObjFromFile("/etc/automotive/sv/cube.obj", &carPartsMap));
+ EXPECT_NE(carPartsMap.size(), 0);
+}
+
+} // namespace
+} // namespace implementation
+} // namespace V1_0
+} // namespace sv
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/surround_view/service-impl/VhalHandler.cpp b/surround_view/service-impl/VhalHandler.cpp
new file mode 100644
index 0000000..f1294d9
--- /dev/null
+++ b/surround_view/service-impl/VhalHandler.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "VhalHandler"
+
+#include "VhalHandler.h"
+
+#include <chrono>
+#include <cmath>
+#include <condition_variable>
+#include <mutex>
+
+#include <android-base/logging.h>
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+#include <time.h>
+#include <utils/SystemClock.h>
+#include <utils/Timers.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+using vehicle::V2_0::IVehicle;
+using vehicle::V2_0::StatusCode;
+using vehicle::V2_0::VehiclePropertyType;
+using vehicle::V2_0::VehiclePropValue;
+
+bool VhalHandler::initialize(UpdateMethod updateMethod, int rate) {
+ LOG(DEBUG) << __FUNCTION__;
+ std::scoped_lock<std::mutex> lock(mAccessLock);
+
+ if (mIsInitialized) {
+ LOG(ERROR) << "Vehicle Handler is already initialized.";
+ return false;
+ }
+
+ LOG(INFO) << "Connecting to Vehicle HAL";
+ mVhalServicePtr = IVehicle::getService();
+ if (mVhalServicePtr.get() == nullptr) {
+ LOG(ERROR) << "Vehicle HAL getService failed.";
+ return false;
+ }
+
+ if (rate < 1 || rate > 100) {
+ LOG(ERROR) << "Rate must be in the range [1, 100].";
+ return false;
+ }
+
+ if (mUpdateMethod == UpdateMethod::SUBSCRIBE) {
+ LOG(ERROR) << "Update method Subscribe is not currently implemented.";
+ return false;
+ }
+
+ mUpdateMethod = updateMethod;
+ mRate = rate;
+ mIsInitialized = true;
+ mIsUpdateActive = false;
+
+ return true;
+}
+
+void VhalHandler::pollProperties() {
+ LOG(DEBUG) << "Polling thread started.";
+ while (true) {
+ nsecs_t startTime = elapsedRealtimeNano();
+
+ // Copy properties to read.
+ std::vector<VehiclePropValue> propertiesToRead;
+ int rate;
+ {
+ std::scoped_lock<std::mutex> lock(mAccessLock);
+ if (!mIsUpdateActive) {
+ LOG(DEBUG) << "Exiting polling thread.";
+ break;
+ }
+ propertiesToRead = mPropertiesToRead;
+ rate = mRate;
+ }
+
+ // Make get call for each VHAL property.
+ // Write to back property values, note lock is not needed as only this thread uses it.
+ std::vector<VehiclePropValue> vehiclePropValuesUpdated;
+ for (auto& propertyToRead : propertiesToRead) {
+ // VehiclePropValue vehiclePropValue;
+ mVhalServicePtr->get(propertyToRead,
+ [&vehiclePropValuesUpdated](StatusCode status,
+ const VehiclePropValue& propValue) {
+ if (status != StatusCode::OK) {
+ LOG(ERROR) << "Failed to read vhal property: "
+ << propValue.prop << ", with status code: "
+ << static_cast<int32_t>(status);
+ } else {
+ vehiclePropValuesUpdated.push_back(propValue);
+ }
+ });
+ }
+
+ // Update property values by swapping with updated property values.
+ {
+ std::scoped_lock<std::mutex> lock(mAccessLock);
+ std::swap(mPropertyValues, vehiclePropValuesUpdated);
+ }
+
+ std::unique_lock<std::mutex> sleepLock(mPollThreadSleepMutex);
+ // Sleep to generate frames at kTargetFrameRate.
+ // rate is number of updates per seconds,
+ // Target time period between two updates in nano-seconds = (10 ^ 9) / rate.
+ const nsecs_t kTargetRateNs = std::pow(10, 9) / mRate;
+ const nsecs_t now = elapsedRealtimeNano();
+ const nsecs_t workTimeNs = now - startTime;
+ const nsecs_t sleepDurationNs = kTargetRateNs - workTimeNs;
+ if (sleepDurationNs > 0) {
+ // Sleep for sleepDurationNs or until a stop signal is received.
+ mPollThreadCondition.wait_for(sleepLock, std::chrono::nanoseconds(sleepDurationNs),
+ [this]() { return mPollStopSleeping; });
+ }
+ }
+}
+
+bool VhalHandler::startPropertiesUpdate() {
+ LOG(DEBUG) << __FUNCTION__;
+ std::scoped_lock<std::mutex> lock(mAccessLock);
+
+ // Check Vhal service is initialized.
+ if (!mIsInitialized) {
+ LOG(ERROR) << "VHAL handler not initialized.";
+ return false;
+ }
+
+ if (mIsUpdateActive) {
+ LOG(ERROR) << "Polling is already started.";
+ return false;
+ }
+
+ mIsUpdateActive = true;
+
+ {
+ std::scoped_lock<std::mutex> sleepLock(mPollThreadSleepMutex);
+ mPollStopSleeping = false;
+ }
+
+ // Start polling thread if updated method is GET.
+ if (mUpdateMethod == UpdateMethod::GET) {
+ mPollingThread = std::thread([this]() { pollProperties(); });
+ }
+
+ return true;
+}
+
+bool VhalHandler::setPropertiesToRead(const std::vector<VehiclePropValue>& propertiesToRead) {
+ LOG(DEBUG) << __FUNCTION__;
+ std::scoped_lock<std::mutex> lock(mAccessLock);
+
+ // Replace property ids to read.
+ mPropertiesToRead = propertiesToRead;
+
+ return true;
+}
+
+bool VhalHandler::getPropertyValues(std::vector<VehiclePropValue>* property_values) {
+ LOG(DEBUG) << __FUNCTION__;
+ std::scoped_lock<std::mutex> lock(mAccessLock);
+
+ // Check Vhal service is initialized.
+ if (!mIsInitialized) {
+ LOG(ERROR) << "VHAL handler not initialized.";
+ return false;
+ }
+
+ // Copy current property values to argument.
+ *property_values = mPropertyValues;
+
+ return true;
+}
+
+bool VhalHandler::stopPropertiesUpdate() {
+ LOG(DEBUG) << __FUNCTION__;
+ {
+ std::scoped_lock<std::mutex> lock(mAccessLock);
+
+ // Check Vhal service is initialized.
+ if (!mIsInitialized) {
+ LOG(ERROR) << "VHAL handler not initialized.";
+ return false;
+ }
+
+ if (!mIsUpdateActive) {
+ LOG(ERROR) << "Polling is already stopped.";
+ return false;
+ }
+
+ mIsUpdateActive = false;
+ }
+
+ // Wake up the polling thread.
+ {
+ std::scoped_lock<std::mutex> sleepLock(mPollThreadSleepMutex);
+ mPollStopSleeping = true;
+ }
+ mPollThreadCondition.notify_one();
+
+ // Wait for polling thread to exit.
+ mPollingThread.join();
+
+ return true;
+}
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace sv
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/surround_view/service-impl/VhalHandler.h b/surround_view/service-impl/VhalHandler.h
new file mode 100644
index 0000000..23941f0
--- /dev/null
+++ b/surround_view/service-impl/VhalHandler.h
@@ -0,0 +1,110 @@
+/*
+ * 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 SURROUND_VIEW_SERVICE_IMPL_VHALHANDLER_H_
+#define SURROUND_VIEW_SERVICE_IMPL_VHALHANDLER_H_
+
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+
+using android::sp;
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+// Vhal handler cache vhal properties needed and updates them at a fixed rate.
+class VhalHandler {
+public:
+ // Enumeration for the method to use for updating the VHAL properties,
+ enum UpdateMethod {
+ // Makes a periodic get call in a polling thread.
+ // Use when VHAL implementation does not support multiple clients in subscribe calls.
+ GET = 0,
+
+ // Subscribes to the VHAL properties, to receive values periodically in a callback.
+ // Use when VHAL implementation support multiple clients in subscribe calls.
+ // NOTE: Currently not implemented.
+ SUBSCRIBE
+ };
+
+ // Empty vhal handler constructor.
+ VhalHandler() : mIsInitialized(false), mUpdateMethod(GET), mRate(0), mIsUpdateActive(false) {}
+
+ // Initializes the VHAL handler.
+ // Valid range of rate is [1, 100] Hz.
+ // For subscribe the rate must be within each properties min and maximum sampling rate.
+ // For get, higher rate may result in excessive binder calls and increased latency.
+ bool initialize(UpdateMethod updateMethod, int rate);
+
+ // List of VHAL properties to read, can include vendor specific VHAL properties.
+ // The updated method determines if properties are updated using get or subscribe calls.
+ bool setPropertiesToRead(const std::vector<vehicle::V2_0::VehiclePropValue>& propertiesToRead);
+
+ // Starts updating the VHAL properties with the specified rate.
+ bool startPropertiesUpdate();
+
+ // Gets the last updated VHAL property values.
+ // property_values is empty if startPropertiesUpdate() has not been called.
+ bool getPropertyValues(std::vector<vehicle::V2_0::VehiclePropValue>* property_values);
+
+ // Stops updating the VHAL properties.
+ // For Get method, waits for the polling thread to exit.
+ bool stopPropertiesUpdate();
+
+private:
+ // Thread function to poll properties.
+ void pollProperties();
+
+ // Pointer to VHAL service.
+ sp<vehicle::V2_0::IVehicle> mVhalServicePtr;
+
+ // Mutex for locking VHAL properties data.
+ std::mutex mAccessLock;
+
+ // Initialized parameters.
+ bool mIsInitialized;
+ UpdateMethod mUpdateMethod;
+ int mRate;
+ bool mIsUpdateActive;
+
+ // GET method related data members.
+ std::thread mPollingThread;
+ std::mutex mPollThreadSleepMutex;
+ std::condition_variable mPollThreadCondition;
+ bool mPollStopSleeping;
+
+ // List of properties to read.
+ std::vector<vehicle::V2_0::VehiclePropValue> mPropertiesToRead;
+
+ // Updated list of property values.
+ std::vector<vehicle::V2_0::VehiclePropValue> mPropertyValues;
+};
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace sv
+} // namespace automotive
+} // namespace hardware
+} // namespace android
+
+#endif // SURROUND_VIEW_SERVICE_IMPL_VHALHANDLER_H_
diff --git a/surround_view/service-impl/VhalHandlerTests.cpp b/surround_view/service-impl/VhalHandlerTests.cpp
new file mode 100644
index 0000000..2d0ef40
--- /dev/null
+++ b/surround_view/service-impl/VhalHandlerTests.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VhalHandlerTests"
+
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+#include "VhalHandler.h"
+
+#include <gtest/gtest.h>
+#include <time.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+namespace {
+
+void SetSamplePropertiesToRead(VhalHandler* vhalHandler) {
+ std::vector<vehicle::V2_0::VehiclePropValue> properties_to_read;
+ vehicle::V2_0::VehiclePropValue property_read;
+ property_read.prop = static_cast<int32_t>(vehicle::V2_0::VehicleProperty::INFO_MODEL);
+ properties_to_read.push_back(property_read);
+ ASSERT_TRUE(vhalHandler->setPropertiesToRead(properties_to_read));
+}
+
+TEST(VhalhandlerTests, UninitializedStartFail) {
+ VhalHandler vhalHandler;
+ ASSERT_FALSE(vhalHandler.startPropertiesUpdate());
+}
+
+TEST(VhalhandlerTests, StartStopSuccess) {
+ VhalHandler vhalHandler;
+ ASSERT_TRUE(vhalHandler.initialize(VhalHandler::UpdateMethod::GET, 10));
+ SetSamplePropertiesToRead(&vhalHandler);
+ ASSERT_TRUE(vhalHandler.startPropertiesUpdate());
+ ASSERT_TRUE(vhalHandler.stopPropertiesUpdate());
+}
+
+TEST(VhalhandlerTests, StopTwiceFail) {
+ VhalHandler vhalHandler;
+ ASSERT_TRUE(vhalHandler.initialize(VhalHandler::UpdateMethod::GET, 10));
+ SetSamplePropertiesToRead(&vhalHandler);
+ ASSERT_TRUE(vhalHandler.startPropertiesUpdate());
+ ASSERT_TRUE(vhalHandler.stopPropertiesUpdate());
+ ASSERT_FALSE(vhalHandler.stopPropertiesUpdate());
+}
+
+TEST(VhalhandlerTests, NoStartFail) {
+ VhalHandler vhalHandler;
+ ASSERT_TRUE(vhalHandler.initialize(VhalHandler::UpdateMethod::GET, 10));
+ SetSamplePropertiesToRead(&vhalHandler);
+ ASSERT_FALSE(vhalHandler.stopPropertiesUpdate());
+}
+
+TEST(VhalhandlerTests, StartAgainSuccess) {
+ VhalHandler vhalHandler;
+ ASSERT_TRUE(vhalHandler.initialize(VhalHandler::UpdateMethod::GET, 10));
+ SetSamplePropertiesToRead(&vhalHandler);
+ ASSERT_TRUE(vhalHandler.startPropertiesUpdate());
+ ASSERT_TRUE(vhalHandler.stopPropertiesUpdate());
+ ASSERT_TRUE(vhalHandler.startPropertiesUpdate());
+ ASSERT_TRUE(vhalHandler.stopPropertiesUpdate());
+}
+
+TEST(VhalhandlerTests, GetMethodSuccess) {
+ VhalHandler vhalHandler;
+ ASSERT_TRUE(vhalHandler.initialize(VhalHandler::UpdateMethod::GET, 10));
+
+ SetSamplePropertiesToRead(&vhalHandler);
+
+ ASSERT_TRUE(vhalHandler.startPropertiesUpdate());
+ sleep(1);
+ std::vector<vehicle::V2_0::VehiclePropValue> property_values;
+ EXPECT_TRUE(vhalHandler.getPropertyValues(&property_values));
+ EXPECT_EQ(property_values.size(), 1);
+
+ EXPECT_TRUE(vhalHandler.stopPropertiesUpdate());
+}
+
+} // namespace
+} // namespace implementation
+} // namespace V1_0
+} // namespace sv
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/surround_view/service-impl/lib/arm64/libcore_lib_shared.so b/surround_view/service-impl/lib/arm64/libcore_lib_shared.so
index 2421d41..3988762 100755
--- 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_shared.so b/surround_view/service-impl/lib/x86-64/libcore_lib_shared.so
index 34d1f6a..e9f94db 100755
--- a/surround_view/service-impl/lib/x86-64/libcore_lib_shared.so
+++ 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_shared.so b/surround_view/service-impl/lib/x86/libcore_lib_shared.so
index c6ba2b6..b82d025 100755
--- a/surround_view/service-impl/lib/x86/libcore_lib_shared.so
+++ b/surround_view/service-impl/lib/x86/libcore_lib_shared.so
Binary files differ
diff --git a/surround_view/service-impl/test_data/cube.mtl b/surround_view/service-impl/test_data/cube.mtl
new file mode 100644
index 0000000..2f4e865
--- /dev/null
+++ b/surround_view/service-impl/test_data/cube.mtl
@@ -0,0 +1,5 @@
+newmtl flatwhite
+d 1.0000
+illum 1
+Ka 0.5000 0.5000 0.5000
+Kd 1.0000 1.0000 1.0000
diff --git a/surround_view/service-impl/test_data/cube.obj b/surround_view/service-impl/test_data/cube.obj
new file mode 100644
index 0000000..60ae1b2
--- /dev/null
+++ b/surround_view/service-impl/test_data/cube.obj
@@ -0,0 +1,32 @@
+mtllib cube.mtl
+g cube
+
+v 0.0 0.0 0.0
+v 0.0 0.0 1.0
+v 0.0 1.0 0.0
+v 0.0 1.0 1.0
+v 1.0 0.0 0.0
+v 1.0 0.0 1.0
+v 1.0 1.0 0.0
+v 1.0 1.0 1.0
+
+vn 0.0 0.0 1.0
+vn 0.0 0.0 -1.0
+vn 0.0 1.0 0.0
+vn 0.0 -1.0 0.0
+vn 1.0 0.0 0.0
+vn -1.0 0.0 0.0
+usemtl flatwhite
+f 1//2 7//2 5//2
+f 1//2 3//2 7//2
+f 1//6 4//6 3//6
+f 1//6 2//6 4//6
+f 3//3 8//3 7//3
+f 3//3 4//3 8//3
+f 5//5 7//5 8//5
+f 5//5 8//5 6//5
+f 1//4 5//4 6//4
+f 1//4 6//4 2//4
+f 2//1 6//1 8//1
+f 2//1 8//1 4//1
+
diff --git a/tests/CarDeveloperOptions/res/values-b+sr+Latn/strings.xml b/tests/CarDeveloperOptions/res/values-b+sr+Latn/strings.xml
index 8861afb..4c69e86 100644
--- a/tests/CarDeveloperOptions/res/values-b+sr+Latn/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-b+sr+Latn/strings.xml
@@ -3190,7 +3190,7 @@
<string name="other_sound_settings" msgid="5250376066099818676">"Drugi zvukovi"</string>
<string name="dial_pad_tones_title" msgid="8877212139988655769">"Tonovi numeričke tastature"</string>
<string name="screen_locking_sounds_title" msgid="4407110895465866809">"Zvukovi zaključavanja ekrana"</string>
- <string name="charging_sounds_title" msgid="5070437987230894287">"Menjajući zvuci i vibracija"</string>
+ <string name="charging_sounds_title" msgid="5070437987230894287">"Zvukovi i vibracija punjenja"</string>
<string name="docking_sounds_title" msgid="2573137471605541366">"Zvukovi montiranja"</string>
<string name="touch_sounds_title" msgid="165237488496165652">"Zvukovi pri dodiru"</string>
<string name="vibrate_on_touch_title" msgid="6360155469279157684">"Vibracija pri dodiru"</string>
diff --git a/tests/CarDeveloperOptions/res/values-be/strings.xml b/tests/CarDeveloperOptions/res/values-be/strings.xml
index ec27b37..8c34666 100644
--- a/tests/CarDeveloperOptions/res/values-be/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-be/strings.xml
@@ -3394,10 +3394,10 @@
<string name="hide_silent_icons_title" msgid="1070905516921542662">"Хаваць значкі стану для апавяшчэнняў без гуку"</string>
<string name="hide_silent_icons_summary" msgid="2624346914488256888">"Хаваць значкі для апавяшчэнняў без гуку на панэлі стану"</string>
<string name="notification_badging_title" msgid="6311699476970264712">"Паказваць значкі апавяшчэнняў"</string>
- <string name="notification_bubbles_title" msgid="9196562435741861317">"Дыялогі"</string>
+ <string name="notification_bubbles_title" msgid="9196562435741861317">"Усплывальныя апавяшчэнні"</string>
<string name="notification_bubbles_summary" msgid="4624512775901949578">"Адусюль атрымлівайце хуткі доступ да змесціва праграмы з дапамогай зменлівых спалучэнняў клавіш"</string>
<string name="bubbles_feature_education" msgid="8979109826818881018">"Некаторыя апавяшчэнні і іншае змесціва могуць паказвацца на экране ў выглядзе дыялогаў. Каб адкрыць дыялог, націсніце на яго. Каб закрыць дыялог, перацягніце яго ўніз экрана."</string>
- <string name="bubbles_app_toggle_title" msgid="6401217027603326439">"Дыялогі"</string>
+ <string name="bubbles_app_toggle_title" msgid="6401217027603326439">"Усплывальныя апавяшчэнні"</string>
<string name="bubbles_app_toggle_summary" msgid="7707611139796553855">"Дазваляе праграме \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" паказваць некаторыя апавяшчэнні ў выглядзе дыялогаў"</string>
<string name="bubbles_feature_disabled_dialog_title" msgid="3375452386012079293">"Уключыце дыялогі"</string>
<string name="bubbles_feature_disabled_dialog_text" msgid="326945485806386477">"Каб уключыць дыялогі для гэтай праграмы, уключыце дыялогі для прылады. Гэта паўплывае на іншыя праграмы, у якіх вы раней уключалі дыялогі."</string>
diff --git a/tests/CarDeveloperOptions/res/values-bn/strings.xml b/tests/CarDeveloperOptions/res/values-bn/strings.xml
index 78b74e4..c86b976 100644
--- a/tests/CarDeveloperOptions/res/values-bn/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-bn/strings.xml
@@ -3041,7 +3041,7 @@
<string name="connected_devices_dashboard_no_driving_mode_summary" msgid="3524409078596318803">"ব্লুটুথ, এনএফসি"</string>
<string name="connected_devices_dashboard_no_driving_mode_no_nfc_summary" msgid="7881286613528299400">"ব্লুটুথ"</string>
<string name="app_and_notification_dashboard_title" msgid="8448096608058843730">"অ্যাপ ও বিজ্ঞপ্তি"</string>
- <string name="app_and_notification_dashboard_summary" msgid="4165181440955038145">"অ্যাসিস্ট্যান্ট, সাম্প্রতিক অ্যাপ, ডিফল্ট অ্যাপ"</string>
+ <string name="app_and_notification_dashboard_summary" msgid="4165181440955038145">"Assistant, সাম্প্রতিক অ্যাপ, ডিফল্ট অ্যাপ"</string>
<string name="notification_settings_work_profile" msgid="7190550347842400029">"কাজের প্রোফাইলে অ্যাপের জন্য বিজ্ঞপ্তি অ্যাক্সেস উপলভ্য নয়।"</string>
<string name="account_dashboard_title" msgid="4734300939532555885">"অ্যাকাউন্ট"</string>
<string name="account_dashboard_default_summary" msgid="6822549669771936206">"কোনও অ্যাকাউন্ট যোগ করা হয়নি"</string>
@@ -4325,7 +4325,7 @@
<string name="battery_suggestion_summary" msgid="2669070349482656490"></string>
<string name="gesture_prevent_ringing_screen_title" msgid="4173494225145223638">"রিং হওয়া বন্ধ করুন"</string>
<string name="gesture_prevent_ringing_title" msgid="8827963588425673557">"রিং হওয়া বন্ধ করতে পাওয়ার ও ভলিউম বাড়ানোর বোতাম একসাথে প্রেস করুন"</string>
- <string name="gesture_prevent_ringing_sound_title" msgid="8642330448721033641">"রিং হওয়া আটকানোর শর্টকাট"</string>
+ <string name="gesture_prevent_ringing_sound_title" msgid="8642330448721033641">"রিংয়ের আরওয়াজ আটকানোর শর্টকাট"</string>
<string name="prevent_ringing_option_vibrate" msgid="6456505293904544108">"ভাইব্রেশন হতে দিন"</string>
<string name="prevent_ringing_option_mute" msgid="53662688921253613">"মিউট করুন"</string>
<string name="prevent_ringing_option_none" msgid="1450985763137666231">"যেমন আছে থাক"</string>
diff --git a/tests/CarDeveloperOptions/res/values-bs/strings.xml b/tests/CarDeveloperOptions/res/values-bs/strings.xml
index ba804e6..0bee24c 100644
--- a/tests/CarDeveloperOptions/res/values-bs/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-bs/strings.xml
@@ -3190,9 +3190,9 @@
<string name="other_sound_settings" msgid="5250376066099818676">"Drugi zvukovi"</string>
<string name="dial_pad_tones_title" msgid="8877212139988655769">"Tonovi tastature telefona"</string>
<string name="screen_locking_sounds_title" msgid="4407110895465866809">"Zvukovi zaključavanja ekrana"</string>
- <string name="charging_sounds_title" msgid="5070437987230894287">"Zvukovi/vibracija pri punjenju"</string>
+ <string name="charging_sounds_title" msgid="5070437987230894287">"Zvukovi/vibracija prilikom punjenja"</string>
<string name="docking_sounds_title" msgid="2573137471605541366">"Zvukovi priključne stanice"</string>
- <string name="touch_sounds_title" msgid="165237488496165652">"Zvukovi pri dodiru"</string>
+ <string name="touch_sounds_title" msgid="165237488496165652">"Zvukovi dodira"</string>
<string name="vibrate_on_touch_title" msgid="6360155469279157684">"Vibracija pri dodiru"</string>
<string name="vibrate_on_touch_summary" msgid="5504424764028676043">"Haptičke povratne informacije za dodir, tastaturu i drugo"</string>
<string name="dock_audio_media_title" msgid="1859521680502040781">"Zvučnik priključne stanice reprodukuje zvuk"</string>
@@ -3470,8 +3470,8 @@
<string name="notification_content_block_summary" msgid="2743896875255591743">"Nikad ne prikazuj obavještenja u nijansi ili na perifernim uređajima"</string>
<string name="notification_badge_title" msgid="8989086619255666442">"Dozvoli tačku za obavještenja"</string>
<string name="notification_channel_badge_title" msgid="8228215248332054612">"Prikaži tačku za obavještenja"</string>
- <string name="app_notification_override_dnd_title" msgid="1757042206738172601">"Zamijeni način rada Ne ometaj"</string>
- <string name="app_notification_override_dnd_summary" msgid="3152957611171210980">"Dozvolite da se obavještenja nastave pojavljivati kada je uključen način rada Ne ometaj"</string>
+ <string name="app_notification_override_dnd_title" msgid="1757042206738172601">"Zanemari način rada Ne ometaj"</string>
+ <string name="app_notification_override_dnd_summary" msgid="3152957611171210980">"Dozvoli da se obavještenja nastave pojavljivati kada je uključen način rada Ne ometaj"</string>
<string name="app_notification_visibility_override_title" msgid="2349335170165637672">"Na zaključavanju ekrana"</string>
<string name="app_notification_row_banned" msgid="2079325338122151677">"Blokirano"</string>
<string name="app_notification_row_priority" msgid="432299064888787236">"Prioritetna"</string>
@@ -3553,7 +3553,7 @@
<string name="zen_mode_media" msgid="3701280649874724055">"Reproduciraj zvukove medija"</string>
<string name="zen_mode_media_list" msgid="509327580522287125">"mediji"</string>
<string name="zen_mode_system" msgid="597437265986355038">"Dozvoli zvukove dodira"</string>
- <string name="zen_mode_system_list" msgid="480192458506838077">"zvuci dodira"</string>
+ <string name="zen_mode_system_list" msgid="480192458506838077">"zvukovi dodira"</string>
<string name="zen_mode_reminders" msgid="7560664194610054038">"Dozvoli podsjetnike"</string>
<string name="zen_mode_reminders_list" msgid="7347061314032326677">"podsjetnici"</string>
<string name="zen_mode_events" msgid="5784076928339534984">"Dozvoli događaje"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ca/strings.xml b/tests/CarDeveloperOptions/res/values-ca/strings.xml
index 60f9de3..29423a2 100644
--- a/tests/CarDeveloperOptions/res/values-ca/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ca/strings.xml
@@ -875,7 +875,7 @@
<string name="wifi_cellular_data_fallback_title" msgid="5067241930716252665">"Canvia automàticament a dades mòbils"</string>
<string name="wifi_cellular_data_fallback_summary" msgid="2721467405851519769">"Utilitza dades mòbils quan la Wi-Fi no tingui accés a Internet. És possible que s\'hi apliquin càrrecs per ús de dades."</string>
<string name="wifi_add_network" msgid="4094957940791876640">"Afegeix una xarxa"</string>
- <string name="wifi_configure_settings_preference_title" msgid="2678534679408777268">"Preferències de la Wi‑Fi"</string>
+ <string name="wifi_configure_settings_preference_title" msgid="2678534679408777268">"Preferències de Wi‑Fi"</string>
<string name="wifi_configure_settings_preference_summary_wakeup_on" msgid="5714892572614655675">"La Wi‑Fi es torna a connectar automàticament"</string>
<string name="wifi_configure_settings_preference_summary_wakeup_off" msgid="286904094152909651">"La Wi‑Fi no es torna a activar automàticament"</string>
<string name="wifi_access_points" msgid="1647976498906871869">"Xarxes Wi-Fi"</string>
@@ -3548,7 +3548,7 @@
<string name="zen_mode_screen_off" msgid="84211490206459038">"Quan la pantalla estigui apagada"</string>
<string name="zen_mode_screen_off_summary" msgid="8592179073243001267">"Permet que les notificacions silenciades pel mode No molestis encenguin la pantalla i facin parpellejar el llum"</string>
<string name="zen_mode_screen_off_summary_no_led" msgid="7255874108150630145">"Permet que les notificacions silenciades pel mode No molestis encenguin la pantalla"</string>
- <string name="notification_app_settings_button" msgid="3651180424198580907">"Configuració de les notificacions"</string>
+ <string name="notification_app_settings_button" msgid="3651180424198580907">"Configuració de notificacions"</string>
<string name="suggestion_button_text" msgid="5783566542423813847">"D\'acord"</string>
<string name="device_feedback" msgid="4042352891448769818">"Envia suggeriments sobre el dispositiu"</string>
<string name="restr_pin_enter_admin_pin" msgid="8577847751493521230">"Introdueix el PIN d\'administrador"</string>
diff --git a/tests/CarDeveloperOptions/res/values-es/strings.xml b/tests/CarDeveloperOptions/res/values-es/strings.xml
index eb24e4b..a7a06e2 100644
--- a/tests/CarDeveloperOptions/res/values-es/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-es/strings.xml
@@ -3140,7 +3140,7 @@
<string name="alarm_volume_option_title" msgid="3184076022438477047">"Volumen de alarma"</string>
<string name="ring_volume_option_title" msgid="2038924918468372264">"Volumen del tono"</string>
<string name="notification_volume_option_title" msgid="1358512611511348260">"Volumen de notificaciones"</string>
- <string name="ringtone_title" msgid="1409086028485922583">"Tono del teléfono"</string>
+ <string name="ringtone_title" msgid="1409086028485922583">"Tono de llamada del teléfono"</string>
<string name="notification_ringtone_title" msgid="2932960620843976285">"Sonido de notificación predeterminado"</string>
<string name="notification_unknown_sound_title" msgid="8043718667804838398">"Sonido proporcionado de app"</string>
<string name="notification_sound_default" msgid="2664544380802426260">"Sonido de notificación predeterminado"</string>
@@ -3422,7 +3422,7 @@
<string name="notification_content_block_summary" msgid="2743896875255591743">"No mostrar nunca notificaciones en el panel de notificaciones ni en dispositivos periféricos"</string>
<string name="notification_badge_title" msgid="8989086619255666442">"Permitir burbuja de notificación"</string>
<string name="notification_channel_badge_title" msgid="8228215248332054612">"Mostrar burbuja de notificación"</string>
- <string name="app_notification_override_dnd_title" msgid="1757042206738172601">"Omitir No molestar"</string>
+ <string name="app_notification_override_dnd_title" msgid="1757042206738172601">"Ignorar modo No molestar"</string>
<string name="app_notification_override_dnd_summary" msgid="3152957611171210980">"Permitir notificaciones cuando el modo No molestar esté activado"</string>
<string name="app_notification_visibility_override_title" msgid="2349335170165637672">"En la pantalla de bloqueo"</string>
<string name="app_notification_row_banned" msgid="2079325338122151677">"Bloqueada"</string>
diff --git a/tests/CarDeveloperOptions/res/values-fa/arrays.xml b/tests/CarDeveloperOptions/res/values-fa/arrays.xml
index 94410d3..2fcb737 100644
--- a/tests/CarDeveloperOptions/res/values-fa/arrays.xml
+++ b/tests/CarDeveloperOptions/res/values-fa/arrays.xml
@@ -111,7 +111,7 @@
<item msgid="8247986727324120082">"۲ دقیقه"</item>
<item msgid="2759776603549270587">"۵ دقیقه"</item>
<item msgid="167772676068860015">"۱ ساعت"</item>
- <item msgid="5985477119043628504">"بدون مهلت زمانی"</item>
+ <item msgid="5985477119043628504">"بدون درنگ"</item>
</string-array>
<string-array name="bluetooth_max_connected_audio_devices">
<item msgid="3800257971619063588">"استفاده از پیشفرض سیستم: <xliff:g id="DEFAULT_BLUETOOTH_MAX_CONNECTED_AUDIO_DEVICES">%1$d</xliff:g>"</item>
@@ -415,7 +415,7 @@
<item msgid="8754480102834556765">"آماده سازی..."</item>
<item msgid="3351334355574270250">"در حال اتصال..."</item>
<item msgid="8303882153995748352">"متصل"</item>
- <item msgid="9135049670787351881">"وقفه زمانی"</item>
+ <item msgid="9135049670787351881">"درنگ"</item>
<item msgid="2124868417182583926">"ناموفق"</item>
</string-array>
<string-array name="security_settings_premium_sms_values">
diff --git a/tests/CarDeveloperOptions/res/values-fa/strings.xml b/tests/CarDeveloperOptions/res/values-fa/strings.xml
index c715326..48e902e 100644
--- a/tests/CarDeveloperOptions/res/values-fa/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-fa/strings.xml
@@ -412,7 +412,7 @@
<string name="face_intro_error_unknown" msgid="3241592604198351134">"چهره بیشتری نمیتوان اضافه کرد"</string>
<string name="security_settings_face_enroll_error_dialog_title" msgid="3933492758701563051">"ثبت انجام نشد"</string>
<string name="security_settings_face_enroll_dialog_ok" msgid="1078348922734845090">"تأیید"</string>
- <string name="security_settings_face_enroll_error_timeout_dialog_message" msgid="4917894418448325405">"مهلت زمانی ثبت چهره به پایان رسید. دوباره امتحان کنید."</string>
+ <string name="security_settings_face_enroll_error_timeout_dialog_message" msgid="4917894418448325405">"درنگ ثبت چهره به پایان رسید. دوباره امتحان کنید."</string>
<string name="security_settings_face_enroll_error_generic_dialog_message" msgid="5160473187142616862">"ثبت چهره کار نکرد."</string>
<string name="security_settings_face_enroll_finish_title" msgid="6800717857394410769">"همه چیز تنظیم شد. خوب به نظر میرسد."</string>
<string name="security_settings_face_enroll_done" msgid="5409739233373490971">"تمام"</string>
@@ -488,7 +488,7 @@
<string name="security_settings_fingerprint_enroll_touch_dialog_title" msgid="7410398793283818609">"اووه، آن حسگر نیست"</string>
<string name="security_settings_fingerprint_enroll_touch_dialog_message" msgid="7192100314788868883">"با استفاده از انگشت اشاره، حسگر را در پشت تلفن لمس کنید."</string>
<string name="security_settings_fingerprint_enroll_error_dialog_title" msgid="1415709674142168770">"ثبت انجام نشد"</string>
- <string name="security_settings_fingerprint_enroll_error_timeout_dialog_message" msgid="498951203761192366">"مهلت زمانی ثبت اثر انگشت به پایان رسید. دوباره امتحان کنید."</string>
+ <string name="security_settings_fingerprint_enroll_error_timeout_dialog_message" msgid="498951203761192366">"درنگ ثبت اثر انگشت به پایان رسید. دوباره امتحان کنید."</string>
<string name="security_settings_fingerprint_enroll_error_generic_dialog_message" msgid="7896295829530444810">"ثبت اثر انگشت کار نمیکند. دوباره امتحان کنید یا از انگشت دیگری استفاده کنید."</string>
<string name="fingerprint_enroll_button_add" msgid="6335782936874996629">"افزودن مورد دیگر"</string>
<string name="fingerprint_enroll_button_next" msgid="6419214079104413695">"بعدی"</string>
@@ -3112,11 +3112,11 @@
<string name="keywords_face_settings" msgid="4117345666006836599">"چهره"</string>
<string name="keywords_fingerprint_settings" msgid="902902368701134163">"اثر انگشت، افزودن اثر انگشت"</string>
<string name="keywords_display_auto_brightness" msgid="1810596220466483996">"تار کردن صفحهنمایش، صفحه لمسی، باتری، روشنایی هوشمند، روشنایی پویا"</string>
- <string name="keywords_display_adaptive_sleep" msgid="1695357782432822811">"کمنور کردن صفحه، خواب، باتری، مهلت زمانی، توجه، نمایشگر، صفحه، بیفعالیتی"</string>
+ <string name="keywords_display_adaptive_sleep" msgid="1695357782432822811">"کمنور کردن صفحه، خواب، باتری، درنگ، توجه، نمایشگر، صفحه، بیفعالیتی"</string>
<string name="keywords_auto_rotate" msgid="4320791369951647513">"چرخاندن، چرخش، چرخش، پرتره، منظره، جهت، عمودی، افقی"</string>
<string name="keywords_system_update_settings" msgid="4419971277998986067">"ارتقا دادن، Android"</string>
<string name="keywords_zen_mode_settings" msgid="4103819458182535493">"«مزاحم نشوید»، زمانبندی، اعلانها، مسدود کردن، سکوت، لرزش، خواب، کار، کانونی کردن، صدا، صامت کردن، روز، روز هفته، آخر هفته، شبهای طول هفته، رویداد"</string>
- <string name="keywords_screen_timeout" msgid="4328381362313993666">"صفحه نمایش، زمان قفل شدن، مهلت زمانی، صفحه درحالت قفل"</string>
+ <string name="keywords_screen_timeout" msgid="4328381362313993666">"صفحه نمایش، زمان قفل شدن، درنگ، صفحه درحالت قفل"</string>
<string name="keywords_storage_settings" msgid="6422454520424236476">"حافظه، حافظه پنهان، داده، حذف، پاک کردن، آزاد، فضا"</string>
<string name="keywords_bluetooth_settings" msgid="1152229891590622822">"متصل، دستگاه، هدفونها، هدستها، بلندگو، بیسیم، مرتبطسازی، هدفونهای توگوشی، موسیقی، رسانه"</string>
<string name="keywords_wallpaper" msgid="7665778626293643625">"پسزمینه، صفحه نمایش، صفحه در حالت قفل، طرح زمینه"</string>
diff --git a/tests/CarDeveloperOptions/res/values-fi/strings.xml b/tests/CarDeveloperOptions/res/values-fi/strings.xml
index 61588ac..659ba8a 100644
--- a/tests/CarDeveloperOptions/res/values-fi/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-fi/strings.xml
@@ -246,7 +246,7 @@
<string name="band_mode_set" msgid="4962130364076526789">"Aseta"</string>
<string name="band_mode_failed" msgid="8350123391471974137">"Epäonnistui"</string>
<string name="band_mode_succeeded" msgid="5516613616395402809">"Onnistui"</string>
- <string name="sdcard_changes_instructions" msgid="4138217393448114001">"Muutokset tulevat voimaan, kun USB-kaapeli kytketään uudelleen."</string>
+ <string name="sdcard_changes_instructions" msgid="4138217393448114001">"Muutokset tulevat voimaan, kun USB-johto kytketään uudelleen."</string>
<string name="sdcard_settings_screen_mass_storage_text" msgid="7486030250999007641">"Ota USB-massamuisti käyttöön"</string>
<string name="sdcard_settings_total_bytes_label" msgid="6461741874400909157">"Tavuja yhteensä:"</string>
<string name="sdcard_settings_not_present_status" product="nosdcard" msgid="5419085128792417589">"USB-tallennustila ei käytössä."</string>
@@ -849,7 +849,7 @@
<string name="wifi_notify_open_networks" msgid="4782239203624619655">"Avaa verkkoilmoitus"</string>
<string name="wifi_notify_open_networks_summary" msgid="1383681260705466715">"Ilmoita, kun käytettävissä on laadukkaita julkisia verkkoja."</string>
<string name="wifi_wakeup" msgid="4963732992164721548">"Laita Wi-Fi päälle automaattisesti"</string>
- <string name="wifi_wakeup_summary" msgid="1152699417411690">"Wi-Fi laitetaan automaattisesti päälle, kun lähistöllä on kotiverkkosi tai muita laadukkaita tallennettuja verkkoja."</string>
+ <string name="wifi_wakeup_summary" msgid="1152699417411690">"Wi-Fi laitetaan automaattisesti päälle, kun lähistöllä on kotiverkkosi tai muita laadukkaita tallennettuja verkkoja"</string>
<string name="wifi_wakeup_summary_no_location" msgid="3007457288587966962">"Ei käytettävissä, koska sijainti on poistettu käytöstä. Ota "<annotation id="link">"sijainti"</annotation>" käyttöön."</string>
<string name="wifi_wakeup_summary_scanning_disabled" msgid="6820040651529910914">"Ei käytettävissä, koska Wi‑Fi-haku on pois päältä."</string>
<string name="wifi_wakeup_summary_scoring_disabled" msgid="7067018832237903151">"Valitse verkon arviointipalvelu, jotta voit käyttää tätä."</string>
diff --git a/tests/CarDeveloperOptions/res/values-gl/strings.xml b/tests/CarDeveloperOptions/res/values-gl/strings.xml
index d8f5367..51e5d0c 100644
--- a/tests/CarDeveloperOptions/res/values-gl/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-gl/strings.xml
@@ -4477,7 +4477,7 @@
<string name="contextual_card_dismiss_confirm_message" msgid="2352465079667730312">"Queres eliminar esta suxestión?"</string>
<string name="contextual_card_removed_message" msgid="4047307820743366876">"Quitouse a suxestión"</string>
<string name="contextual_card_undo_dismissal_text" msgid="5009245286931852012">"Desfacer"</string>
- <string name="low_storage_summary" msgid="4562224870189133400">"Queda pouco espazo. Utilizado: <xliff:g id="PERCENTAGE">%1$s</xliff:g>. Libre: <xliff:g id="FREE_SPACE">%2$s</xliff:g>"</string>
+ <string name="low_storage_summary" msgid="4562224870189133400">"Queda pouco espazo. En uso: <xliff:g id="PERCENTAGE">%1$s</xliff:g>. Libre: <xliff:g id="FREE_SPACE">%2$s</xliff:g>"</string>
<string name="contextual_card_feedback_send" msgid="8698649023854350623">"Enviar comentarios"</string>
<string name="contextual_card_feedback_confirm_message" msgid="3987973028353264878">"Queres indicarnos a túa opinión sobre esta suxestión?"</string>
<string name="copyable_slice_toast" msgid="1357518174923789947">"<xliff:g id="COPY_CONTENT">%1$s</xliff:g>: copiouse no portapapeis."</string>
diff --git a/tests/CarDeveloperOptions/res/values-gu/strings.xml b/tests/CarDeveloperOptions/res/values-gu/strings.xml
index 398c56d..49de15b 100644
--- a/tests/CarDeveloperOptions/res/values-gu/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-gu/strings.xml
@@ -3041,7 +3041,7 @@
<string name="connected_devices_dashboard_no_driving_mode_summary" msgid="3524409078596318803">"બ્લૂટૂથ, NFC"</string>
<string name="connected_devices_dashboard_no_driving_mode_no_nfc_summary" msgid="7881286613528299400">"બ્લૂટૂથ"</string>
<string name="app_and_notification_dashboard_title" msgid="8448096608058843730">"ઍપ્લિકેશનો અને નોટિફિકેશન"</string>
- <string name="app_and_notification_dashboard_summary" msgid="4165181440955038145">"આસિસ્ટંટ, તાજેતરની ઍપ, ડિફૉલ્ટ ઍપ"</string>
+ <string name="app_and_notification_dashboard_summary" msgid="4165181440955038145">"Assistant, તાજેતરની ઍપ, ડિફૉલ્ટ ઍપ"</string>
<string name="notification_settings_work_profile" msgid="7190550347842400029">"કાર્યાલયની પ્રોફાઇલમાં ઍપ માટે નોટિફિકેશન ઍક્સેસ ઉપલબ્ધ નથી."</string>
<string name="account_dashboard_title" msgid="4734300939532555885">"એકાઉન્ટ"</string>
<string name="account_dashboard_default_summary" msgid="6822549669771936206">"કોઈ એકાઉન્ટ ઉમેરવામાં આવ્યાં નથી"</string>
diff --git a/tests/CarDeveloperOptions/res/values-hy/strings.xml b/tests/CarDeveloperOptions/res/values-hy/strings.xml
index 0154721..5d6c540 100644
--- a/tests/CarDeveloperOptions/res/values-hy/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-hy/strings.xml
@@ -849,7 +849,7 @@
<string name="wifi_notify_open_networks" msgid="4782239203624619655">"Ծանուցումներ բաց ցանցերի մասին"</string>
<string name="wifi_notify_open_networks_summary" msgid="1383681260705466715">"Տեղեկացնել լավ ազդանշանով բաց ցանցերի հասանելիության մասին"</string>
<string name="wifi_wakeup" msgid="4963732992164721548">"Ավտոմատ միացնել Wi‑Fi-ը"</string>
- <string name="wifi_wakeup_summary" msgid="1152699417411690">"Wi‑Fi-ը կրկին կմիանա պահված լավ ազդանշանով պահված ցանցերի, օրինակ, ձեր տան ցանցի մոտակայքում"</string>
+ <string name="wifi_wakeup_summary" msgid="1152699417411690">"Wi‑Fi-ը կրկին կմիանա լավ ազդանշանով պահված ցանցերի, օրինակ, ձեր տան ցանցի մոտակայքում"</string>
<string name="wifi_wakeup_summary_no_location" msgid="3007457288587966962">"Անհասանելի է, քանի որ տեղորոշումն անջատված է։ Միացրեք "<annotation id="link">"տեղորոշումը"</annotation>"։"</string>
<string name="wifi_wakeup_summary_scanning_disabled" msgid="6820040651529910914">"Անհասանելի է, քանի որ Wi‑Fi ցանցերի որոնումն անջատված է"</string>
<string name="wifi_wakeup_summary_scoring_disabled" msgid="7067018832237903151">"Օգտագործելու համար ընտրեք ցանցի վարկանիշի մատակարարը"</string>
diff --git a/tests/CarDeveloperOptions/res/values-in/strings.xml b/tests/CarDeveloperOptions/res/values-in/strings.xml
index 6ada981..307e232 100644
--- a/tests/CarDeveloperOptions/res/values-in/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-in/strings.xml
@@ -2862,7 +2862,7 @@
<string name="user_summary_managed_profile_not_set_up" msgid="3032986082684011281">"Tidak disiapkan - Profil kerja"</string>
<string name="user_admin" msgid="805802526361071709">"Admin"</string>
<string name="user_you" msgid="8212549708652717106">"Anda (<xliff:g id="NAME">%s</xliff:g>)"</string>
- <string name="user_nickname" msgid="1088216221559125529">"Nama julukan"</string>
+ <string name="user_nickname" msgid="1088216221559125529">"Nama panggilan"</string>
<string name="user_add_user_type_title" msgid="8672326434351387845">"Tambahkan"</string>
<string name="user_add_max_count" msgid="4524573950126500416">"Anda dapat menambahkan maksimal <xliff:g id="USER_COUNT">%1$d</xliff:g> pengguna"</string>
<string name="user_add_user_item_summary" msgid="6114355152711455716">"Pengguna memiliki aplikasi dan konten mereka sendiri"</string>
@@ -3035,7 +3035,7 @@
<string name="network_dashboard_summary_mobile" msgid="5560545061217580626">"seluler"</string>
<string name="network_dashboard_summary_data_usage" msgid="4695629715072542102">"penggunaan kuota"</string>
<string name="network_dashboard_summary_hotspot" msgid="3928610802321995214">"hotspot"</string>
- <string name="connected_devices_dashboard_title" msgid="7795222675849060444">"Perangkat tersambung"</string>
+ <string name="connected_devices_dashboard_title" msgid="7795222675849060444">"Perangkat terhubung"</string>
<string name="connected_devices_dashboard_summary" msgid="1072664369515033179">"Bluetooth, mode mengemudi, NFC"</string>
<string name="connected_devices_dashboard_no_nfc_summary" msgid="2610085597733526722">"Bluetooth, mode mengemudi"</string>
<string name="connected_devices_dashboard_no_driving_mode_summary" msgid="3524409078596318803">"Bluetooth, NFC"</string>
diff --git a/tests/CarDeveloperOptions/res/values-it/strings.xml b/tests/CarDeveloperOptions/res/values-it/strings.xml
index 5c72158..02abed3 100644
--- a/tests/CarDeveloperOptions/res/values-it/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-it/strings.xml
@@ -4325,7 +4325,7 @@
<string name="battery_suggestion_summary" msgid="2669070349482656490"></string>
<string name="gesture_prevent_ringing_screen_title" msgid="4173494225145223638">"Disattiva suoneria"</string>
<string name="gesture_prevent_ringing_title" msgid="8827963588425673557">"Premi contemporaneamente i tasti di accensione e Volume su per"</string>
- <string name="gesture_prevent_ringing_sound_title" msgid="8642330448721033641">"Scorciatoia per impedire al telefono di suonare"</string>
+ <string name="gesture_prevent_ringing_sound_title" msgid="8642330448721033641">"Scorciatoia per disattivare la suoneria"</string>
<string name="prevent_ringing_option_vibrate" msgid="6456505293904544108">"Vibrazione"</string>
<string name="prevent_ringing_option_mute" msgid="53662688921253613">"Disattiva audio"</string>
<string name="prevent_ringing_option_none" msgid="1450985763137666231">"Non fare niente"</string>
diff --git a/tests/CarDeveloperOptions/res/values-km/strings.xml b/tests/CarDeveloperOptions/res/values-km/strings.xml
index 29c5ca4..c8b2bdb 100644
--- a/tests/CarDeveloperOptions/res/values-km/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-km/strings.xml
@@ -3136,7 +3136,7 @@
<string name="sound_settings_example_summary" msgid="2091822107298841827">"កម្រិតសំឡេងរោទ៍ត្រឹម 80%"</string>
<string name="media_volume_option_title" msgid="3553411883305505682">"កម្រិតសំឡេងមេឌៀ"</string>
<string name="remote_media_volume_option_title" msgid="6355710054191873836">"កម្រិតសំឡេងនៃការបញ្ជូន"</string>
- <string name="call_volume_option_title" msgid="5028003296631037334">"កម្រិតសំឡេងហៅ"</string>
+ <string name="call_volume_option_title" msgid="5028003296631037334">"កម្រិតសំឡេងហៅទូរសព្ទ"</string>
<string name="alarm_volume_option_title" msgid="3184076022438477047">"កម្រិតសំឡេងម៉ោងរោទ៍"</string>
<string name="ring_volume_option_title" msgid="2038924918468372264">"កម្រិតសំឡេងរោទ៍"</string>
<string name="notification_volume_option_title" msgid="1358512611511348260">"កម្រិតសំឡេងការជូនដំណឹង"</string>
@@ -3153,7 +3153,7 @@
<string name="docking_sounds_title" msgid="2573137471605541366">"សំឡេងភ្ជាប់"</string>
<string name="touch_sounds_title" msgid="165237488496165652">"សំឡេងប៉ះ"</string>
<string name="vibrate_on_touch_title" msgid="6360155469279157684">"ការញ័រពេលប៉ះ"</string>
- <string name="vibrate_on_touch_summary" msgid="5504424764028676043">"មតិស្ថាបនាតាមការប៉ះសម្រាប់ការចុច ក្ដារចុច និងច្រើនទៀត"</string>
+ <string name="vibrate_on_touch_summary" msgid="5504424764028676043">"ប្រតិកម្មញ័រពេលចុច ក្ដារចុច និងច្រើនទៀត"</string>
<string name="dock_audio_media_title" msgid="1859521680502040781">"ភ្ជាប់ការចាក់តាមអូប៉ាល័រ"</string>
<string name="dock_audio_media_disabled" msgid="4300752306178486302">"អូឌីយ៉ូទាំងអស់"</string>
<string name="dock_audio_media_enabled" msgid="2873275045878628153">"តែអូឌីយ៉ូមេឌៀប៉ុណ្ណោះ"</string>
@@ -4310,7 +4310,7 @@
<string name="change_wifi_state_title" msgid="5140754955787584174">"ការគ្រប់គ្រង Wi-Fi"</string>
<string name="change_wifi_state_app_detail_switch" msgid="6489090744937816260">"អនុញ្ញាតឱ្យកម្មវិធីគ្រប់គ្រង Wi-Fi"</string>
<string name="change_wifi_state_app_detail_summary" msgid="614854822469259860">"អនុញ្ញាតឱ្យកម្មវិធីនេះបើក ឬបិទ Wi-Fi ស្កេន និងភ្ជាប់បណ្តាញ Wi-Fi បញ្ចូល ឬលុបបណ្តាញ ឬចាប់ផ្តើមតែហតស្ប៉តមូលដ្ឋានប៉ុណ្ណោះ"</string>
- <string name="media_output_title" msgid="8710632337456601848">"ចាក់មេឌៀទៅកាន់"</string>
+ <string name="media_output_title" msgid="8710632337456601848">"ចាក់មេឌៀនៅលើ"</string>
<string name="media_output_default_summary" msgid="3159237976830415584">"ឧបករណ៍នេះ"</string>
<string name="media_output_summary" product="default" msgid="6294261435613551178">"ទូរសព្ទ"</string>
<string name="media_output_summary" product="tablet" msgid="6672024060360538526">"ថេប្លេត"</string>
diff --git a/tests/CarDeveloperOptions/res/values-kn/arrays.xml b/tests/CarDeveloperOptions/res/values-kn/arrays.xml
index 63d5f6a..2b09f58 100644
--- a/tests/CarDeveloperOptions/res/values-kn/arrays.xml
+++ b/tests/CarDeveloperOptions/res/values-kn/arrays.xml
@@ -223,7 +223,7 @@
<item msgid="2585253854462134715">"ಒರಟು ಸ್ಥಳ"</item>
<item msgid="1830619568689922920">"ಉತ್ಕೃಷ್ಟ ಸ್ಥಳ"</item>
<item msgid="3317274469481923141">"GPS"</item>
- <item msgid="8931785990160383356">"ಕಂಪನ"</item>
+ <item msgid="8931785990160383356">"ವೈಬ್ರೇಟ್"</item>
<item msgid="8632513128515114092">"ಓದುವ ಸಂಪರ್ಕಗಳು"</item>
<item msgid="3741042113569620272">"ಸಂಪರ್ಕಗಳನ್ನು ಮಾರ್ಪಡಿಸಿ"</item>
<item msgid="4204420969709009931">"ಕರೆಯ ಲಾಗ್ ಓದಿ"</item>
diff --git a/tests/CarDeveloperOptions/res/values-kn/strings.xml b/tests/CarDeveloperOptions/res/values-kn/strings.xml
index 5d1e489..ad359d0 100644
--- a/tests/CarDeveloperOptions/res/values-kn/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-kn/strings.xml
@@ -3124,7 +3124,7 @@
<string name="keywords_default_payment_app" msgid="845369409578423996">"ಪಾವತಿ, ಡಿಫಾಲ್ಟ್"</string>
<string name="keywords_ambient_display" msgid="8835182491798487184">"ಒಳಬರುವ ಅಧಿಸೂಚನೆ"</string>
<string name="keywords_hotspot_tethering" msgid="1723591462602613867">"usb ಟೆಥರ್, ಬ್ಲೂಟೂತ್ ಟೆಥರ್, ವೈಫೈ ಹಾಟ್ಸ್ಪಾಟ್"</string>
- <string name="keywords_touch_vibration" msgid="2081175517528255224">"ಹ್ಯಾಪ್ಟಿಕ್ಸ್, ಕಂಪನ, ಪರದೆ, ಸಂವೇದನೆ"</string>
+ <string name="keywords_touch_vibration" msgid="2081175517528255224">"ಹ್ಯಾಪ್ಟಿಕ್ಸ್, ವೈಬ್ರೇಟ್, ಪರದೆ, ಸಂವೇದನೆ"</string>
<string name="keywords_ring_vibration" msgid="4210509151866460210">"ಹ್ಯಾಪ್ಟಿಕ್ಸ್, ವೈಬ್ರೇಟ್, ಫೋನ್, ಕರೆ, ಸೂಕ್ಷ್ಮತೆ, ರಿಂಗ್"</string>
<string name="keywords_notification_vibration" msgid="1077515502086745166">"ಹ್ಯಾಪ್ಟಿಕ್ಸ್, ವೈಬ್ರೇಟ್, ಸೂಕ್ಷ್ಮತೆ"</string>
<string name="keywords_battery_saver_sticky" msgid="8733804259716284872">"ಬ್ಯಾಟರಿ ಸೇವರ್, ಸ್ಟಿಕಿ, ತಡೆ ಹಿಡಿ, ಪವರ್ ಸೇವರ್, ಬ್ಯಾಟರಿ"</string>
@@ -3149,7 +3149,7 @@
<string name="other_sound_settings" msgid="5250376066099818676">"ಇತರ ಧ್ವನಿಗಳು"</string>
<string name="dial_pad_tones_title" msgid="8877212139988655769">"ಡಯಲ್ ಪ್ಯಾಡ್ ಟೋನ್ಗಳು"</string>
<string name="screen_locking_sounds_title" msgid="4407110895465866809">"ಸ್ಕ್ರೀನ್ ಲಾಕಿಂಗ್ ಧ್ವನಿಗಳು"</string>
- <string name="charging_sounds_title" msgid="5070437987230894287">"ಚಾರ್ಜಿಂಗ್ ಧ್ವನಿಗಳು ಮತ್ತು ಕಂಪನ"</string>
+ <string name="charging_sounds_title" msgid="5070437987230894287">"ಚಾರ್ಜಿಂಗ್ ಧ್ವನಿಗಳು ಮತ್ತು ವೈಬ್ರೇಟ್"</string>
<string name="docking_sounds_title" msgid="2573137471605541366">"ಡಾಕಿಂಗ್ ಧ್ವನಿಗಳು"</string>
<string name="touch_sounds_title" msgid="165237488496165652">"ಸ್ಪರ್ಶ ಧ್ವನಿಗಳು"</string>
<string name="vibrate_on_touch_title" msgid="6360155469279157684">"ಸ್ಪರ್ಶಿಸಿದಾಗ ವೈಬ್ರೇಷನ್"</string>
@@ -4037,7 +4037,7 @@
<string name="notification_log_details_ashmem" msgid="4272241723105041393">"ಆಶ್ಮೆಮ್"</string>
<string name="notification_log_details_alerted" msgid="1891749888625061319">"ಅಧಿಸೂಚನೆಯ ಎಚ್ಚರಿಕೆಯನ್ನು ನೀಡಲಾಗಿದೆ"</string>
<string name="notification_log_details_sound" msgid="4028782443557466322">"ಶಬ್ದ"</string>
- <string name="notification_log_details_vibrate" msgid="8372400602058888072">"ಕಂಪನ"</string>
+ <string name="notification_log_details_vibrate" msgid="8372400602058888072">"ವೈಬ್ರೇಟ್"</string>
<string name="notification_log_details_vibrate_pattern" msgid="7015554755444260922">"ಪ್ಯಾಟರ್ನ್"</string>
<string name="notification_log_details_default" msgid="455451833359888182">"ಡಿಫಾಲ್ಟ್"</string>
<string name="notification_log_details_none" msgid="4294690532744821638">"ಯಾವುದೂ ಇಲ್ಲ"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ky/strings.xml b/tests/CarDeveloperOptions/res/values-ky/strings.xml
index c436ab2..be41ac2 100644
--- a/tests/CarDeveloperOptions/res/values-ky/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ky/strings.xml
@@ -3035,7 +3035,7 @@
<string name="network_dashboard_summary_mobile" msgid="5560545061217580626">"мобилдик"</string>
<string name="network_dashboard_summary_data_usage" msgid="4695629715072542102">"дайын-даректердин өткөрүлүшү"</string>
<string name="network_dashboard_summary_hotspot" msgid="3928610802321995214">"байланыш түйүнү"</string>
- <string name="connected_devices_dashboard_title" msgid="7795222675849060444">"Туташкан түзмөктөр"</string>
+ <string name="connected_devices_dashboard_title" msgid="7795222675849060444">"Байланышкан түзмөктөр"</string>
<string name="connected_devices_dashboard_summary" msgid="1072664369515033179">"Bluetooth, айдоо режими, NFC"</string>
<string name="connected_devices_dashboard_no_nfc_summary" msgid="2610085597733526722">"Bluetooth, айдоо режими"</string>
<string name="connected_devices_dashboard_no_driving_mode_summary" msgid="3524409078596318803">"Bluetooth, NFC"</string>
diff --git a/tests/CarDeveloperOptions/res/values-lo/strings.xml b/tests/CarDeveloperOptions/res/values-lo/strings.xml
index 4d48d74..f43a442 100644
--- a/tests/CarDeveloperOptions/res/values-lo/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-lo/strings.xml
@@ -3403,7 +3403,7 @@
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ໝວດໝູ່</item>
<item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ໝວດໝູ່</item>
</plurals>
- <string name="no_channels" msgid="8884254729302501652">"This app has not posted any notifications"</string>
+ <string name="no_channels" msgid="8884254729302501652">"ແອັບນີ້ຍັງບໍ່ໄດ້ໂພສການແຈ້ງເຕືອນໃດເທື່ອ"</string>
<string name="app_settings_link" msgid="8465287765715790984">"ການຕັ້ງຄ່າເພີ່ມເຕີມໃນແອັບ"</string>
<string name="app_notification_listing_summary_zero" msgid="4047782719487686699">"ເປີດສຳລັບທຸກແອັບ"</string>
<plurals name="app_notification_listing_summary_others" formatted="false" msgid="1161774065480666519">
diff --git a/tests/CarDeveloperOptions/res/values-mk/strings.xml b/tests/CarDeveloperOptions/res/values-mk/strings.xml
index 56d2a0c..94c0d71 100644
--- a/tests/CarDeveloperOptions/res/values-mk/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-mk/strings.xml
@@ -1608,7 +1608,7 @@
<string name="master_clear_not_available" msgid="4676613348163652454">"Фабричкото ресетирање не е достапно за овој корисник"</string>
<string name="master_clear_progress_title" msgid="378953167274114857">"Се брише"</string>
<string name="master_clear_progress_text" msgid="5418958116008976218">"Почекајте..."</string>
- <string name="call_settings_title" msgid="5033906789261282752">"Поставки на повик"</string>
+ <string name="call_settings_title" msgid="5033906789261282752">"Поставки за повици"</string>
<string name="call_settings_summary" msgid="2119161087671450035">"Постави говорна пошта, проследување на повик, повик на чекање, ID на повикувач"</string>
<string name="tether_settings_title_usb" msgid="4265582654602420357">"Интернет преку USB"</string>
<string name="tether_settings_title_wifi" msgid="2060965130234484613">"Преносл. точка на пристап"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ml/strings.xml b/tests/CarDeveloperOptions/res/values-ml/strings.xml
index a529cee..2f0c29a 100644
--- a/tests/CarDeveloperOptions/res/values-ml/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ml/strings.xml
@@ -3105,7 +3105,7 @@
<string name="keywords_android_version" msgid="4842749998088987740">"android സുരക്ഷാ പാച്ച് നില, ബേസ്ബാൻഡ് പതിപ്പ്, കെർണൽ പതിപ്പ്"</string>
<string name="keywords_dark_ui_mode" msgid="1027966176887770318">"തീം, പ്രകാശം, ഇരുണ്ട മോഡ്"</string>
<string name="keywords_financial_apps_sms_access" msgid="3236014691838121857">"സാമ്പത്തിക ആപ്പ്, SMS, അനുമതി"</string>
- <string name="keywords_systemui_theme" msgid="9150908170417305866">"ഇരുണ്ട തീം"</string>
+ <string name="keywords_systemui_theme" msgid="9150908170417305866">"ഡാർക്ക് തീം"</string>
<string name="keywords_device_feedback" msgid="6948977907405738490">"ബഗ്"</string>
<string name="keywords_ambient_display_screen" msgid="5873935693887583428">"പാതിമയക്ക ഡിസ്പ്ലേ, ലോക്ക് സ്ക്രീൻ ഡിസ്പ്ലേ"</string>
<string name="keywords_lock_screen_notif" msgid="4914337222856805463">"ലോക്ക് സ്ക്രീൻ അറിയിപ്പ്, അറിയിപ്പുകൾ"</string>
@@ -4310,8 +4310,8 @@
<string name="change_wifi_state_title" msgid="5140754955787584174">"വൈഫൈ നിയന്ത്രണം"</string>
<string name="change_wifi_state_app_detail_switch" msgid="6489090744937816260">"വൈഫൈയെ നിയന്ത്രിക്കാൻ ആപ്പിനെ അനുവദിക്കുക"</string>
<string name="change_wifi_state_app_detail_summary" msgid="614854822469259860">"വൈഫൈ ഓണോ ഓഫോ ആക്കാനോ വൈഫൈ നെറ്റ്വർക്കുകൾ സ്കാൻ ചെയ്യാനോ അവയിലേക്ക് കണക്റ്റ് ചെയ്യാനോ നെറ്റ്വർക്കുകൾ ചേർക്കാനോ നീക്കം ചെയ്യാനോ ഉപകരണം ഉള്ളിടത്ത് മാത്രം പ്രവർത്തിക്കുന്ന ഒരു ഹോട്ട്സ്പോട്ട് ആരംഭിക്കാനോ ഈ ആപ്പിനെ അനുവദിക്കുക"</string>
- <string name="media_output_title" msgid="8710632337456601848">"ഇതിലേക്ക് മീഡിയ പ്ലേ ചെയ്യുക"</string>
- <string name="media_output_default_summary" msgid="3159237976830415584">"ഈ ഉപകരണം"</string>
+ <string name="media_output_title" msgid="8710632337456601848">"മീഡിയ പ്ലേ ചെയ്യുക:"</string>
+ <string name="media_output_default_summary" msgid="3159237976830415584">"ഈ ഉപകരണത്തിൽ"</string>
<string name="media_output_summary" product="default" msgid="6294261435613551178">"ഫോൺ"</string>
<string name="media_output_summary" product="tablet" msgid="6672024060360538526">"ടാബ്ലെറ്റ്"</string>
<string name="media_output_summary" product="device" msgid="5132223072593052660">"ഉപകരണം"</string>
diff --git a/tests/CarDeveloperOptions/res/values-my/strings.xml b/tests/CarDeveloperOptions/res/values-my/strings.xml
index 4ad5b8e..3716e91 100644
--- a/tests/CarDeveloperOptions/res/values-my/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-my/strings.xml
@@ -3332,7 +3332,7 @@
<string name="lock_screen_notifications_interstitial_title_profile" msgid="4043621508889929254">"ပရိုဖိုင်သတိပေးချက်များ"</string>
<string name="notifications_title" msgid="8334011924253810654">"အကြောင်းကြားချက်များ"</string>
<string name="app_notifications_title" msgid="1141791221581312325">"အက်ပ် အကြောင်းကြားချက်များ"</string>
- <string name="notification_channel_title" msgid="6637705960909690229">"အသိပေးချက် အမျိုးအစား"</string>
+ <string name="notification_channel_title" msgid="6637705960909690229">"အကြောင်းကြားချက် အမျိုးအစား"</string>
<string name="notification_group_title" msgid="6105337987437608590">"အကြောင်းကြားချက် အုပ်စုအမျိုးအစား"</string>
<string name="notification_importance_title" msgid="4131979083408000545">"အပြုအမူ"</string>
<string name="notification_importance_unspecified" msgid="2515778981253707724">"အသံကို ခွင့်ပြုရန်"</string>
diff --git a/tests/CarDeveloperOptions/res/values-or/strings.xml b/tests/CarDeveloperOptions/res/values-or/strings.xml
index bbc3273..17e330b 100644
--- a/tests/CarDeveloperOptions/res/values-or/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-or/strings.xml
@@ -1608,7 +1608,7 @@
<string name="master_clear_not_available" msgid="4676613348163652454">"ଏହି ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ପାଇଁ ଫ୍ୟାକ୍ଟୋରୀ ରିସେଟ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
<string name="master_clear_progress_title" msgid="378953167274114857">"ଲିଭାଉଛି"</string>
<string name="master_clear_progress_text" msgid="5418958116008976218">"ଦୟାକରି ଅପେକ୍ଷା କରନ୍ତୁ..."</string>
- <string name="call_settings_title" msgid="5033906789261282752">"କଲ୍ ସେଟିଙ୍ଗ"</string>
+ <string name="call_settings_title" msgid="5033906789261282752">"କଲ୍ ସେଟିଂସ୍"</string>
<string name="call_settings_summary" msgid="2119161087671450035">"ଭଏସ୍ମେଲ୍, କଲ୍ ଫର୍ୱାର୍ଡିଙ୍ଗ, କଲ୍ ୱେଟିଙ୍ଗ, କଲର୍ ID ସେଟ୍ କରନ୍ତୁ"</string>
<string name="tether_settings_title_usb" msgid="4265582654602420357">"USB ଟିଥରିଂ"</string>
<string name="tether_settings_title_wifi" msgid="2060965130234484613">"ପୋର୍ଟବଲ୍ ହଟସ୍ପଟ୍"</string>
@@ -3147,13 +3147,13 @@
<string name="alarm_ringtone_title" msgid="6411326147408635902">"ଡିଫଲ୍ଟ ଆଲାର୍ମ ସାଉଣ୍ଡ"</string>
<string name="vibrate_when_ringing_title" msgid="2757996559847126952">"କଲ୍ ପାଇଁ ଭାଇବ୍ରେଟ୍ କରନ୍ତୁ"</string>
<string name="other_sound_settings" msgid="5250376066099818676">"ଅନ୍ୟାନ୍ୟ ଶବ୍ଦ"</string>
- <string name="dial_pad_tones_title" msgid="8877212139988655769">"ଡାୟଲ୍ ପ୍ୟାଡ୍ ଟୋନ୍"</string>
+ <string name="dial_pad_tones_title" msgid="8877212139988655769">"ଡାଏଲ୍ ପ୍ୟାଡ୍ ଟୋନ୍"</string>
<string name="screen_locking_sounds_title" msgid="4407110895465866809">"ସ୍କ୍ରୀନ୍ ଲକ୍ କରିବା ସାଉଣ୍ଡ"</string>
<string name="charging_sounds_title" msgid="5070437987230894287">"ସାଉଣ୍ଡ ଓ ଭାଇବ୍ରେସନ୍ ଚାର୍ଜ ହେଉଛି"</string>
<string name="docking_sounds_title" msgid="2573137471605541366">"ଡକ୍ କରିବା ଧ୍ୱନୀ"</string>
<string name="touch_sounds_title" msgid="165237488496165652">"ସ୍ପର୍ଶ ଶବ୍ଦ"</string>
- <string name="vibrate_on_touch_title" msgid="6360155469279157684">"ସ୍ପର୍ଶଜନିତ ଭାଇବ୍ରେଶନ୍"</string>
- <string name="vibrate_on_touch_summary" msgid="5504424764028676043">"ଟ୍ୟାପ୍, କୀ-ବୋର୍ଡ ଓ ଆହୁରି ଅଧିକ ପାଇଁ ସ୍ପର୍ଶ ମାଧ୍ୟମରେ ମତାମତ"</string>
+ <string name="vibrate_on_touch_title" msgid="6360155469279157684">"ସ୍ପର୍ଶ ଭାଇବ୍ରେସନ୍"</string>
+ <string name="vibrate_on_touch_summary" msgid="5504424764028676043">"ଟ୍ୟାପ୍, କୀ-ବୋର୍ଡ ଓ ଆହୁରି ଅଧିକ ପାଇଁ ହେପଟିକ୍ ମତାମତ"</string>
<string name="dock_audio_media_title" msgid="1859521680502040781">"ଡକ୍ ସ୍ପିକର୍ ବାଜିବ"</string>
<string name="dock_audio_media_disabled" msgid="4300752306178486302">"ସମସ୍ତ ଅଡିଓ"</string>
<string name="dock_audio_media_enabled" msgid="2873275045878628153">"କେବଳ ମିଡିଆ ଅଡିଓ"</string>
@@ -3293,7 +3293,7 @@
<string name="ringtones_install_custom_sound_title" msgid="210551218424553671">"କଷ୍ଟମ୍ ସାଉଣ୍ଡ ଯୋଡ଼ିବେ?"</string>
<string name="ringtones_install_custom_sound_content" msgid="6683649115132255452">"<xliff:g id="FOLDER_NAME">%s</xliff:g> ଫୋଲ୍ଡର୍କୁ ଏହି ଫାଇଲ୍ କପୀ କରାଯିବ"</string>
<string name="ringtones_category_preference_title" msgid="4491932700769815470">"ରିଙ୍ଗଟୋନ୍"</string>
- <string name="other_sound_category_preference_title" msgid="2045757472469840859">"ଅନ୍ୟାନ୍ୟ ସାଉଣ୍ଡ ଓ ଭାଇବ୍ରେଶନ୍"</string>
+ <string name="other_sound_category_preference_title" msgid="2045757472469840859">"ଅନ୍ୟ ସାଉଣ୍ଡ ଓ ଭାଇବ୍ରେସନ୍"</string>
<string name="configure_notification_settings" msgid="291914315140851270">"ବିଜ୍ଞପ୍ତି"</string>
<string name="recent_notifications" msgid="8125865995065032049">"କିଛି ସମୟ ପୂର୍ବରୁ ବିଜ୍ଞପ୍ତି ପଠାଇଥିବା ଆପ୍"</string>
<string name="recent_notifications_see_all_title" msgid="4089007770442871469">"ବିଗତ 7 ଦିନରୁ ସବୁଗୁଡ଼ିକୁ ଦେଖନ୍ତୁ"</string>
@@ -3423,7 +3423,7 @@
<string name="notification_badge_title" msgid="8989086619255666442">"ବିଜ୍ଞପ୍ତି ଡଟ୍ର ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="notification_channel_badge_title" msgid="8228215248332054612">"ବିଜ୍ଞପ୍ତି ଡଟ୍ ଦେଖାନ୍ତୁ"</string>
<string name="app_notification_override_dnd_title" msgid="1757042206738172601">"ବିରକ୍ତ କରନାହିଁ ଓଭରରାଇଡ"</string>
- <string name="app_notification_override_dnd_summary" msgid="3152957611171210980">"’ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ’ ଅନ୍ ଥିବାବେଳେ ଏହି ବିଜ୍ଞପ୍ତିର ବାଧା ଦେବା ପ୍ରକ୍ରିୟାକୁ ଜାରି ରଖନ୍ତୁ"</string>
+ <string name="app_notification_override_dnd_summary" msgid="3152957611171210980">"\'ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ\' ଚାଲୁଥିବା ବେଳେ ଏହି ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକର ବାଧା ଦେବା ପ୍ରକ୍ରିୟାକୁ ଜାରି ରଖନ୍ତୁ"</string>
<string name="app_notification_visibility_override_title" msgid="2349335170165637672">"ଲକ୍ ସ୍କ୍ରୀନ୍ ଉପରେ"</string>
<string name="app_notification_row_banned" msgid="2079325338122151677">"ଅବରୋଧିତ"</string>
<string name="app_notification_row_priority" msgid="432299064888787236">"ପ୍ରାଥମିକତା"</string>
@@ -4310,7 +4310,7 @@
<string name="change_wifi_state_title" msgid="5140754955787584174">"ୱାଇ-ଫାଇର ନିୟନ୍ତ୍ରଣ"</string>
<string name="change_wifi_state_app_detail_switch" msgid="6489090744937816260">"ୱାଇ-ଫାଇକୁ ନିୟନ୍ତ୍ରଣ କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="change_wifi_state_app_detail_summary" msgid="614854822469259860">"ୱାଇ-ଫାଇକୁ ଚାଲୁ କରିବା କିମ୍ବା ବନ୍ଦ କରିବା, ୱାଇ-ଫାଇ ନେଟୱର୍କକୁ ଖୋଜିବା ଓ କନେକ୍ଟ କରିବା, ନେଟୱର୍କକୁ ଯୋଡ଼ିବା କିମ୍ବା କାଢ଼ିଦେବା କିମ୍ବା କେବଳ ସୀମିତ କ୍ଷେତ୍ରରେ କାମ କରୁଥିବା ହଟସ୍ପଟ୍କୁ ଚାଲୁ କରିବା ପାଇଁ ଏହି ଆପ୍କୁ ଅନୁମତି ଦେବା"</string>
- <string name="media_output_title" msgid="8710632337456601848">"ମିଡିଆ ଚଲାନ୍ତୁ"</string>
+ <string name="media_output_title" msgid="8710632337456601848">"ଏଥିରେ ମିଡିଆ ଚଲାନ୍ତୁ"</string>
<string name="media_output_default_summary" msgid="3159237976830415584">"ଏହି ଡିଭାଇସ୍"</string>
<string name="media_output_summary" product="default" msgid="6294261435613551178">"ଫୋନ୍"</string>
<string name="media_output_summary" product="tablet" msgid="6672024060360538526">"ଟାବଲେଟ୍"</string>
@@ -4330,7 +4330,7 @@
<string name="prevent_ringing_option_mute" msgid="53662688921253613">"ମ୍ୟୁଟ୍ କରନ୍ତୁ"</string>
<string name="prevent_ringing_option_none" msgid="1450985763137666231">"କିଛି କରନ୍ତୁ ନାହିଁ"</string>
<string name="prevent_ringing_option_vibrate_summary" msgid="7961818570574683926">"ଚାଲୁ (ଭାଇବ୍ରେଟ୍)"</string>
- <string name="prevent_ringing_option_mute_summary" msgid="3509459199090688328">"(ମ୍ୟୁଟ୍) ଅନ୍ ଅଛି"</string>
+ <string name="prevent_ringing_option_mute_summary" msgid="3509459199090688328">"ଚାଲୁ (ମ୍ୟୁଟ୍)"</string>
<string name="prevent_ringing_option_none_summary" msgid="5152618221093037451">"ଅଫ୍"</string>
<string name="pref_title_network_details" msgid="3971074015034595956">"ନେଟୱାର୍କ୍ ବିବରଣୀ"</string>
<string name="about_phone_device_name_warning" msgid="9088572775969880106">"ଆପଣଙ୍କ ଡିଭାଇସ୍ରେ ଥିବା ଆପ୍ଗୁଡ଼ିକୁ ଆପଣଙ୍କର ଡିଭାଇସ୍ ନାମ ଦେଖାଯାଉଛି। ବ୍ଲୁଟୂଥ୍ ଡିଭାଇସ୍ ସହ ଯୋଡ଼ି ହେବାବେଳେ କିମ୍ୱା ଏକ ୱାଇ-ଫାଇ ହଟସ୍ପଟ୍ ସେଟ୍ କରିବା ସମୟରେ, ଏହା ଅନ୍ୟ ଲୋକମାନଙ୍କୁ ମଧ୍ୟ ଦେଖାଦେଇପାରେ।"</string>
diff --git a/tests/CarDeveloperOptions/res/values-pt/strings.xml b/tests/CarDeveloperOptions/res/values-pt/strings.xml
index 41b6c14..1f678e4 100644
--- a/tests/CarDeveloperOptions/res/values-pt/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-pt/strings.xml
@@ -3031,7 +3031,7 @@
<item quantity="one">Mostrar %d item oculto</item>
<item quantity="other">Mostrar %d itens ocultos</item>
</plurals>
- <string name="network_dashboard_title" msgid="8288134139584687806">"Rede e internet"</string>
+ <string name="network_dashboard_title" msgid="8288134139584687806">"Rede e Internet"</string>
<string name="network_dashboard_summary_mobile" msgid="5560545061217580626">"rede móvel"</string>
<string name="network_dashboard_summary_data_usage" msgid="4695629715072542102">"uso de dados"</string>
<string name="network_dashboard_summary_hotspot" msgid="3928610802321995214">"ponto de acesso"</string>
@@ -3423,7 +3423,7 @@
<string name="notification_badge_title" msgid="8989086619255666442">"Permitir ponto de notificação"</string>
<string name="notification_channel_badge_title" msgid="8228215248332054612">"Mostrar ponto de notificação"</string>
<string name="app_notification_override_dnd_title" msgid="1757042206738172601">"Ignorar o Não perturbe"</string>
- <string name="app_notification_override_dnd_summary" msgid="3152957611171210980">"Permitir que essas notificações sejam mostradas mesmo quando o Não perturbe estiver ativado"</string>
+ <string name="app_notification_override_dnd_summary" msgid="3152957611171210980">"Permitir que as notificações sejam mostradas mesmo quando o Não perturbe estiver ativado"</string>
<string name="app_notification_visibility_override_title" msgid="2349335170165637672">"Na tela de bloqueio"</string>
<string name="app_notification_row_banned" msgid="2079325338122151677">"Bloqueadas"</string>
<string name="app_notification_row_priority" msgid="432299064888787236">"Prioridade"</string>
diff --git a/tests/CarDeveloperOptions/res/values-sk/strings.xml b/tests/CarDeveloperOptions/res/values-sk/strings.xml
index fbac769..0d97f16 100644
--- a/tests/CarDeveloperOptions/res/values-sk/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sk/strings.xml
@@ -766,7 +766,7 @@
<string name="bluetooth_pairing_request" msgid="7221745525632573125">"Spárovať so zariadením <xliff:g id="DEVICE_NAME">%1$s</xliff:g>?"</string>
<string name="bluetooth_pairing_key_msg" msgid="1139230917419961975">"Párovací kód Bluetooth"</string>
<string name="bluetooth_enter_passkey_msg" msgid="6205151011298670207">"Zadajte párovací kód a potom stlačte tlačidlo Return alebo Enter"</string>
- <string name="bluetooth_enable_alphanumeric_pin" msgid="9138308197078115672">"Kód PIN obsahuje písmená alebo symboly"</string>
+ <string name="bluetooth_enable_alphanumeric_pin" msgid="9138308197078115672">"PIN obsahuje písmená či symboly"</string>
<string name="bluetooth_pin_values_hint" msgid="8044671726261326240">"Obvykle 0000 alebo 1234"</string>
<string name="bluetooth_pin_values_hint_16_digits" msgid="2665983525706661525">"Musí obsahovať 16 číslic"</string>
<string name="bluetooth_enter_pin_other_device" msgid="1727015949040507621">"Tento kód PIN bude možno treba zadať aj na druhom zariadení."</string>
diff --git a/tests/CarDeveloperOptions/res/values-sq/strings.xml b/tests/CarDeveloperOptions/res/values-sq/strings.xml
index 37c7aa3..ac2c3bc 100644
--- a/tests/CarDeveloperOptions/res/values-sq/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sq/strings.xml
@@ -3555,7 +3555,7 @@
<string name="switch_on_text" msgid="7100491749799298324">"Aktivizuar"</string>
<string name="switch_off_text" msgid="3539551289454353555">"Joaktiv"</string>
<string name="screen_pinning_title" msgid="578020318289781102">"Gozhdimi i ekranit"</string>
- <string name="screen_pinning_description" msgid="3814537379086412278">"Kur ky funksion është i aktivizuar, mund të përdorësh gozhdimin e ekranit për të mbajtur pamjen aktuale të ekranit deri sa ta anulosh gozhdimin.\n\nPër të përdorur gozhdimin e ekranit:\n\n1. Sigurohu që gozhdimi i ekranit është i aktivizuar\n\n2. Hap \"Përmbledhja\"\n\n3. Trokit tek ikona e aplikacionit në krye të ekranit dhe më pas trokit te \"Gozhdo\""</string>
+ <string name="screen_pinning_description" msgid="3814537379086412278">"Kur ky cilësim është i aktivizuar, mund të përdorësh gozhdimin e ekranit për të mbajtur pamjen aktuale të ekranit deri sa ta anulosh gozhdimin.\n\nPër të përdorur gozhdimin e ekranit:\n\n1. Sigurohu që gozhdimi i ekranit është i aktivizuar\n\n2. Hap \"Përmbledhja\"\n\n3. Trokit tek ikona e aplikacionit në krye të ekranit dhe më pas trokit te \"Gozhdo\""</string>
<string name="screen_pinning_unlock_pattern" msgid="1060334707088339444">"Kërko motivin e shkyçjes para anulimit të mbërthimit"</string>
<string name="screen_pinning_unlock_pin" msgid="1441705536015645023">"Zhgozhdimi kërkon PIN-in"</string>
<string name="screen_pinning_unlock_password" msgid="1017776884000170841">"Kërko fjalëkalim para heqjes nga gozhdimi"</string>
diff --git a/tests/CarDeveloperOptions/res/values-sr/strings.xml b/tests/CarDeveloperOptions/res/values-sr/strings.xml
index fbee5c3..edf510e 100644
--- a/tests/CarDeveloperOptions/res/values-sr/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sr/strings.xml
@@ -3190,7 +3190,7 @@
<string name="other_sound_settings" msgid="5250376066099818676">"Други звукови"</string>
<string name="dial_pad_tones_title" msgid="8877212139988655769">"Тонови нумеричке тастатуре"</string>
<string name="screen_locking_sounds_title" msgid="4407110895465866809">"Звукови закључавања екрана"</string>
- <string name="charging_sounds_title" msgid="5070437987230894287">"Мењајући звуци и вибрација"</string>
+ <string name="charging_sounds_title" msgid="5070437987230894287">"Звукови и вибрација пуњења"</string>
<string name="docking_sounds_title" msgid="2573137471605541366">"Звукови монтирања"</string>
<string name="touch_sounds_title" msgid="165237488496165652">"Звукови при додиру"</string>
<string name="vibrate_on_touch_title" msgid="6360155469279157684">"Вибрација при додиру"</string>
diff --git a/tests/CarDeveloperOptions/res/values-sv/strings.xml b/tests/CarDeveloperOptions/res/values-sv/strings.xml
index 5b2dcd1..68c7e34 100644
--- a/tests/CarDeveloperOptions/res/values-sv/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sv/strings.xml
@@ -3149,7 +3149,7 @@
<string name="other_sound_settings" msgid="5250376066099818676">"Andra ljud"</string>
<string name="dial_pad_tones_title" msgid="8877212139988655769">"Knappsatsljud"</string>
<string name="screen_locking_sounds_title" msgid="4407110895465866809">"Ljud vid skärmlåsning"</string>
- <string name="charging_sounds_title" msgid="5070437987230894287">"Laddningsljud- och vibration"</string>
+ <string name="charging_sounds_title" msgid="5070437987230894287">"Laddningsljud och -vibration"</string>
<string name="docking_sounds_title" msgid="2573137471605541366">"Ljud via dockningsstationen"</string>
<string name="touch_sounds_title" msgid="165237488496165652">"Ljud vid tryck"</string>
<string name="vibrate_on_touch_title" msgid="6360155469279157684">"Vibration vid tryck"</string>
diff --git a/tests/CarDeveloperOptions/res/values-sw/strings.xml b/tests/CarDeveloperOptions/res/values-sw/strings.xml
index a07110a..b665336 100644
--- a/tests/CarDeveloperOptions/res/values-sw/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sw/strings.xml
@@ -2094,7 +2094,7 @@
<string name="accessibility_vibration_settings_title" msgid="1902649657883159406">"Mtetemo"</string>
<string name="accessibility_notification_vibration_title" msgid="1005799039440510298">"Mtetemo wa arifa"</string>
<string name="accessibility_ring_vibration_title" msgid="7943341443551359985">"Mtetemo wa mlio"</string>
- <string name="accessibility_touch_vibration_title" msgid="285890135612038092">"Mtetemo wa mguso"</string>
+ <string name="accessibility_touch_vibration_title" msgid="285890135612038092">"Mtetemo inapoguswa"</string>
<string name="accessibility_service_master_switch_title" msgid="2734791644475782924">"Tumia huduma"</string>
<string name="accessibility_daltonizer_master_switch_title" msgid="4855011639012300777">"Tumia usahihishaji wa rangi"</string>
<string name="accessibility_caption_master_switch_title" msgid="6373335123229234053">"Tumia manukuu"</string>
@@ -3152,7 +3152,7 @@
<string name="charging_sounds_title" msgid="5070437987230894287">"Sauti za kuchaji na mtetemo"</string>
<string name="docking_sounds_title" msgid="2573137471605541366">"Kuambatisha sauti"</string>
<string name="touch_sounds_title" msgid="165237488496165652">"Sauti inapoguswa"</string>
- <string name="vibrate_on_touch_title" msgid="6360155469279157684">"Mtetemo wa mguso"</string>
+ <string name="vibrate_on_touch_title" msgid="6360155469279157684">"Mtetemo inapoguswa"</string>
<string name="vibrate_on_touch_summary" msgid="5504424764028676043">"Majibu unayoweza kuhisi kwa kugusa, kibodi na mengineyo"</string>
<string name="dock_audio_media_title" msgid="1859521680502040781">"Cheza kutumia spika ya kituo"</string>
<string name="dock_audio_media_disabled" msgid="4300752306178486302">"Sauti zote"</string>
@@ -3341,11 +3341,11 @@
<string name="notification_importance_low" msgid="7609797151662295364">"Onyesha chinichini"</string>
<string name="notification_importance_default" msgid="4091563759103917166">"Toa sauti"</string>
<string name="notification_importance_high" msgid="7973764540402436656">"Toa sauti na ibukizi kwenye skrini"</string>
- <string name="notification_importance_high_silent" msgid="3177662759865661155">"Ichomoze kwenye skrini"</string>
+ <string name="notification_importance_high_silent" msgid="3177662759865661155">"Zionekane kwenye skrini"</string>
<string name="notification_importance_min_title" msgid="705872537330744154">"Punguza"</string>
<string name="notification_importance_low_title" msgid="2956199021781786232">"Wastani"</string>
<string name="notification_importance_default_title" msgid="7985549807203332482">"Juu"</string>
- <string name="notification_importance_high_title" msgid="7258373094258585858">"Ichomoze kwenye skrini"</string>
+ <string name="notification_importance_high_title" msgid="7258373094258585858">"Zionekane kwenye skrini"</string>
<string name="notification_block_title" msgid="2570364198866886906">"Zuia"</string>
<string name="notification_silence_title" msgid="6959637402003838093">"Ionyeshe bila kutoa sauti"</string>
<string name="notification_alert_title" msgid="750683027055192648">"Arifa"</string>
diff --git a/tests/CarDeveloperOptions/res/values-te/strings.xml b/tests/CarDeveloperOptions/res/values-te/strings.xml
index b646142..6e54dda 100644
--- a/tests/CarDeveloperOptions/res/values-te/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-te/strings.xml
@@ -2323,9 +2323,9 @@
<string name="smart_battery_manager_title" msgid="5744035036663849515">"బ్యాటరీ మేనేజర్"</string>
<string name="smart_battery_title" msgid="4919670408532804351">"యాప్లను ఆటోమేటిక్గా నిర్వహించండి"</string>
<string name="smart_battery_summary" msgid="640027046471198174">"మీరు తరచుగా ఉపయోగించని యాప్ల కోసం బ్యాటరీని పరిమితం చేయండి"</string>
- <string name="smart_battery_footer" product="default" msgid="3971715848890205632">"యాప్లు బ్యాటరీ శక్తిని హరిస్తున్నయని బ్యాటరీ మేనేజర్ గుర్తించినప్పుడు, ఈ యాప్లను పరిమితం చేసే ఎంపిక మీకు ఉంటుంది. పరిమితం చేయబడిన యాప్లు సరిగ్గా పనిచేయకపోవచ్చు మరియు నోటిఫికేషన్లు రావడానికి ఆలస్యం కావచ్చు."</string>
- <string name="smart_battery_footer" product="tablet" msgid="3971715848890205632">"యాప్లు బ్యాటరీ శక్తిని హరిస్తున్నయని బ్యాటరీ మేనేజర్ గుర్తించినప్పుడు, ఈ యాప్లను పరిమితం చేసే ఎంపిక మీకు ఉంటుంది. పరిమితం చేయబడిన యాప్లు సరిగ్గా పనిచేయకపోవచ్చు మరియు నోటిఫికేషన్లు రావడానికి ఆలస్యం కావచ్చు."</string>
- <string name="smart_battery_footer" product="device" msgid="3971715848890205632">"యాప్లు బ్యాటరీ శక్తిని హరిస్తున్నయని బ్యాటరీ మేనేజర్ గుర్తించినప్పుడు, ఈ యాప్లను పరిమితం చేసే ఎంపిక మీకు ఉంటుంది. పరిమితం చేయబడిన యాప్లు సరిగ్గా పనిచేయకపోవచ్చు మరియు నోటిఫికేషన్లు రావడానికి ఆలస్యం కావచ్చు."</string>
+ <string name="smart_battery_footer" product="default" msgid="3971715848890205632">"ఏవైనా యాప్లు బ్యాటరీని అధికంగా వాడుతున్నాయని బ్యాటరీ మేనేజర్ గుర్తించినప్పుడు, ఆ యాప్లను పరిమితం చేసే ఎంపిక మీకు ఉంటుంది. పరిమితం చేయబడిన యాప్లు సరిగ్గా పనిచేయకపోవచ్చు, వాటి నోటిఫికేషన్లు రావడానికి ఆలస్యం కావచ్చు."</string>
+ <string name="smart_battery_footer" product="tablet" msgid="3971715848890205632">"ఏవైనా యాప్లు బ్యాటరీని అధికంగా వాడుతున్నాయని బ్యాటరీ మేనేజర్ గుర్తించినప్పుడు, ఆ యాప్లను పరిమితం చేసే ఎంపిక మీకు ఉంటుంది. పరిమితం చేయబడిన యాప్లు సరిగ్గా పనిచేయకపోవచ్చు, వాటి నోటిఫికేషన్లు రావడానికి ఆలస్యం కావచ్చు."</string>
+ <string name="smart_battery_footer" product="device" msgid="3971715848890205632">"ఏవైనా యాప్లు బ్యాటరీని అధికంగా వాడుతున్నాయని బ్యాటరీ మేనేజర్ గుర్తించినప్పుడు, ఆ యాప్లను పరిమితం చేసే ఎంపిక మీకు ఉంటుంది. పరిమితం చేయబడిన యాప్లు సరిగ్గా పనిచేయకపోవచ్చు, వాటి నోటిఫికేషన్లు రావడానికి ఆలస్యం కావచ్చు."</string>
<string name="restricted_app_title" msgid="4957644700640127606">"నియంత్రించబడిన యాప్లు"</string>
<plurals name="restricted_app_summary" formatted="false" msgid="7609538735465186040">
<item quantity="other">%1$d యాప్ల కోసం బ్యాటరీ వినియోగాన్ని పరిమితం చేయడం</item>
@@ -3041,7 +3041,7 @@
<string name="connected_devices_dashboard_no_driving_mode_summary" msgid="3524409078596318803">"బ్లూటూత్, NFC"</string>
<string name="connected_devices_dashboard_no_driving_mode_no_nfc_summary" msgid="7881286613528299400">"బ్లూటూత్"</string>
<string name="app_and_notification_dashboard_title" msgid="8448096608058843730">"యాప్లు & నోటిఫికేషన్లు"</string>
- <string name="app_and_notification_dashboard_summary" msgid="4165181440955038145">"అసిస్టెంట్, ఇటీవలి యాప్లు, డిఫాల్ట్ యాప్లు"</string>
+ <string name="app_and_notification_dashboard_summary" msgid="4165181440955038145">"Assistant, ఇటీవలి యాప్లు, డిఫాల్ట్ యాప్లు"</string>
<string name="notification_settings_work_profile" msgid="7190550347842400029">"కార్యాలయ ప్రొఫైల్లో ఉన్న యాప్లకు సంబంధించి నోటిఫికేషన్ యాక్సెస్ అందుబాటులో లేదు."</string>
<string name="account_dashboard_title" msgid="4734300939532555885">"ఖాతాలు"</string>
<string name="account_dashboard_default_summary" msgid="6822549669771936206">"ఖాతాలు జోడించబడలేదు"</string>
@@ -4473,7 +4473,7 @@
<string name="hwui_force_dark_title" msgid="3744825212652331461">"ఫోర్స్-డార్క్ను అధిగమించడం"</string>
<string name="hwui_force_dark_summary" msgid="2051891908674765817">"ఫోర్స్-డార్క్ ఫీచర్ను అధిగమించడం ఎల్లప్పుడూ ఆన్లో ఉండాలి"</string>
<string name="privacy_dashboard_title" msgid="8764930992456607513">"గోప్యత"</string>
- <string name="privacy_dashboard_summary" msgid="7916431309860824945">"అనుమతులు, ఖాతా కార్యకలాపం, వ్యక్తిగత డేటా"</string>
+ <string name="privacy_dashboard_summary" msgid="7916431309860824945">"అనుమతులు, ఖాతా యాక్టివిటీ, వ్యక్తిగత డేటా"</string>
<string name="contextual_card_dismiss_remove" msgid="1750420285615827309">"తీసివేయి"</string>
<string name="contextual_card_dismiss_keep" msgid="3204450672928193661">"Keep"</string>
<string name="contextual_card_dismiss_confirm_message" msgid="2352465079667730312">"ఈ సూచనని తీసేయాలా?"</string>
diff --git a/tests/CarDeveloperOptions/res/values-uk/strings.xml b/tests/CarDeveloperOptions/res/values-uk/strings.xml
index 2816cc8..b39d1a4 100644
--- a/tests/CarDeveloperOptions/res/values-uk/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-uk/strings.xml
@@ -1246,7 +1246,7 @@
<string name="adaptive_sleep_summary_off" msgid="2891586225954973431">"Вимкнено"</string>
<string name="adaptive_sleep_description" msgid="812673735459170009">"Не дає екрану вимикатись, якщо ви на нього дивитесь."</string>
<string name="adaptive_sleep_privacy" msgid="5706802215479902623">"Розпізнавання уваги за допомогою передньої камери визначає, чи дивиться користувач на екран. Ця функція працює лише на пристрої, не зберігає зображень і не надсилає їх у Google."</string>
- <string name="night_display_title" msgid="1305002424893349814">"Нічний режим"</string>
+ <string name="night_display_title" msgid="1305002424893349814">"Нічний екран"</string>
<string name="night_display_text" msgid="5330502493684652527">"У нічному режимі екран набуває бурштинового відтінку. Це знімає напруження очей при тьмяному освітленні та допомагає легше заснути."</string>
<string name="night_display_auto_mode_title" msgid="8493573087102481588">"Розклад"</string>
<string name="night_display_auto_mode_never" msgid="2897444637217807088">"Ніколи"</string>
@@ -4464,7 +4464,7 @@
<string name="change_wifi_state_title" msgid="5140754955787584174">"Керування Wi-Fi"</string>
<string name="change_wifi_state_app_detail_switch" msgid="6489090744937816260">"Дозволити додатку керувати Wi-Fi"</string>
<string name="change_wifi_state_app_detail_summary" msgid="614854822469259860">"Дозволити цьому додатку вмикати чи вимикати Wi-Fi, шукати мережі Wi-Fi та підключатися до них, додавати або видаляти мережі чи запускати лише локальну точку доступу"</string>
- <string name="media_output_title" msgid="8710632337456601848">"Відтворювати медіа-вміст у додатку"</string>
+ <string name="media_output_title" msgid="8710632337456601848">"Відтворення медіаконтенту"</string>
<string name="media_output_default_summary" msgid="3159237976830415584">"Цей пристрій"</string>
<string name="media_output_summary" product="default" msgid="6294261435613551178">"Телефон"</string>
<string name="media_output_summary" product="tablet" msgid="6672024060360538526">"Планшет"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ur/strings.xml b/tests/CarDeveloperOptions/res/values-ur/strings.xml
index 787d550..4a881ea 100644
--- a/tests/CarDeveloperOptions/res/values-ur/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ur/strings.xml
@@ -1128,7 +1128,7 @@
<string name="notification_sound_title" msgid="6812164482799723931">"ڈیفالٹ اطلاع کی آواز"</string>
<string name="incoming_call_volume_title" msgid="4736570528754310450">"رنگ ٹون"</string>
<string name="notification_volume_title" msgid="6022562909288085275">"اطلاع"</string>
- <string name="checkbox_notification_same_as_incoming_call" msgid="7312942422655861175">"اطلاعات کیلئے آنے والی کال کا والیوم استعمال کریں"</string>
+ <string name="checkbox_notification_same_as_incoming_call" msgid="7312942422655861175">"اطلاعات کیلئے اِن کمنگ کال کا والیوم استعمال کریں"</string>
<string name="home_work_profile_not_supported" msgid="6137073723297076818">"کام کے پروفائلز کا تعاون نہیں کرتا ہے"</string>
<string name="notification_sound_dialog_title" msgid="6653341809710423276">"ڈیفالٹ اطلاع کی آواز"</string>
<string name="media_volume_title" msgid="1030438549497800914">"میڈیا"</string>
@@ -3401,7 +3401,7 @@
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> زمرے</item>
<item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> زمرہ</item>
</plurals>
- <string name="no_channels" msgid="8884254729302501652">"اس ایپ نے کوئی اطلاعات شائع نہیں کیا ہے"</string>
+ <string name="no_channels" msgid="8884254729302501652">"اس ایپ نے کوئی اطلاعات شائع نہیں کی ہیں"</string>
<string name="app_settings_link" msgid="8465287765715790984">"ایپ میں اضافی ترتیبات"</string>
<string name="app_notification_listing_summary_zero" msgid="4047782719487686699">"سبھی ایپس کے لیے آن ہے"</string>
<plurals name="app_notification_listing_summary_others" formatted="false" msgid="1161774065480666519">
diff --git a/tests/CarDeveloperOptions/res/values-uz/strings.xml b/tests/CarDeveloperOptions/res/values-uz/strings.xml
index 401fb18..f2fb286 100644
--- a/tests/CarDeveloperOptions/res/values-uz/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-uz/strings.xml
@@ -732,7 +732,7 @@
<string name="bluetooth_pairing_request" msgid="7221745525632573125">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasiga ulansinmi?"</string>
<string name="bluetooth_pairing_key_msg" msgid="1139230917419961975">"Bluetooth orqali ulanish kodi"</string>
<string name="bluetooth_enter_passkey_msg" msgid="6205151011298670207">"Ulanish kodini kiriting va keyin “Return” yoki “Enter” tugmasini bosing"</string>
- <string name="bluetooth_enable_alphanumeric_pin" msgid="9138308197078115672">"PIN-kod harflar va belgilardan iborat bo‘ladi"</string>
+ <string name="bluetooth_enable_alphanumeric_pin" msgid="9138308197078115672">"Harflar yoki maxsus belgilardan iborat PIN kod"</string>
<string name="bluetooth_pin_values_hint" msgid="8044671726261326240">"Odatda 0000 yoki 1234"</string>
<string name="bluetooth_pin_values_hint_16_digits" msgid="2665983525706661525">"16 ta raqam bo‘lishi lozim"</string>
<string name="bluetooth_enter_pin_other_device" msgid="1727015949040507621">"Ushbu PIN kodni boshqa qurilmada ham terish lozim bo‘lishi mumkin."</string>
@@ -2754,7 +2754,7 @@
<string name="data_usage_metered_no" msgid="1961524615778610008">"Bepul"</string>
<string name="data_usage_disclaimer" msgid="4683321532922590425">"Aloqa operatorining hisob-kitobi qurilmanikidan farq qilishi mumkin."</string>
<string name="cryptkeeper_emergency_call" msgid="4625420047524693116">"Favqulodda chaqiruv"</string>
- <string name="cryptkeeper_return_to_call" msgid="4433942821196822815">"Chaqiruvga qaytish"</string>
+ <string name="cryptkeeper_return_to_call" msgid="4433942821196822815">"Suhbatga qaytish"</string>
<string name="vpn_name" msgid="3538818658670774080">"Tarmoq nomi"</string>
<string name="vpn_type" msgid="6389116710008658550">"Turi"</string>
<string name="vpn_server" msgid="5216559017318406820">"Server manzili"</string>
diff --git a/tests/CarDeveloperOptions/res/values-vi/strings.xml b/tests/CarDeveloperOptions/res/values-vi/strings.xml
index ab20398..1efea2d 100644
--- a/tests/CarDeveloperOptions/res/values-vi/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-vi/strings.xml
@@ -34,7 +34,7 @@
<string name="radio_info_data_connection_enable" msgid="2554249462719717119">"Bật kết nối dữ liệu"</string>
<string name="radio_info_data_connection_disable" msgid="2430609627397999371">"Tắt kết nối dữ liệu"</string>
<string name="volte_provisioned_switch_string" msgid="6326756678226686704">"Đã cấp phép VoLTE"</string>
- <string name="vt_provisioned_switch_string" msgid="7458479879009293613">"Đã cấp phép gọi điện video"</string>
+ <string name="vt_provisioned_switch_string" msgid="7458479879009293613">"Đã cấp phép gọi video"</string>
<string name="wfc_provisioned_switch_string" msgid="5446697646596639516">"Đã cấp phép gọi điện qua Wi-Fi"</string>
<string name="eab_provisioned_switch_string" msgid="3921103790584572430">"Đã cấp phép hiện diện/EAB"</string>
<string name="cbrs_data_switch_string" msgid="9120919504831536183">"Dữ liệu Cbrs"</string>
@@ -53,7 +53,7 @@
<string name="radio_info_ims_reg_status_not_registered" msgid="1286050699734226077">"Chưa được đăng ký"</string>
<string name="radio_info_ims_feature_status_available" msgid="2040629393134756058">"Khả dụng"</string>
<string name="radio_info_ims_feature_status_unavailable" msgid="3348223769202693596">"Không khả dụng"</string>
- <string name="radio_info_ims_reg_status" msgid="4771711884059371514">"Đăng ký IMS: <xliff:g id="STATUS">%1$s</xliff:g>\nThoại trên nền LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nThoại qua Wi-Fi: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nGọi điện video: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nGiao diện UT: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
+ <string name="radio_info_ims_reg_status" msgid="4771711884059371514">"Đăng ký IMS: <xliff:g id="STATUS">%1$s</xliff:g>\nThoại trên nền LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nThoại qua Wi-Fi: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nGọi video: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nGiao diện UT: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
<string name="radioInfo_service_in" msgid="1297020186765943857">"Đang sử dụng"</string>
<string name="radioInfo_service_out" msgid="8460363463722476510">"Không có dịch vụ"</string>
<string name="radioInfo_service_emergency" msgid="7674989004735662599">"Chỉ cuộc gọi khẩn cấp"</string>
@@ -3403,8 +3403,8 @@
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> loại</item>
<item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> loại</item>
</plurals>
- <string name="no_channels" msgid="8884254729302501652">"Ứng dụng chưa đăng bất kỳ thông báo nào"</string>
- <string name="app_settings_link" msgid="8465287765715790984">"Cài đặt bổ sung trong ứng dụng"</string>
+ <string name="no_channels" msgid="8884254729302501652">"Ứng dụng này chưa đăng bất kỳ thông báo nào"</string>
+ <string name="app_settings_link" msgid="8465287765715790984">"Tùy chọn cài đặt bổ sung trong ứng dụng"</string>
<string name="app_notification_listing_summary_zero" msgid="4047782719487686699">"Đang bật cho tất cả ứng dụng"</string>
<plurals name="app_notification_listing_summary_others" formatted="false" msgid="1161774065480666519">
<item quantity="other">Tắt cho <xliff:g id="COUNT_1">%d</xliff:g> ứng dụng</item>
@@ -3422,7 +3422,7 @@
<string name="notification_content_block_summary" msgid="2743896875255591743">"Không bao giờ hiển thị thông báo trong ngăn thông báo hoặc trên thiết bị ngoại vi"</string>
<string name="notification_badge_title" msgid="8989086619255666442">"Cho phép dấu chấm thông báo"</string>
<string name="notification_channel_badge_title" msgid="8228215248332054612">"Hiển thị dấu chấm thông báo"</string>
- <string name="app_notification_override_dnd_title" msgid="1757042206738172601">"Ghi đè Không làm phiền"</string>
+ <string name="app_notification_override_dnd_title" msgid="1757042206738172601">"Vô hiệu hóa chế độ Không làm phiền"</string>
<string name="app_notification_override_dnd_summary" msgid="3152957611171210980">"Cho phép những thông báo này tiếp tục làm gián đoạn khi chế độ Không làm phiền đang bật"</string>
<string name="app_notification_visibility_override_title" msgid="2349335170165637672">"Trên màn hình khóa"</string>
<string name="app_notification_row_banned" msgid="2079325338122151677">"Bị chặn"</string>
@@ -3511,8 +3511,8 @@
<string name="zen_mode_bypassing_apps" msgid="3080739479028713449">"Cho phép ứng dụng ghi đè"</string>
<string name="zen_mode_bypassing_apps_title" msgid="2115024664615538847">"Ứng dụng ngoại lệ"</string>
<plurals name="zen_mode_bypassing_apps_subtext" formatted="false" msgid="8723144434730871572">
- <item quantity="other">Thông báo từ <xliff:g id="NUMBER">%1$d</xliff:g> ứng dụng có thể ghi đè chế độ Không làm phiền</item>
- <item quantity="one">Thông báo từ 1 ứng dụng có thể ghi đè chế độ Không làm phiền</item>
+ <item quantity="other">Thông báo từ <xliff:g id="NUMBER">%1$d</xliff:g> ứng dụng có thể vô hiệu hóa chế độ Không làm phiền</item>
+ <item quantity="one">Thông báo từ 1 ứng dụng có thể vô hiệu hóa chế độ Không làm phiền</item>
</plurals>
<string name="zen_mode_events_list" msgid="8578102701815684873">"sự kiện"</string>
<string name="zen_mode_all_callers" msgid="4455039040077343838">"bất kỳ ai"</string>
diff --git a/tests/CarDeveloperOptions/res/values-zh-rHK/strings.xml b/tests/CarDeveloperOptions/res/values-zh-rHK/strings.xml
index 51500a7..5e42e3c 100644
--- a/tests/CarDeveloperOptions/res/values-zh-rHK/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-zh-rHK/strings.xml
@@ -3423,7 +3423,7 @@
<string name="notification_content_block_summary" msgid="2743896875255591743">"永不在通知欄或周邊裝置上顯示通知"</string>
<string name="notification_badge_title" msgid="8989086619255666442">"允許通知圓點"</string>
<string name="notification_channel_badge_title" msgid="8228215248332054612">"顯示通知圓點"</string>
- <string name="app_notification_override_dnd_title" msgid="1757042206738172601">"蓋過「請勿騷擾」設定"</string>
+ <string name="app_notification_override_dnd_title" msgid="1757042206738172601">"忽略「請勿騷擾」設定"</string>
<string name="app_notification_override_dnd_summary" msgid="3152957611171210980">"「請勿騷擾」模式開啟時繼續接收這些通知"</string>
<string name="app_notification_visibility_override_title" msgid="2349335170165637672">"在上鎖畫面上"</string>
<string name="app_notification_row_banned" msgid="2079325338122151677">"已封鎖"</string>
@@ -3512,8 +3512,8 @@
<string name="zen_mode_bypassing_apps" msgid="3080739479028713449">"允許應用程式取代「請勿騷擾」"</string>
<string name="zen_mode_bypassing_apps_title" msgid="2115024664615538847">"應用程式例外情況"</string>
<plurals name="zen_mode_bypassing_apps_subtext" formatted="false" msgid="8723144434730871572">
- <item quantity="other"><xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式的通知可蓋過「請勿騷擾」設定</item>
- <item quantity="one">1 個應用程式的通知可蓋過「請勿騷擾」設定</item>
+ <item quantity="other"><xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式的通知可忽略「請勿騷擾」設定</item>
+ <item quantity="one">1 個應用程式的通知可忽略「請勿騷擾」設定</item>
</plurals>
<string name="zen_mode_events_list" msgid="8578102701815684873">"活動"</string>
<string name="zen_mode_all_callers" msgid="4455039040077343838">"任何人"</string>
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/CarTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/CarPermisisonTest.java
similarity index 97%
rename from tests/CarSecurityPermissionTest/src/com/android/car/CarTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/CarPermisisonTest.java
index 90378a8..07f4310 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/CarTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/CarPermisisonTest.java
@@ -29,10 +29,10 @@
import org.junit.runner.RunWith;
/**
- * This class contains security permission tests for the {@link CarTest}'s system APIs.
+ * This class contains security permission tests for the {@link Car}'s system APIs.
*/
@RunWith(AndroidJUnit4.class)
-public class CarTest {
+public class CarPermisisonTest {
private Car mCar = null;
@Before
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicPermissionTest.java
similarity index 98%
rename from tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicPermissionTest.java
index 92226a1..975bc74 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicPermissionTest.java
@@ -40,11 +40,11 @@
* This class contains security permission tests for the {@link CarPropertyManager}'s public APIs.
*/
@RunWith(AndroidJUnit4.class)
-public class CarPropertyManagerPublicTest {
+public class CarPropertyManagerPublicPermissionTest {
private Car mCar = null;
private CarPropertyManager mPropertyManager;
private HashSet<Integer> mProps = new HashSet<>();
- private static final String TAG = CarPropertyManagerPublicTest.class.getSimpleName();
+ private static final String TAG = CarPropertyManagerPublicPermissionTest.class.getSimpleName();
private static final Integer DUMMY_AREA_ID = VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
// Dummy values for setter test.
private static final int DUMMY_PROPERTY_VALUE_INTEGER = 1;
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicPermissionTest.java
similarity index 97%
rename from tests/CarSecurityPermissionTest/src/com/android/car/CarPublicTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/CarPublicPermissionTest.java
index af1867b..16aef37 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicPermissionTest.java
@@ -29,10 +29,10 @@
import org.junit.runner.RunWith;
/**
- * This class contains security permission tests for the {@link CarTest}'s public APIs.
+ * This class contains security permission tests for the {@link CarPermisisonTest}'s public APIs.
*/
@RunWith(AndroidJUnit4.class)
-public class CarPublicTest {
+public class CarPublicPermissionTest {
private Car mCar = null;
@Before
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/content/pm/CarPackageManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/content/pm/CarPackageManagerPermissionTest.java
new file mode 100644
index 0000000..f1e23ba
--- /dev/null
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/content/pm/CarPackageManagerPermissionTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.car.content.pm.CarAppBlockingPolicy;
+import android.car.content.pm.CarPackageManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * This class contains security permission tests for {@link CarPackageManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarPackageManagerPermissionTest {
+ private Car mCar;
+ private CarPackageManager mPm;
+
+ @Before
+ public void setUp() throws Exception {
+ mCar = Car.createCar(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ mPm = (CarPackageManager) mCar.getCarManager(Car.PACKAGE_SERVICE);
+ }
+
+ @After
+ public void tearDown() {
+ mCar.disconnect();
+ }
+
+ @Test
+ public void testRestartTask() {
+ assertThrows(SecurityException.class, () -> mPm.restartTask(0));
+ }
+
+ @Test
+ public void testSetAppBlockingPolicy() {
+ String packageName = "com.android";
+ CarAppBlockingPolicy policy = new CarAppBlockingPolicy(null, null);
+ assertThrows(SecurityException.class, () -> mPm.setAppBlockingPolicy(packageName, policy,
+ 0));
+ }
+
+}
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
similarity index 97%
rename from tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
index 354446e..9730d13 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
@@ -35,7 +35,7 @@
* This class contains security permission tests for the {@link CarInputManager}'s system APIs.
*/
@RunWith(AndroidJUnit4.class)
-public class CarInputManagerTest {
+public class CarInputManagerPermisisonTest {
private Car mCar;
private CarInputManager mCarInputManager;
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPermissionTest.java
similarity index 87%
rename from tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPermissionTest.java
index 1bb81ec..1e4391b 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPermissionTest.java
@@ -25,16 +25,12 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeNotNull;
import static org.testng.Assert.expectThrows;
import android.car.Car;
import android.car.media.CarAudioManager;
import android.content.Context;
-import android.hardware.display.DisplayManager;
import android.os.Handler;
-import android.view.Display;
-import android.view.DisplayAddress;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -43,15 +39,13 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Arrays;
import java.util.Objects;
-import java.util.Optional;
/**
* This class contains security permission tests for the {@link CarAudioManager}'s system APIs.
*/
@RunWith(AndroidJUnit4.class)
-public final class CarAudioManagerTest {
+public final class CarAudioManagerPermissionTest {
private static final int GROUP_ID = 0;
private static final int UID = 10;
@@ -220,22 +214,6 @@
}
@Test
- public void getZoneIdForDisplayPermission() {
- Display display = getPhysicalDisplay();
- assumeNotNull(display);
- Exception e = expectThrows(SecurityException.class,
- () -> mCarAudioManager.getZoneIdForDisplay(display));
- assertThat(e.getMessage()).contains(PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
- }
-
- @Test
- public void getZoneIdForDisplayPortIdPermission() {
- Exception e = expectThrows(SecurityException.class,
- () -> mCarAudioManager.getZoneIdForDisplayPortId(Byte.MAX_VALUE));
- assertThat(e.getMessage()).contains(PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
- }
-
- @Test
public void getOutputDeviceForUsagePermission() {
Exception e = expectThrows(SecurityException.class,
() -> mCarAudioManager.getOutputDeviceForUsage(PRIMARY_AUDIO_ZONE, USAGE_MEDIA));
@@ -255,12 +233,4 @@
() -> mCarAudioManager.onCarDisconnected());
assertThat(e.getMessage()).contains(PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
}
-
- private Display getPhysicalDisplay() {
- DisplayManager displayManager = (DisplayManager) mContext.getSystemService(
- Context.DISPLAY_SERVICE);
- Optional<Display> physical = Arrays.stream(displayManager.getDisplays()).filter(
- display -> (display.getAddress() instanceof DisplayAddress.Physical)).findFirst();
- return physical.orElse(null);
- }
}
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicPermissionTest.java
similarity index 96%
rename from tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicPermissionTest.java
index 2877043..b9cafc4 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicPermissionTest.java
@@ -39,7 +39,7 @@
* This class contains security permission tests for the {@link CarAudioManager}'s public APIs.
*/
@RunWith(AndroidJUnit4.class)
-public final class CarAudioManagerPublicTest {
+public final class CarAudioManagerPublicPermissionTest {
private CarAudioManager mCarAudioManager;
@Before
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
index 86a2853..3a5ea1b 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
@@ -16,6 +16,7 @@
package com.android.car.user;
+import static android.Manifest.permission.CREATE_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_USERS;
@@ -35,6 +36,7 @@
import android.car.user.CarUserManager.UserLifecycleListener;
import android.content.Context;
import android.os.Handler;
+import android.os.UserManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -67,9 +69,21 @@
@Test
public void testSwitchUserPermission() throws Exception {
- int target_uid = 100;
+ Exception e = expectThrows(SecurityException.class, () -> mCarUserManager.switchUser(100));
+ assertThat(e.getMessage()).contains(MANAGE_USERS);
+ }
+
+ @Test
+ public void testCreateUserPermission() throws Exception {
Exception e = expectThrows(SecurityException.class,
- () -> mCarUserManager.switchUser(target_uid));
+ () -> mCarUserManager.createUser(null, UserManager.USER_TYPE_FULL_SECONDARY, 0));
+ assertThat(e.getMessage()).contains(MANAGE_USERS);
+ assertThat(e.getMessage()).contains(CREATE_USERS);
+ }
+
+ @Test
+ public void testRemoveUserPermission() throws Exception {
+ Exception e = expectThrows(SecurityException.class, () -> mCarUserManager.removeUser(100));
assertThat(e.getMessage()).contains(MANAGE_USERS);
}
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml b/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
index 7c77b1b..cfc3515 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
@@ -28,6 +28,50 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_weight="1"
+ android:visibility="gone"
+ android:id="@+id/audio_select_device_address_layout">
+ <TextView
+ android:id="@+id/select_device_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/select_device" />
+ <Space
+ android:layout_width="3dp"
+ android:layout_height="match_parent" />
+ <Spinner
+ android:id="@+id/device_address_spinner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <Space
+ android:layout_width="3dp"
+ android:layout_height="match_parent" />
+ <Button
+ android:id="@+id/button_device_media_play_start"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/play" />
+ <Space
+ android:layout_width="3dp"
+ android:layout_height="match_parent" />
+ <Button
+ android:id="@+id/button_device_media_play_once"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/play_pcm_once" />
+ <Space
+ android:layout_width="3dp"
+ android:layout_height="match_parent" />
+ <Button
+ android:id="@+id/button_device_media_play_stop"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/stop" />
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
android:layout_weight="1" >
<TextView
android:id="@+id/zone_selection_title"
@@ -72,50 +116,6 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_weight="1"
- android:visibility="gone"
- android:id="@+id/audio_display_layout">
- <TextView
- android:id="@+id/select_display_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/select_display" />
- <Space
- android:layout_width="3dp"
- android:layout_height="match_parent" />
- <Spinner
- android:id="@+id/display_spinner"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <Space
- android:layout_width="3dp"
- android:layout_height="match_parent" />
- <Button
- android:id="@+id/button_display_media_play_start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/play" />
- <Space
- android:layout_width="3dp"
- android:layout_height="match_parent" />
- <Button
- android:id="@+id/button_display_media_play_once"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/play_pcm_once" />
- <Space
- android:layout_width="3dp"
- android:layout_height="match_parent" />
- <Button
- android:id="@+id/button_display_media_play_stop"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/stop" />
- </LinearLayout>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="1" >
<LinearLayout
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 80e6fc4..162c37d 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -137,7 +137,7 @@
<string name="mock_audio_on" translatable="false">Audio Mocking On</string>
<string name="mock_audio_off" translatable="false">Audio Mocking Off</string>
<string name="select_zone_to_hear_on_speaker" translatable="false">Select Zone to hear on speaker</string>
- <string name="select_display" translatable="false">Select Display</string>
+ <string name="select_device" translatable="false">Select Device</string>
<string name="activity_current_zone_id" translatable="false">Activity\'s current audio zone id</string>
<string name="no_zone" translatable="false">none</string>
<string name="phone_audio_player" translatable="false">Phone Player</string>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
index d4e30b7..bf5865a 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.hardware.display.DisplayManager;
import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioFocusRequest;
@@ -36,8 +35,6 @@
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
-import android.view.Display;
-import android.view.DisplayAddress;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -82,7 +79,7 @@
private AudioPlayer mVrPlayer;
private AudioPlayer mSystemPlayer;
private AudioPlayer mWavPlayer;
- private AudioPlayer mMusicPlayerForSelectedDisplay;
+ private AudioPlayer mMusicPlayerForSelectedDeviceAddress;
private HwAudioSource mHwAudioSource;
private AudioPlayer[] mAllPlayers;
@@ -97,15 +94,14 @@
private AudioAttributes mVrAudioAttrib;
private AudioAttributes mRadioAudioAttrib;
private AudioAttributes mSystemSoundAudioAttrib;
- private AudioAttributes mMusicAudioAttribForDisplay;
+ private AudioAttributes mMusicAudioAttribForDeviceAddress;
private CarEmulator mCarEmulator;
private CarAudioManager mCarAudioManager;
private Spinner mZoneSpinner;
ArrayAdapter<Integer> mZoneAdapter;
- private Spinner mDisplaySpinner;
- ArrayAdapter<Integer> mDisplayAdapter;
- private LinearLayout mDisplayLayout;
- private int mOldZonePosition;
+ private Spinner mDeviceAddressSpinner;
+ ArrayAdapter<CarAudioZoneDeviceInfo> mDeviceAddressAdapter;
+ private LinearLayout mDeviceAddressLayout;
private final Object mLock = new Object();
@@ -114,8 +110,6 @@
private OnAudioFocusChangeListener mMediaWithDelayedFocusListener;
private TextView mDelayedStatusText;
- private static int sDefaultExtraTestScreenPortId = 1;
-
private final OnAudioFocusChangeListener mNavFocusListener = (focusChange) -> {
Log.i(TAG, "Nav focus change:" + focusChange);
};
@@ -160,9 +154,7 @@
handleSetUpZoneSelection();
- if (mCarAudioManager.isDynamicRoutingEnabled()) {
- setUpDisplayPlayer();
- }
+ setUpDeviceAddressPlayer();
});
}
@@ -173,8 +165,8 @@
//Zone Spinner
setUpZoneSpinnerView(view);
- //Display layout
- setUpDisplayLayoutView(view);
+ // Device Address layout
+ setUpDeviceAddressLayoutView(view);
connectCar();
initializePlayers();
@@ -293,17 +285,17 @@
}
});
- // Manage buttons for audio player for displays
- view.findViewById(R.id.button_display_media_play_start).setOnClickListener(v -> {
- startDisplayAudio();
+ // Manage buttons for audio player for device address
+ view.findViewById(R.id.button_device_media_play_start).setOnClickListener(v -> {
+ startDeviceAudio();
});
- view.findViewById(R.id.button_display_media_play_once).setOnClickListener(v -> {
- startDisplayAudio();
+ view.findViewById(R.id.button_device_media_play_once).setOnClickListener(v -> {
+ startDeviceAudio();
// play only for 1 sec and stop
- mHandler.postDelayed(() -> mMusicPlayerForSelectedDisplay.stop(), 1000);
+ mHandler.postDelayed(() -> mMusicPlayerForSelectedDeviceAddress.stop(), 1000);
});
- view.findViewById(R.id.button_display_media_play_stop)
- .setOnClickListener(v -> mMusicPlayerForSelectedDisplay.stop());
+ view.findViewById(R.id.button_device_media_play_stop)
+ .setOnClickListener(v -> mMusicPlayerForSelectedDeviceAddress.stop());
view.findViewById(R.id.media_delayed_focus_start)
.setOnClickListener(v -> handleDelayedMediaStart());
@@ -347,61 +339,62 @@
private void initializePlayers() {
mMusicAudioAttrib = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_MEDIA)
- .build();
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
mNavAudioAttrib = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
- .build();
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+ .build();
mPhoneAudioAttrib = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.build();
mVrAudioAttrib = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANT)
- .build();
+ .setUsage(AudioAttributes.USAGE_ASSISTANT)
+ .build();
mRadioAudioAttrib = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_MEDIA)
- .build();
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
mSystemSoundAudioAttrib = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
- // Create a display audio attribute
- mMusicAudioAttribForDisplay = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .build();
+ // Create an audio device address audio attribute
+ mMusicAudioAttribForDeviceAddress = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.build();
- mMusicPlayerForSelectedDisplay = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
- mMusicAudioAttribForDisplay);
+
+ mMusicPlayerForSelectedDeviceAddress = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
+ mMusicAudioAttribForDeviceAddress);
mMusicPlayer = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
- mMusicAudioAttrib);
+ mMusicAudioAttrib);
mMusicPlayerWithDelayedFocus = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
mMusicAudioAttrib);
mMusicPlayerShort = new AudioPlayer(mContext, R.raw.ring_classic_01,
- mMusicAudioAttrib);
+ mMusicAudioAttrib);
mNavGuidancePlayer = new AudioPlayer(mContext, R.raw.turnright,
- mNavAudioAttrib);
+ mNavAudioAttrib);
mPhoneAudioPlayer = new AudioPlayer(mContext, R.raw.free_flight,
mPhoneAudioAttrib);
mVrPlayer = new AudioPlayer(mContext, R.raw.one2six,
- mVrAudioAttrib);
+ mVrAudioAttrib);
mSystemPlayer = new AudioPlayer(mContext, R.raw.ring_classic_01,
- mSystemSoundAudioAttrib);
+ mSystemSoundAudioAttrib);
mWavPlayer = new AudioPlayer(mContext, R.raw.free_flight,
- mMusicAudioAttrib);
+ mMusicAudioAttrib);
final AudioDeviceInfo tuner = findTunerDevice(mContext);
if (tuner != null) {
mHwAudioSource = new HwAudioSource.Builder()
- .setAudioAttributes(mMusicAudioAttrib)
- .setAudioDeviceInfo(findTunerDevice(mContext))
- .build();
+ .setAudioAttributes(mMusicAudioAttrib)
+ .setAudioDeviceInfo(findTunerDevice(mContext))
+ .build();
}
mAllPlayers = new AudioPlayer[] {
- mMusicPlayer,
- mMusicPlayerShort,
- mNavGuidancePlayer,
- mVrPlayer,
- mSystemPlayer,
- mWavPlayer,
- mMusicPlayerWithDelayedFocus
+ mMusicPlayer,
+ mMusicPlayerShort,
+ mNavGuidancePlayer,
+ mVrPlayer,
+ mSystemPlayer,
+ mWavPlayer,
+ mMusicPlayerWithDelayedFocus
};
}
@@ -494,14 +487,14 @@
if (DBG) Log.d(TAG, "Media With Delayed Focus already stopped");
}
- private void setUpDisplayLayoutView(View view) {
- mDisplayLayout = view.findViewById(R.id.audio_display_layout);
+ private void setUpDeviceAddressLayoutView(View view) {
+ mDeviceAddressLayout = view.findViewById(R.id.audio_select_device_address_layout);
- mDisplaySpinner = view.findViewById(R.id.display_spinner);
- mDisplaySpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ mDeviceAddressSpinner = view.findViewById(R.id.device_address_spinner);
+ mDeviceAddressSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
- handleDisplaySelection();
+ handleDeviceAddressSelection();
}
@Override
@@ -642,72 +635,68 @@
mAudioManager.abandonAudioFocus(mRadioFocusListener, mRadioAudioAttrib);
}
- private void setUpDisplayPlayer() {
- DisplayManager displayManager = (DisplayManager) mContext.getSystemService(
- Context.DISPLAY_SERVICE);
- Display[] displays = displayManager.getDisplays();
- List<Integer> displayList = new ArrayList<>();
- for (Display display : displays) {
- DisplayAddress.Physical physical = (DisplayAddress.Physical) display.getAddress();
- if (physical != null) {
- displayList.add((int) physical.getPort());
- Log.d(TAG, "Found Display Port " + physical.getPort());
- } else {
- Log.d(TAG, "Found Display with no physical " + display.getDisplayId());
- }
+ private void setUpDeviceAddressPlayer() {
+ if (!mCarAudioManager.isDynamicRoutingEnabled()) {
+ mDeviceAddressLayout.setVisibility(View.GONE);
+ return;
}
- // If only one display is available add another display for testing
- if (displayList.size() == 1) {
- displayList.add(sDefaultExtraTestScreenPortId);
+ mDeviceAddressLayout.setVisibility(View.VISIBLE);
+ List<CarAudioZoneDeviceInfo> deviceList = new ArrayList<>();
+ for (int audioZoneId: mCarAudioManager.getAudioZoneIds()) {
+ AudioDeviceInfo deviceInfo = mCarAudioManager
+ .getOutputDeviceForUsage(audioZoneId, AudioAttributes.USAGE_MEDIA);
+ CarAudioZoneDeviceInfo carAudioZoneDeviceInfo = new CarAudioZoneDeviceInfo();
+ carAudioZoneDeviceInfo.mDeviceInfo = deviceInfo;
+ carAudioZoneDeviceInfo.mAudioZoneId = audioZoneId;
+ deviceList.add(carAudioZoneDeviceInfo);
+ if (DBG) {
+ Log.d(TAG, "Found device address"
+ + carAudioZoneDeviceInfo.mDeviceInfo.getAddress()
+ + " for audio zone id " + audioZoneId);
+ }
+
}
- //take care of display selection
- Integer[] displayArray = displayList.stream().toArray(Integer[]::new);
- mDisplayAdapter = new ArrayAdapter<>(mContext,
- android.R.layout.simple_spinner_item, displayArray);
- mDisplayAdapter.setDropDownViewResource(
+ CarAudioZoneDeviceInfo[] deviceArray =
+ deviceList.stream().toArray(CarAudioZoneDeviceInfo[]::new);
+ mDeviceAddressAdapter = new ArrayAdapter<>(mContext,
+ android.R.layout.simple_spinner_item, deviceArray);
+ mDeviceAddressAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
- mDisplaySpinner.setAdapter(mDisplayAdapter);
- createDisplayAudioPlayer();
+ mDeviceAddressSpinner.setAdapter(mDeviceAddressAdapter);
+ createDeviceAddressAudioPlayer();
}
- private void createDisplayAudioPlayer() {
- byte selectedDisplayPortId = mDisplayAdapter.getItem(
- mDisplaySpinner.getSelectedItemPosition()).byteValue();
- int zoneIdForDisplayId = mCarAudioManager.getZoneIdForDisplayPortId(selectedDisplayPortId);
- Log.d(TAG, "Setting Bundle to zone " + zoneIdForDisplayId);
+ private void createDeviceAddressAudioPlayer() {
+ CarAudioZoneDeviceInfo carAudioZoneDeviceInfo = mDeviceAddressAdapter.getItem(
+ mDeviceAddressSpinner.getSelectedItemPosition());
+ Log.d(TAG, "Setting Bundle to zone " + carAudioZoneDeviceInfo.mAudioZoneId);
Bundle bundle = new Bundle();
bundle.putInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,
- zoneIdForDisplayId);
- mMusicAudioAttribForDisplay = new AudioAttributes.Builder()
+ carAudioZoneDeviceInfo.mAudioZoneId);
+ mMusicAudioAttribForDeviceAddress = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.addBundle(bundle)
.build();
- AudioDeviceInfo audioDeviceInfo =
- mCarAudioManager.getOutputDeviceForUsage(zoneIdForDisplayId,
- AudioAttributes.USAGE_MEDIA);
-
- mMusicPlayerForSelectedDisplay = new AudioPlayer(mContext,
+ mMusicPlayerForSelectedDeviceAddress = new AudioPlayer(mContext,
R.raw.well_worth_the_wait,
- mMusicAudioAttribForDisplay,
- audioDeviceInfo);
-
- mDisplayLayout.findViewById(R.id.audio_display_layout)
- .setVisibility(View.VISIBLE);
+ mMusicAudioAttribForDeviceAddress,
+ carAudioZoneDeviceInfo.mDeviceInfo);
}
- private void startDisplayAudio() {
- Log.d(TAG, "Starting display audio");
- mMusicPlayerForSelectedDisplay.start(true, false,
+ private void startDeviceAudio() {
+ Log.d(TAG, "Starting device address audio");
+ mMusicPlayerForSelectedDeviceAddress.start(true, false,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
}
- public void handleDisplaySelection() {
- if (mMusicPlayerForSelectedDisplay != null && mMusicPlayerForSelectedDisplay.isPlaying()) {
- mMusicPlayerForSelectedDisplay.stop();
+ public void handleDeviceAddressSelection() {
+ if (mMusicPlayerForSelectedDeviceAddress != null
+ && mMusicPlayerForSelectedDeviceAddress.isPlaying()) {
+ mMusicPlayerForSelectedDeviceAddress.stop();
}
- createDisplayAudioPlayer();
+ createDeviceAddressAudioPlayer();
}
/**
@@ -838,4 +827,19 @@
}
}
}
+
+ private class CarAudioZoneDeviceInfo {
+ AudioDeviceInfo mDeviceInfo;
+ int mAudioZoneId;
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Device Address : ");
+ builder.append(mDeviceInfo.getAddress());
+ builder.append(", Audio Zone Id: ");
+ builder.append(mAudioZoneId);
+ return builder.toString();
+ }
+ }
}
diff --git a/tests/android_car_api_test/Android.mk b/tests/android_car_api_test/Android.mk
index 7ac74aa..44c38c4 100644
--- a/tests/android_car_api_test/Android.mk
+++ b/tests/android_car_api_test/Android.mk
@@ -41,6 +41,7 @@
android.car.cluster.navigation \
android.car.cluster.navigation \
android.car.testapi \
+ android.car.test.utils \
compatibility-device-util-axt \
testng \
truth-prebuilt \
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
index 4106ccb..b3bf492 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
@@ -16,14 +16,22 @@
package android.car.apitest;
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import static com.android.compatibility.common.util.TestUtils.BooleanSupplierWithThrow;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
import android.car.Car;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -39,6 +47,15 @@
protected static final long DEFAULT_WAIT_TIMEOUT_MS = 1_000;
+ /**
+ * Constant used to wait blindly, when there is no condition that can be checked.
+ */
+ private static final int SUSPEND_TIMEOUT_MS = 5_000;
+ /**
+ * How long to sleep (multiple times) while waiting for a condition.
+ */
+ private static final int SMALL_NAP_MS = 100;
+
protected static final Context sContext = InstrumentationRegistry.getInstrumentation()
.getTargetContext();
@@ -89,4 +106,36 @@
assertMainThread();
}
}
+
+ protected static void suspendToRamAndResume() throws Exception {
+ Log.d(TAG, "Emulate suspend to RAM and resume");
+ PowerManager powerManager = sContext.getSystemService(PowerManager.class);
+ runShellCommand("cmd car_service suspend");
+ // Check for suspend success
+ waitUntil("Suspsend is not successful",
+ SUSPEND_TIMEOUT_MS, () -> !powerManager.isScreenOn());
+
+ // Force turn off garage mode
+ runShellCommand("cmd car_service garage-mode off");
+ runShellCommand("cmd car_service resume");
+ }
+
+ protected static boolean waitUntil(String msg, long timeoutMs,
+ BooleanSupplierWithThrow condition) {
+ long deadline = SystemClock.elapsedRealtime() + timeoutMs;
+ do {
+ try {
+ if (condition.getAsBoolean()) {
+ return true;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in waitUntil: " + msg);
+ throw new RuntimeException(e);
+ }
+ SystemClock.sleep(SMALL_NAP_MS);
+ } while (SystemClock.elapsedRealtime() < deadline);
+
+ fail(msg + " after: " + timeoutMs + "ms");
+ return false;
+ }
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
index 4a4681b..8b64620 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
@@ -54,7 +54,7 @@
// Request all application focuses and abandon them to ensure no active context is present
// when test starts.
int[] activeTypes = mManager.getActiveAppTypes();
- FocusOwnershipCallback owner = new FocusOwnershipCallback();
+ FocusOwnershipCallback owner = new FocusOwnershipCallback(/* assertEventThread= */ false);
for (int i = 0; i < activeTypes.length; i++) {
mManager.requestAppFocus(activeTypes[i], owner);
owner.waitForOwnershipGrantAndAssert(DEFAULT_WAIT_TIMEOUT_MS, activeTypes[i]);
@@ -165,9 +165,9 @@
assertThat(manager1.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder();
assertThat(manager2.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder();
assertThat(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
- APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+ APP_FOCUS_TYPE_NAVIGATION, true)).isTrue();
assertThat(change1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
- APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+ APP_FOCUS_TYPE_NAVIGATION, true)).isTrue();
assertThat(manager2.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner2))
.isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
@@ -337,6 +337,15 @@
private final Semaphore mLossEventWait = new Semaphore(0);
private int mLastGrantEvent;
private final Semaphore mGrantEventWait = new Semaphore(0);
+ private final boolean mAssertEventThread;
+
+ private FocusOwnershipCallback(boolean assertEventThread) {
+ mAssertEventThread = assertEventThread;
+ }
+
+ private FocusOwnershipCallback() {
+ this(true);
+ }
boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedAppType)
throws Exception {
@@ -359,7 +368,9 @@
@Override
public void onAppFocusOwnershipLost(int appType) {
Log.i(TAG, "onAppFocusOwnershipLost " + appType);
- assertEventThread();
+ if (mAssertEventThread) {
+ assertEventThread();
+ }
mLastLossEvent = appType;
mLossEventWait.release();
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
index 08ccdd6..421dd44 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
@@ -15,6 +15,9 @@
*/
package android.car.apitest;
+import static android.car.test.util.UserTestingHelper.clearUserLockCredentials;
+import static android.car.test.util.UserTestingHelper.setMaxSupportedUsers;
+import static android.car.test.util.UserTestingHelper.setUserLockCredentials;
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;
@@ -42,6 +45,8 @@
import android.os.UserManager;
import android.util.Log;
+import androidx.test.filters.FlakyTest;
+
import com.android.internal.infra.AndroidFuture;
import org.junit.AfterClass;
@@ -49,6 +54,7 @@
import org.junit.BeforeClass;
import org.junit.Test;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -58,6 +64,8 @@
private static final String TAG = CarUserManagerTest.class.getSimpleName();
+ private static final int PIN = 2345;
+
private static final int SWITCH_TIMEOUT_MS = 70_000;
private static final int STOP_TIMEOUT_MS = 300_000;
@@ -69,8 +77,11 @@
private static final UserManager sUserManager = UserManager.get(sContext);
+ private static final int sMaxNumberUsersBefore = UserManager.getMaxSupportedUsers();
+
+ private static final List<Integer> sCreatedUsers = new ArrayList<>();
+
private static int sInitialUserId = UserHandle.USER_NULL;
- private static int sNewUserId = UserHandle.USER_NULL;
private CarUserManager mCarUserManager;
@@ -78,25 +89,20 @@
public static void setupUsers() {
sInitialUserId = ActivityManager.getCurrentUser();
Log.i(TAG, "Running test as user " + sInitialUserId);
-
- sNewUserId = createNewUser("Main", /* isGuest= */ false).id;
+ setMaxSupportedUsers(8); // Total 6 users will be created for all tests
}
@AfterClass
public static void cleanupUsers() {
+ setMaxSupportedUsers(sMaxNumberUsersBefore);
switchUserDirectly(sInitialUserId);
- if (sNewUserId == UserHandle.USER_NULL) {
- Log.w(TAG, "No need to remove user" + sNewUserId);
- return;
- }
-
- Log.i(TAG, "Switching back to " + sInitialUserId);
- switchUserDirectly(sInitialUserId);
-
- Log.i(TAG, "Removing user" + sNewUserId);
- if (!sUserManager.removeUser(sNewUserId)) {
- Log.wtf(TAG, "Failed to remove user " + sNewUserId);
+ for (int i = 0; i < sCreatedUsers.size(); i++) {
+ int id = sCreatedUsers.get(i);
+ Log.d(TAG, "removeCreatedUsers: " + id);
+ if (!sUserManager.removeUser(id)) {
+ Log.wtf(TAG, "Failed to remove user " + id);
+ }
}
}
@@ -107,8 +113,8 @@
@Test
public void testLifecycleListener() throws Exception {
- int oldUserId = sInitialUserId;
- int newUserId = sNewUserId;
+ int oldUserId = ActivityManager.getCurrentUser();
+ int newUserId = createNewUser("Test", /* isGuest= */ false).id;
BlockingUserLifecycleListener startListener = BlockingUserLifecycleListener
.forSpecificEvents()
@@ -132,14 +138,16 @@
// Switch while listener is registered
switchUser(newUserId);
- List<UserLifecycleEvent> startEvents = startListener.waitForEvents();
+ List<UserLifecycleEvent> startEvents = startListener.waitForEvents();
Log.d(TAG, "Received start events: " + startEvents);
// Make sure listener callback was executed in the proper threaqd
assertWithMessage("not executed on executor").that(executedRef.get()).isTrue();
// Assert user ids
- for (UserLifecycleEvent event : startEvents) {
+ List<UserLifecycleEvent> expectedEvents = startListener.waitForEvents();
+ Log.d(TAG, "Received expected events: " + expectedEvents);
+ for (UserLifecycleEvent event : expectedEvents) {
assertWithMessage("wrong userId on %s", event)
.that(event.getUserId()).isEqualTo(newUserId);
assertWithMessage("wrong userHandle on %s", event)
@@ -166,8 +174,8 @@
Log.d(TAG, "registering stop listener: " + stopListener);
mCarUserManager.addListener(mExecutor, stopListener);
- // Switch back to the previous user
- switchUser(oldUserId);
+ // Switch back to the initial user
+ switchUser(sInitialUserId);
if (TEST_STOP) {
// Must force stop the user, otherwise it can take minutes for its process to finish
@@ -188,8 +196,7 @@
Log.w(TAG, "NOT testing user stop events");
}
- // Make sure unregistered listener din't receive any more events
-
+ // Make sure unregistered listener didn't receive any more events
List<UserLifecycleEvent> allStartEvents = startListener.getAllReceivedEvents();
Log.d(TAG, "All start events: " + startEvents);
assertThat(allStartEvents).containsAllIn(startEvents).inOrder();
@@ -198,13 +205,129 @@
mCarUserManager.removeListener(stopListener);
}
+ /**
+ * Tests resume behavior when current user is ephemeral guest, a new guest user should be
+ * created and switched to.
+ */
+ @Test
+ @FlakyTest // TODO(b/158050171) remove once process is stable on user switching.
+ public void testGuestUserResumeToNewGuestUser() throws Exception {
+ // Create new guest user
+ UserInfo guestUser = createNewUser("Guest", /* isGuestUser= */ true);
+ int guestUserId = guestUser.id;
+
+ // Wait for this user to be active
+ BlockingUserLifecycleListener listener1 = BlockingUserLifecycleListener
+ .forSpecificEvents()
+ .forUser(guestUserId)
+ .setTimeout(SWITCH_TIMEOUT_MS)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .build();
+ mCarUserManager.addListener(Runnable::run, listener1);
+ switchUser(guestUserId);
+ listener1.waitForEvents();
+ mCarUserManager.removeListener(listener1);
+
+ BlockingUserLifecycleListener listener2 = BlockingUserLifecycleListener
+ .forSpecificEvents()
+ .forPreviousUser(guestUserId)
+ .setTimeout(SWITCH_TIMEOUT_MS)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
+ .build();
+ // Make sure listener callback was executed in the proper threaqd
+ mCarUserManager.addListener(Runnable::run, listener2);
+
+ // Emulate suspend to RAM
+ suspendToRamAndResume();
+ UserLifecycleEvent event = listener2.waitForEvents().get(0);
+
+ int newGuestId = event.getUserId();
+ sCreatedUsers.add(newGuestId);
+
+ assertWithMessage("wrong user on event %s", event).that(newGuestId)
+ .isNotEqualTo(guestUserId);
+ assertWithMessage("wrong current user").that(newGuestId)
+ .isEqualTo(ActivityManager.getCurrentUser());
+ UserInfo newGuest = sUserManager.getUserInfo(newGuestId);
+ assertWithMessage("new user (%s) is not a guest", newGuest.toFullString())
+ .that(newGuest.isGuest()).isTrue();
+ assertWithMessage("wrong name on new guest(%s)", newGuest.toFullString())
+ .that(newGuest.name).isNotEqualTo(guestUser.name);
+ mCarUserManager.removeListener(listener2);
+ }
+
+ /**
+ * Tests resume behavior when current user is guest guest but with secured lock screen,
+ * resume to same guest user.
+ */
+ @Test
+ @FlakyTest // TODO(b/158050171) remove once process is stable on user switching.
+ public void testSecuredGuestUserResumeToSameUser() throws Exception {
+ // Create new guest user
+ UserInfo guestUser = createNewUser("Guest", /* isGuestUser= */ true);
+ int guestUserId = guestUser.id;
+
+ // Wait for this user to be active
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener
+ .forSpecificEvents()
+ .forUser(guestUserId)
+ .setTimeout(SWITCH_TIMEOUT_MS)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .build();
+ mCarUserManager.addListener(Runnable::run, listener);
+
+ switchUser(guestUserId);
+
+ listener.waitForEvents();
+ mCarUserManager.removeListener(listener);
+
+ setUserLockCredentials(guestUserId, PIN);
+ try {
+ // Emulate suspend to RAM
+ suspendToRamAndResume();
+
+ assertWithMessage("not resumed to previous user: %s", guestUser)
+ .that(ActivityManager.getCurrentUser()).isEqualTo(guestUserId);
+ } finally {
+ clearUserLockCredentials(guestUserId, PIN);
+ }
+
+ }
+
+ /**
+ * Tests resume behavior when current user is persistent user.
+ */
+ @Test
+ @FlakyTest // TODO(b/158050171) remove once process is stable on user switching.
+ public void testPersistentUserResumeToUser() throws Exception {
+ int newUserId = createNewUser("Test", /* isGuest= */ false).id;
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener
+ .forSpecificEvents()
+ .forUser(newUserId)
+ .setTimeout(SWITCH_TIMEOUT_MS)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .build();
+ mCarUserManager.addListener(Runnable::run, listener);
+ switchUser(newUserId);
+ listener.waitForEvents();
+
+ // Emulate suspend to RAM
+ suspendToRamAndResume();
+
+ listener.waitForEvents();
+ assertWithMessage("not resumed to previous user: %s", newUserId)
+ .that(ActivityManager.getCurrentUser()).isEqualTo(newUserId);
+
+ mCarUserManager.removeListener(listener);
+ }
+
@NonNull
private static UserInfo createNewUser(String name, boolean isGuest) {
name = "CarUserManagerTest." + name;
Log.i(TAG, "Creating new user " + name);
UserInfo newUser = isGuest ? sUserManager.createGuest(sContext, name)
: sUserManager.createUser(name, /* flags= */ 0);
-
+ sCreatedUsers.add(newUser.id);
Log.i(TAG, "Created new user: " + newUser.toFullString());
return newUser;
}
diff --git a/tests/carservice_test/res/raw/car_audio_configuration.xml b/tests/carservice_test/res/raw/car_audio_configuration.xml
index b4bb08a..e1d75df 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration.xml
@@ -21,10 +21,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V1.xml b/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
index 4aaaa6c..26b1c08 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
@@ -33,10 +33,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml b/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml
index 03e9a0f..5c39ceb 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml
@@ -33,10 +33,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
index 4b5abc5..8561968 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="1" occupantZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
index 1d15b36..8c1d0e8 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2" occupantZoneId="1">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
index 288e8fb..dba13a2 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
@@ -17,10 +17,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="1"/>
- </displays>
</zone>
</zones>
</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
index 182e606..972da37 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
index acdb784..060934b 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
index b7d7a62..cffad51 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
index 09168a6..8e1c41b 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
index af57c24..f00b169 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
index f6995bc..9adef24 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
@@ -21,9 +21,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="one"/>
- </displays>
</zone>
</zones>
</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
index f822469..4364a1d 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
@@ -33,10 +33,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="0">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml b/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
index 83c054c..a359d36 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
index 6f845e7..2d25716 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
@@ -37,10 +37,6 @@
</device>
</group>
</volumeGroups>
- <displays>
- <display port="1"/>
- <display port="2"/>
- </displays>
</zone>
<zone name="rear seat zone" audioZoneId="2">
<volumeGroups>
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
index a37c0e4..fce0a78 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
@@ -28,7 +28,6 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.util.SparseIntArray;
-import android.view.DisplayAddress;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -293,56 +292,6 @@
}
@Test
- public void loadAudioZones_parsesPhysicalDisplayAddresses() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
- mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
- CarAudioZone[] zones = cazh.loadAudioZones();
-
- CarAudioZone primaryZone = zones[0];
- List<DisplayAddress.Physical> primaryPhysicals = primaryZone.getPhysicalDisplayAddresses();
- assertThat(primaryPhysicals).hasSize(2);
- assertThat(primaryPhysicals.get(0).getPort()).isEqualTo(1);
- assertThat(primaryPhysicals.get(1).getPort()).isEqualTo(2);
- }
-
- @Test
- public void loadAudioZones_defaultsDisplayAddressesToEmptyList() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
- mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
- CarAudioZone[] zones = cazh.loadAudioZones();
-
- CarAudioZone rseZone = zones[1];
- List<DisplayAddress.Physical> rsePhysicals = rseZone.getPhysicalDisplayAddresses();
- assertThat(rsePhysicals).isEmpty();
- }
-
- @Test(expected = RuntimeException.class)
- public void loadAudioZones_throwsOnDuplicatePorts() throws Exception {
- try (InputStream duplicatePortStream = mContext.getResources().openRawResource(
- R.raw.car_audio_configuration_duplicate_ports)) {
- CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mCarAudioSettings, duplicatePortStream,
- mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
- cazh.loadAudioZones();
- }
- }
-
- @Test
- public void loadAudioZones_throwsOnNonNumericalPort() {
- InputStream duplicatePortStream = mContext.getResources().openRawResource(
- R.raw.car_audio_configuration_non_numerical_port);
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, duplicatePortStream,
- mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
- IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
- cazh::loadAudioZones);
- assertThat(exception).hasMessageThat().contains("Port one is not a number");
- }
-
- @Test
public void loadAudioZones_passesOnMissingAudioZoneIdForPrimary() throws Exception {
try (InputStream missingAudioZoneIdStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_no_audio_zone_id_for_primary_zone)) {
diff --git a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
index fb65ff0..28d70e6 100644
--- a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
@@ -17,7 +17,7 @@
package com.android.car.watchdog;
import static android.car.test.mocks.AndroidMockitoHelper.mockQueryService;
-import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUsers;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAllUsers;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmIsUserRunning;
import static android.car.test.util.UserTestingHelper.UserInfoBuilder;
import static android.car.watchdog.CarWatchdogManager.TIMEOUT_CRITICAL;
@@ -100,7 +100,7 @@
when(mCar.getEventHandler()).thenReturn(mMainHandler);
when(mServiceBinder.queryLocalInterface(anyString())).thenReturn(mCarWatchdogService);
when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
- mockUmGetUsers(mUserManager, mUserInfos);
+ mockUmGetAllUsers(mUserManager, mUserInfos);
mockUmIsUserRunning(mUserManager, 10, true);
mockUmIsUserRunning(mUserManager, 11, false);
diff --git a/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java b/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
index 5fe5670..62cf6d4 100644
--- a/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
@@ -16,6 +16,8 @@
package android.car.userlib;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetSystemUser;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserInfo;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUsers;
import static android.car.test.util.UserTestingHelper.newUser;
import static android.os.UserHandle.USER_SYSTEM;
@@ -31,6 +33,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.car.settings.CarSettings;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.content.ContentResolver;
import android.content.Context;
@@ -38,7 +41,6 @@
import android.content.res.Resources;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.sysprop.CarProperties;
import androidx.test.InstrumentationRegistry;
@@ -192,12 +194,31 @@
}
@Test
- public void testGetInitialUser_WithNonExistLastActiveUser_ReturnsSmallestUserId() {
- setLastActiveUser(12);
- mockGetUsers(USER_SYSTEM, 10, 10 + 1);
+ public void testGetInitialUser_WithNonExistLastActiveUser_ReturnsLastPersistentUser() {
+ setLastActiveUser(120);
+ setLastPersistentActiveUser(110);
+ mockGetUsers(USER_SYSTEM, 100, 110);
assertThat(mCarUserManagerHelper.getInitialUser(/* usesOverrideUserIdProperty= */ true))
- .isEqualTo(10);
+ .isEqualTo(110);
+ // should have reset last active user
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+ .isEqualTo(UserHandle.USER_NULL);
+ }
+
+ @Test
+ public void testGetInitialUser_WithNonExistLastActiveAndPersistentUsers_ReturnsSmallestUser() {
+ setLastActiveUser(120);
+ setLastPersistentActiveUser(120);
+ mockGetUsers(USER_SYSTEM, 100, 110);
+
+ assertThat(mCarUserManagerHelper.getInitialUser(/* usesOverrideUserIdProperty= */ true))
+ .isEqualTo(100);
+ // should have reset both settions
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+ .isEqualTo(UserHandle.USER_NULL);
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID))
+ .isEqualTo(UserHandle.USER_NULL);
}
@Test
@@ -289,6 +310,65 @@
assertThat(mCarUserManagerHelper.hasInitialUser()).isFalse();
}
+ @Test
+ public void testSetLastActiveUser_headlessSystem() {
+ mockIsHeadlessSystemUserMode(true);
+ mockUmGetSystemUser(mUserManager);
+
+ mCarUserManagerHelper.setLastActiveUser(UserHandle.USER_SYSTEM);
+
+ assertSettingsNotSet(CarSettings.Global.LAST_ACTIVE_USER_ID);
+ assertSettingsNotSet(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+ }
+
+ @Test
+ public void testSetLastActiveUser_nonHeadlessSystem() {
+ mockIsHeadlessSystemUserMode(false);
+ mockUmGetSystemUser(mUserManager);
+
+ mCarUserManagerHelper.setLastActiveUser(UserHandle.USER_SYSTEM);
+
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+ .isEqualTo(UserHandle.USER_SYSTEM);
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID))
+ .isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void testSetLastActiveUser_nonExistingUser() {
+ // Don't need to mock um.getUser(), it will return null by default
+ mCarUserManagerHelper.setLastActiveUser(42);
+
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID)).isEqualTo(42);
+ assertSettingsNotSet(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+ }
+
+ @Test
+ public void testSetLastActiveUser_ephemeralUser() {
+ int persistentUserId = 42;
+ int ephemeralUserid = 108;
+ mockUmGetUserInfo(mUserManager, persistentUserId, NO_FLAGS);
+ mockUmGetUserInfo(mUserManager, ephemeralUserid, UserInfo.FLAG_EPHEMERAL);
+
+ mCarUserManagerHelper.setLastActiveUser(persistentUserId);
+ mCarUserManagerHelper.setLastActiveUser(ephemeralUserid);
+
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+ .isEqualTo(ephemeralUserid);
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID))
+ .isEqualTo(persistentUserId);
+ }
+
+ @Test
+ public void testSetLastActiveUser_nonEphemeralUser() {
+ mockUmGetUserInfo(mUserManager, 42, NO_FLAGS);
+
+ mCarUserManagerHelper.setLastActiveUser(42);
+
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID)).isEqualTo(42);
+ assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID)).isEqualTo(42);
+ }
+
private void mockGetUsers(@NonNull @UserIdInt int... userIds) {
mockUmGetUsers(mUserManager, userIds);
}
@@ -298,7 +378,11 @@
}
private void setLastActiveUser(@UserIdInt int userId) {
- putSettingsInt(Settings.Global.LAST_ACTIVE_USER_ID, userId);
+ putSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID, userId);
+ }
+
+ private void setLastPersistentActiveUser(@UserIdInt int userId) {
+ putSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID, userId);
}
private void setDefaultBootUserOverride(@UserIdInt int userId) {
diff --git a/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java b/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
index 6397c3c..29a1946 100644
--- a/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
@@ -17,6 +17,8 @@
package android.car.userlib;
import static android.car.userlib.UserHalHelper.CREATE_USER_PROPERTY;
+import static android.car.userlib.UserHalHelper.REMOVE_USER_PROPERTY;
+import static android.car.userlib.UserHalHelper.SWITCH_USER_PROPERTY;
import static android.car.userlib.UserHalHelper.USER_IDENTIFICATION_ASSOCIATION_PROPERTY;
import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.ASSOCIATE_CURRENT_USER;
import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.DISASSOCIATE_ALL_USERS;
@@ -49,6 +51,9 @@
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
@@ -861,6 +866,62 @@
}
@Test
+ public void testRemoveUserRequestToVehiclePropValue_null() {
+ assertThrows(NullPointerException.class,
+ () -> UserHalHelper.toVehiclePropValue((RemoveUserRequest) null));
+ }
+
+ @Test
+ public void testRemoveUserRequestToVehiclePropValue_emptyRequest() {
+ RemoveUserRequest request = new RemoveUserRequest();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
+ public void testRemoveUserRequestToVehiclePropValue_missingRequestId() {
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.removedUserInfo.userId = 11;
+ request.usersInfo.existingUsers.add(request.removedUserInfo);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
+ public void testRemoveUserRequestToVehiclePropValue_ok() {
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.requestId = 42;
+
+ android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ user10.userId = 10;
+ user10.flags = UserFlags.ADMIN;
+
+ // existing users
+ request.usersInfo.numberUsers = 1;
+ request.usersInfo.existingUsers.add(user10);
+
+ // current user
+ request.usersInfo.currentUser = user10;
+ // user to remove
+ request.removedUserInfo = user10;
+
+ VehiclePropValue propValue = UserHalHelper.toVehiclePropValue(request);
+
+ assertWithMessage("wrong prop on %s", propValue).that(propValue.prop)
+ .isEqualTo(REMOVE_USER_PROPERTY);
+ assertWithMessage("wrong int32values on %s", propValue).that(propValue.value.int32Values)
+ .containsExactly(42, // request id
+ 10, UserFlags.ADMIN, // user to remove
+ 10, UserFlags.ADMIN, // current user
+ 1, // number of users
+ 10, UserFlags.ADMIN // existing user 1
+ ).inOrder();
+ }
+
+ @Test
public void testCreateUserRequestToVehiclePropValue_null() {
assertThrows(NullPointerException.class,
() -> UserHalHelper.toVehiclePropValue((CreateUserRequest) null));
@@ -885,6 +946,24 @@
}
@Test
+ public void testCreateUserRequestToVehiclePropValue_nullNewUserName() {
+ CreateUserRequest request = new CreateUserRequest();
+ request.requestId = 42;
+
+ request.newUserInfo.userId = 10;
+ request.newUserInfo.flags = UserFlags.ADMIN;
+ request.newUserName = null;
+
+ request.usersInfo.numberUsers = 1;
+ request.usersInfo.currentUser.userId = request.newUserInfo.userId;
+ request.usersInfo.currentUser.flags = request.newUserInfo.flags;
+ request.usersInfo.existingUsers.add(request.usersInfo.currentUser);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
public void testCreateUserRequestToVehiclePropValue_usersInfoDoesNotContainNewUser() {
CreateUserRequest request = new CreateUserRequest();
request.requestId = 42;
@@ -966,13 +1045,91 @@
}
@Test
+ public void testSwitchUserRequestToVehiclePropValue_null() {
+ assertThrows(NullPointerException.class,
+ () -> UserHalHelper.toVehiclePropValue((SwitchUserRequest) null));
+ }
+
+ @Test
+ public void testSwitchUserRequestToVehiclePropValue_emptyRequest() {
+ SwitchUserRequest request = new SwitchUserRequest();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
+ public void testSwitchUserRequestToVehiclePropValue_missingMessageType() {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.requestId = 42;
+ android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ user10.userId = 10;
+ request.usersInfo.numberUsers = 1;
+ request.usersInfo.existingUsers.add(user10);
+ request.usersInfo.currentUser = user10;
+ request.targetUser = user10;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
+ public void testSwitchUserRequestToVehiclePropValue_incorrectMessageType() {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.requestId = 42;
+ request.messageType = -1;
+ android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ user10.userId = 10;
+ request.usersInfo.numberUsers = 1;
+ request.usersInfo.existingUsers.add(user10);
+ request.usersInfo.currentUser = user10;
+ request.targetUser = user10;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> UserHalHelper.toVehiclePropValue(request));
+ }
+
+ @Test
+ public void tesSwitchUserRequestToVehiclePropValue_ok() {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.requestId = 42;
+ android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ user10.userId = 10;
+ user10.flags = UserFlags.ADMIN;
+ // existing users
+ request.usersInfo.numberUsers = 1;
+ request.usersInfo.existingUsers.add(user10);
+ // current user
+ request.usersInfo.currentUser = user10;
+ // user to remove
+ request.targetUser = user10;
+ request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+
+ VehiclePropValue propValue = UserHalHelper.toVehiclePropValue(request);
+
+ assertWithMessage("wrong prop on %s", propValue).that(propValue.prop)
+ .isEqualTo(SWITCH_USER_PROPERTY);
+ assertWithMessage("wrong int32values on %s", propValue).that(propValue.value.int32Values)
+ .containsExactly(42, // request id
+ SwitchUserMessageType.ANDROID_SWITCH, // message type
+ 10, UserFlags.ADMIN, // target user
+ 10, UserFlags.ADMIN, // current user
+ 1, // number of users
+ 10, UserFlags.ADMIN // existing user 1
+ ).inOrder();
+ }
+
+ @Test
public void testNewUsersInfo_nullUm() {
- assertThrows(IllegalArgumentException.class, () -> UserHalHelper.newUsersInfo(null));
+ assertThrows(IllegalArgumentException.class, () -> UserHalHelper.newUsersInfo(null, 100));
}
@Test
public void testNewUsersInfo_nullUsers() {
- UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUm);
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUm, 100);
assertEmptyUsersInfo(usersInfo);
}
@@ -982,12 +1139,58 @@
List<UserInfo> users = new ArrayList<>();
AndroidMockitoHelper.mockUmGetUsers(mUm, users);
- UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUm);
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUm, 100);
assertEmptyUsersInfo(usersInfo);
}
@Test
+ public void testNewUsersInfo_ok() {
+ UserInfo user100 = new UserInfoBuilder(100).setFlags(UserInfo.FLAG_ADMIN).build();
+ UserInfo user200 = new UserInfoBuilder(200).build();
+
+ AndroidMockitoHelper.mockUmGetUsers(mUm, user100, user200);
+ AndroidMockitoHelper.mockAmGetCurrentUser(300); // just to make sure it's not used
+
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUm, 100);
+
+ assertThat(usersInfo).isNotNull();
+ assertThat(usersInfo.currentUser.userId).isEqualTo(100);
+ assertThat(usersInfo.currentUser.flags).isEqualTo(UserFlags.ADMIN);
+
+ assertThat(usersInfo.numberUsers).isEqualTo(2);
+ assertThat(usersInfo.existingUsers).hasSize(2);
+
+ assertThat(usersInfo.existingUsers.get(0).userId).isEqualTo(100);
+ assertThat(usersInfo.existingUsers.get(0).flags).isEqualTo(UserFlags.ADMIN);
+ assertThat(usersInfo.existingUsers.get(1).userId).isEqualTo(200);
+ assertThat(usersInfo.existingUsers.get(1).flags).isEqualTo(UserFlags.NONE);
+ }
+
+ @Test
+ public void testNewUsersInfo_currentUser_ok() {
+ UserInfo user100 = new UserInfoBuilder(100).setFlags(UserInfo.FLAG_ADMIN).build();
+ UserInfo user200 = new UserInfoBuilder(200).build();
+
+ AndroidMockitoHelper.mockUmGetUsers(mUm, user100, user200);
+ AndroidMockitoHelper.mockAmGetCurrentUser(100);
+
+ UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUm);
+
+ assertThat(usersInfo).isNotNull();
+ assertThat(usersInfo.currentUser.userId).isEqualTo(100);
+ assertThat(usersInfo.currentUser.flags).isEqualTo(UserFlags.ADMIN);
+
+ assertThat(usersInfo.numberUsers).isEqualTo(2);
+ assertThat(usersInfo.existingUsers).hasSize(2);
+
+ assertThat(usersInfo.existingUsers.get(0).userId).isEqualTo(100);
+ assertThat(usersInfo.existingUsers.get(0).flags).isEqualTo(UserFlags.ADMIN);
+ assertThat(usersInfo.existingUsers.get(1).userId).isEqualTo(200);
+ assertThat(usersInfo.existingUsers.get(1).flags).isEqualTo(UserFlags.NONE);
+ }
+
+ @Test
public void testNewUsersInfo_noCurrentUser() {
UserInfo user100 = new UserInfoBuilder(100).setFlags(UserInfo.FLAG_ADMIN).build();
UserInfo user200 = new UserInfoBuilder(200).build();
@@ -1011,29 +1214,6 @@
}
@Test
- public void testNewUsersInfo_ok() {
- UserInfo user100 = new UserInfoBuilder(100).setFlags(UserInfo.FLAG_ADMIN).build();
- UserInfo user200 = new UserInfoBuilder(200).build();
-
- AndroidMockitoHelper.mockUmGetUsers(mUm, user100, user200);
- AndroidMockitoHelper.mockAmGetCurrentUser(100);
-
- UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUm);
-
- assertThat(usersInfo).isNotNull();
- assertThat(usersInfo.currentUser.userId).isEqualTo(100);
- assertThat(usersInfo.currentUser.flags).isEqualTo(UserFlags.ADMIN);
-
- assertThat(usersInfo.numberUsers).isEqualTo(2);
- assertThat(usersInfo.existingUsers).hasSize(2);
-
- assertThat(usersInfo.existingUsers.get(0).userId).isEqualTo(100);
- assertThat(usersInfo.existingUsers.get(0).flags).isEqualTo(UserFlags.ADMIN);
- assertThat(usersInfo.existingUsers.get(1).userId).isEqualTo(200);
- assertThat(usersInfo.existingUsers.get(1).flags).isEqualTo(UserFlags.NONE);
- }
-
- @Test
public void testCheckValidUsersInfo_null() {
assertThrows(IllegalArgumentException.class, () -> UserHalHelper.checkValid(null));
}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
index 9f360e1..cabab88 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
@@ -30,16 +30,25 @@
import static org.mockito.Mockito.ignoreStubs;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.UserIdInt;
+import android.app.IActivityManager;
import android.car.CarProjectionManager;
import android.car.input.CarInputHandlingService.InputFilter;
import android.car.input.ICarInputListener;
+import android.car.testapi.BlockingUserLifecycleListener;
+import android.car.user.CarUserManager;
+import android.car.userlib.CarUserManagerHelper;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -48,17 +57,25 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
import android.service.voice.VoiceInteractionSession;
import android.telecom.TelecomManager;
+import android.test.mock.MockContentResolver;
import android.view.KeyEvent;
import androidx.test.core.app.ApplicationProvider;
import com.android.car.hal.InputHalService;
+import com.android.car.hal.UserHalService;
+import com.android.car.user.CarUserService;
import com.android.internal.app.AssistUtils;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
import com.google.common.collect.Range;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,6 +90,9 @@
@RunWith(MockitoJUnitRunner.class)
public class CarInputServiceTest {
+ // TODO(b/152069895): decrease value once refactored. In fact, it should not even use
+ // runWithScissors(), but only rely on CountdownLatches
+ private static final long DEFAULT_TIMEOUT_MS = 5_000;
@Mock InputHalService mInputHalService;
@Mock TelecomManager mTelecomManager;
@@ -84,12 +104,54 @@
@Spy Context mContext = ApplicationProvider.getApplicationContext();
@Spy Handler mHandler = new Handler(Looper.getMainLooper());
+ private MockContext mMockContext;
+ private CarUserService mCarUserService;
private CarInputService mCarInputService;
+ /**
+ * A mock {@link Context}.
+ * This class uses a mock {@link ContentResolver} and {@link android.content.ContentProvider} to
+ * avoid changing real system settings. Besides, to emulate the case where the OEM changes
+ * {@link R.string.rotaryService} to empty in the resource file (e.g., the OEM doesn't want to
+ * start RotaryService), this class allows to return a given String when retrieving {@link
+ * R.string.rotaryService}.
+ */
+ private static class MockContext extends BroadcastInterceptingContext {
+ private final MockContentResolver mContentResolver;
+ private final FakeSettingsProvider mContentProvider;
+ private final Resources mResources;
+
+ MockContext(Context base, String rotaryService) {
+ super(base);
+ FakeSettingsProvider.clearSettingsProvider();
+ mContentResolver = new MockContentResolver(this);
+ mContentProvider = new FakeSettingsProvider();
+ mContentResolver.addProvider(Settings.AUTHORITY, mContentProvider);
+
+ mResources = spy(base.getResources());
+ doReturn(rotaryService).when(mResources).getString(R.string.rotaryService);
+ }
+
+ void release() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
+
+ @Override
+ public Resources getResources() {
+ return mResources;
+ }
+ }
+
@Before
public void setUp() {
- mCarInputService = new CarInputService(mContext, mInputHalService, mHandler,
- mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
+ mCarUserService = mock(CarUserService.class);
+ mCarInputService = new CarInputService(mContext, mInputHalService, mCarUserService,
+ mHandler, mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
/* customInputServiceComponent= */ null, mLongPressDelaySupplier);
when(mInputHalService.isKeyInputSupported()).thenReturn(true);
@@ -100,6 +162,69 @@
}
@Test
+ public void rotaryServiceSettingsUpdated_whenRotaryServiceIsNotEmpty() throws Exception {
+ final String rotaryService = "com.android.car.rotary/com.android.car.rotary.RotaryService";
+ init(rotaryService);
+ assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
+
+ final int userId = 11;
+
+ // By default RotaryService is not enabled.
+ String enabledServices = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId);
+ assertThat(enabledServices == null ? "" : enabledServices).doesNotContain(rotaryService);
+
+ String enabled = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ userId);
+ assertThat(enabled).isNull();
+
+ // Enable RotaryService by sending user switch event.
+ sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, userId);
+
+ enabledServices = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId);
+ assertThat(enabledServices).contains(rotaryService);
+
+ enabled = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ userId);
+ assertThat(enabled).isEqualTo("1");
+ }
+
+ @Test
+ public void rotaryServiceSettingsNotUpdated_whenRotaryServiceIsEmpty() throws Exception {
+ final String rotaryService = "";
+ init(rotaryService);
+ assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
+
+ final int userId = 11;
+
+ // By default the Accessibility is disabled.
+ String enabled = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ userId);
+ assertThat(enabled).isNull();
+
+ sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, userId);
+
+ // Sending user switch event shouldn't enable the Accessibility because RotaryService is
+ // empty.
+ enabled = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ userId);
+ assertThat(enabled).isNull();
+ }
+
+ @Test
public void ordinaryEvents_onMainDisplay_routedToInputManager() {
KeyEvent event = send(Key.DOWN, KeyEvent.KEYCODE_ENTER, Display.MAIN);
@@ -458,6 +583,54 @@
assertThat(timeCaptor.getValue()).isIn(Range.closed(then + systemDelay, now + systemDelay));
}
+ @After
+ public void tearDown() {
+ if (mMockContext != null) {
+ mMockContext.release();
+ mMockContext = null;
+ }
+ }
+
+ /**
+ * Initializes {@link #mMockContext}, {@link #mCarUserService}, and {@link #mCarInputService}.
+ */
+ private void init(String rotaryService) {
+ mMockContext = new MockContext(mContext, rotaryService);
+
+ UserManager userManager = mock(UserManager.class);
+ UserInfo userInfo = mock(UserInfo.class);
+ doReturn(userInfo).when(userManager).getUserInfo(anyInt());
+ UserHalService userHal = mock(UserHalService.class);
+ CarUserManagerHelper carUserManagerHelper = mock(CarUserManagerHelper.class);
+ IActivityManager iActivityManager = mock(IActivityManager.class);
+ mCarUserService = new CarUserService(mMockContext, userHal, carUserManagerHelper,
+ userManager, iActivityManager, /* maxRunningUsers= */ 2);
+
+ mCarInputService = new CarInputService(mMockContext, mInputHalService, mCarUserService,
+ mHandler, mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
+ /* customInputServiceComponent= */ null, mLongPressDelaySupplier);
+ mCarInputService.init();
+ }
+
+ private void sendUserLifecycleEvent(@CarUserManager.UserLifecycleEventType int eventType,
+ @UserIdInt int userId) throws InterruptedException {
+ // Add a blocking listener to ensure CarUserService event notification is completed
+ // before proceeding with test execution.
+ BlockingUserLifecycleListener blockingListener =
+ BlockingUserLifecycleListener.forAnyEvent().build();
+ mCarUserService.addUserLifecycleListener(blockingListener);
+
+ runOnMainThreadAndWaitForIdle(() -> mCarUserService.onUserLifecycleEvent(eventType,
+ /* timestampMs= */ 0, /* fromUserId= */ UserHandle.USER_NULL, userId));
+ blockingListener.waitForAnyEvent();
+ }
+
+ private static void runOnMainThreadAndWaitForIdle(Runnable r) {
+ Handler.getMain().runWithScissors(r, DEFAULT_TIMEOUT_MS);
+ // Run empty runnable to make sure that all posted handlers are done.
+ Handler.getMain().runWithScissors(() -> { }, DEFAULT_TIMEOUT_MS);
+ }
+
private enum Key {DOWN, UP}
private enum Display {MAIN, INSTRUMENT_CLUSTER}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
index afd6f57..3cbfffc 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
@@ -62,6 +62,7 @@
import com.android.car.systeminterface.WakeLockInterface;
import com.android.car.test.utils.TemporaryDirectory;
import com.android.car.user.CarUserService;
+import com.android.internal.app.IVoiceInteractionManagerService;
import org.junit.After;
import org.junit.Before;
@@ -107,6 +108,9 @@
private CarUserService mUserService;
@Mock
private InitialUserSetter mInitialUserSetter;
+ @Mock
+ private IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
@Override
protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
@@ -150,7 +154,8 @@
+ ", maxGarageModeRunningDurationInSecs="
+ mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
- mSystemInterface, mUserManager, mUserService, mInitialUserSetter);
+ mSystemInterface, mUserManager, mUserService, mInitialUserSetter,
+ mVoiceInteractionManagerService);
mService.init();
mService.setShutdownTimersForTest(0, 0);
mPowerHal.setSignalListener(mPowerSignalListener);
@@ -180,7 +185,7 @@
new PowerState(
VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.SHUTDOWN_ONLY));
- assertStateReceived(PowerHalService.SET_SHUTDOWN_START, WAKE_UP_DELAY);
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_SHUTDOWN_START);
assertThat(mService.garageModeShouldExitImmediately()).isFalse();
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isFalse();
mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
@@ -266,10 +271,10 @@
new PowerState(
VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY));
- assertStateReceived(PowerHalService.SET_SHUTDOWN_START, 0);
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_SHUTDOWN_START, 0);
// Cancel the shutdown
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.CANCEL_SHUTDOWN, 0));
- assertStateReceived(PowerHalService.SET_SHUTDOWN_CANCELLED, 0);
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_SHUTDOWN_CANCELLED);
// Go to suspend
mPowerHal.setCurrentPowerState(
new PowerState(
@@ -288,7 +293,7 @@
new PowerState(
VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY));
- assertStateReceived(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
assertThat(mService.garageModeShouldExitImmediately()).isTrue();
mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
@@ -501,6 +506,7 @@
VehicleApPowerStateShutdownParam.CAN_SLEEP));
assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isFalse();
assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+ assertVoiceInteractionDisabled();
mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
// Send the finished signal
@@ -530,6 +536,7 @@
mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
// Since we just woke up from shutdown, wake up time will be 0
assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
+ assertVoiceInteractionEnabled();
assertThat(mDisplayInterface.getDisplayState()).isFalse();
}
@@ -573,6 +580,14 @@
assertStateReceivedForShutdownOrSleepWithPostpone(lastState, expectedSecondParameter);
}
+ private void assertVoiceInteractionEnabled() throws Exception {
+ verify(mVoiceInteractionManagerService).setDisabled(false);
+ }
+
+ private void assertVoiceInteractionDisabled() throws Exception {
+ verify(mVoiceInteractionManagerService).setDisabled(true);
+ }
+
private static void waitForSemaphore(Semaphore semaphore, long timeoutMs)
throws InterruptedException {
if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
index 2b11bd7..e6a81b6 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
@@ -18,6 +18,7 @@
import static android.car.VehiclePropertyIds.CREATE_USER;
import static android.car.VehiclePropertyIds.CURRENT_GEAR;
import static android.car.VehiclePropertyIds.INITIAL_USER_INFO;
+import static android.car.VehiclePropertyIds.REMOVE_USER;
import static android.car.VehiclePropertyIds.SWITCH_USER;
import static android.car.VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION;
import static android.car.test.mocks.CarArgumentMatchers.isProperty;
@@ -53,7 +54,9 @@
import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
@@ -413,28 +416,28 @@
@Test
public void testSwitchUser_invalidTimeout() {
- assertThrows(IllegalArgumentException.class,
- () -> mUserHalService.switchUser(mUser10, 0, mUsersInfo, noOpCallback()));
- assertThrows(IllegalArgumentException.class,
- () -> mUserHalService.switchUser(mUser10, -1, mUsersInfo, noOpCallback()));
+ assertThrows(IllegalArgumentException.class, () -> mUserHalService
+ .switchUser(createUserSwitchRequest(mUser10, mUsersInfo), 0, noOpCallback()));
+ assertThrows(IllegalArgumentException.class, () -> mUserHalService
+ .switchUser(createUserSwitchRequest(mUser10, mUsersInfo), -1, noOpCallback()));
}
@Test
public void testSwitchUser_noUsersInfo() {
- assertThrows(IllegalArgumentException.class,
- () -> mUserHalService.switchUser(mUser10, TIMEOUT_MS, null, noOpCallback()));
+ assertThrows(IllegalArgumentException.class, () -> mUserHalService
+ .switchUser(createUserSwitchRequest(mUser10, null), TIMEOUT_MS, noOpCallback()));
}
@Test
public void testSwitchUser_noCallback() {
- assertThrows(NullPointerException.class,
- () -> mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, null));
+ assertThrows(NullPointerException.class, () -> mUserHalService
+ .switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS, null));
}
@Test
public void testSwitchUser_noTarget() {
- assertThrows(NullPointerException.class,
- () -> mUserHalService.switchUser(null, TIMEOUT_MS, mUsersInfo, noOpCallback()));
+ assertThrows(NullPointerException.class, () -> mUserHalService
+ .switchUser(createUserSwitchRequest(null, mUsersInfo), TIMEOUT_MS, noOpCallback()));
}
@Test
@@ -443,7 +446,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
assertCallbackStatus(callback, HalCallback.STATUS_HAL_SET_TIMEOUT);
@@ -458,7 +462,8 @@
public void testSwitchUser_halDidNotReply() throws Exception {
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
@@ -475,7 +480,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
@@ -493,7 +499,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
@@ -517,7 +524,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
@@ -545,7 +553,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
@@ -567,8 +576,10 @@
CALLBACK_TIMEOUT_TIMEOUT);
GenericHalCallback<SwitchUserResponse> callback2 = new GenericHalCallback<>(
CALLBACK_TIMEOUT_TIMEOUT);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback1);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback2);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback1);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback2);
callback1.assertCalled();
assertCallbackStatus(callback1, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
@@ -590,7 +601,8 @@
GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
CALLBACK_TIMEOUT_SUCCESS);
- mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+ mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+ callback);
callback.assertCalled();
@@ -634,30 +646,113 @@
@Test
public void testPostSwitchResponse_noUsersInfo() {
- assertThrows(NullPointerException.class,
- () -> mUserHalService.postSwitchResponse(42, mUser10, null));
+ SwitchUserRequest request = createUserSwitchRequest(mUser10, null);
+ request.requestId = 42;
+ assertThrows(NullPointerException.class, () -> mUserHalService.postSwitchResponse(request));
}
@Test
public void testPostSwitchResponse_HalCalledWithCorrectProp() {
- mUserHalService.postSwitchResponse(42, mUser10, mUsersInfo);
+ SwitchUserRequest request = createUserSwitchRequest(mUser10, mUsersInfo);
+ request.requestId = 42;
+ mUserHalService.postSwitchResponse(request);
ArgumentCaptor<VehiclePropValue> propCaptor =
ArgumentCaptor.forClass(VehiclePropValue.class);
verify(mVehicleHal).set(propCaptor.capture());
VehiclePropValue prop = propCaptor.getValue();
- assertHalSetSwitchUserRequest(prop, SwitchUserMessageType.ANDROID_POST_SWITCH,
- mUser10);
+ assertHalSetSwitchUserRequest(prop, SwitchUserMessageType.ANDROID_POST_SWITCH, mUser10);
+ }
+
+ @Test
+ public void testLegacyUserSwitch_nullRequest() {
+ assertThrows(NullPointerException.class, () -> mUserHalService.legacyUserSwitch(null));
+ }
+
+ @Test
+ public void testLegacyUserSwitch_noMessageType() {
+ SwitchUserRequest request = new SwitchUserRequest();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.legacyUserSwitch(request));
+ }
+
+ @Test
+ public void testLegacyUserSwitch_noTargetUserInfo() {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.legacyUserSwitch(request));
+ }
+
+ @Test
+ public void testRemoveUser_nullRequest() {
+ RemoveUserRequest request = null;
+
+ assertThrows(NullPointerException.class,
+ () -> mUserHalService.removeUser(request));
+ }
+
+ @Test
+ public void testRemoveUser_noRequestId() {
+ RemoveUserRequest request = new RemoveUserRequest();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.removeUser(request));
+ }
+
+ @Test
+ public void testRemoveUser_noRemovedUserInfo() {
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.requestId = 1;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.removeUser(request));
+ }
+
+ @Test
+ public void testRemoveUser_noUsersInfo() {
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.requestId = 1;
+ request.removedUserInfo = mUser10;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserHalService.removeUser(request));
+ }
+
+ @Test
+ public void testRemoveUser_HalCalledWithCorrectProp() {
+ RemoveUserRequest request = new RemoveUserRequest();
+ request.removedUserInfo = mUser10;
+ request.usersInfo = mUsersInfo;
+ ArgumentCaptor<VehiclePropValue> propCaptor =
+ ArgumentCaptor.forClass(VehiclePropValue.class);
+
+ mUserHalService.removeUser(request);
+
+ verify(mVehicleHal).set(propCaptor.capture());
+ assertHalSetRemoveUserRequest(propCaptor.getValue(), mUser10);
}
@Test
public void testLegacyUserSwitch_noUsersInfo() {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+ request.targetUser = mUser10;
+
assertThrows(IllegalArgumentException.class,
- () -> mUserHalService.legacyUserSwitch(mUser10, null));
+ () -> mUserHalService.legacyUserSwitch(request));
}
@Test
public void testLegacyUserSwitch_HalCalledWithCorrectProp() {
- mUserHalService.legacyUserSwitch(mUser10, mUsersInfo);
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.messageType = SwitchUserMessageType.LEGACY_ANDROID_SWITCH;
+ request.requestId = 1;
+ request.targetUser = mUser10;
+ request.usersInfo = mUsersInfo;
+
+ mUserHalService.legacyUserSwitch(request);
ArgumentCaptor<VehiclePropValue> propCaptor =
ArgumentCaptor.forClass(VehiclePropValue.class);
verify(mVehicleHal).set(propCaptor.capture());
@@ -1240,6 +1335,15 @@
"PropId: 0x" + Integer.toHexString(prop))).when(mVehicleHal).set(isProperty(prop));
}
+ @NonNull
+ private SwitchUserRequest createUserSwitchRequest(@NonNull UserInfo targetUser,
+ @NonNull UsersInfo usersInfo) {
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.targetUser = targetUser;
+ request.usersInfo = usersInfo;
+ return request;
+ }
+
/**
* Creates and set expectations for a valid request.
*/
@@ -1313,6 +1417,17 @@
assertUsersInfo(req, mUsersInfo, 4);
}
+ private void assertHalSetRemoveUserRequest(VehiclePropValue req, UserInfo userInfo) {
+ assertThat(req.prop).isEqualTo(REMOVE_USER);
+ assertWithMessage("wrong request Id on %s", req).that(req.value.int32Values.get(0))
+ .isAtLeast(1);
+ assertWithMessage("user.id mismatch on %s", req).that(req.value.int32Values.get(1))
+ .isEqualTo(userInfo.userId);
+ assertWithMessage("user.flags mismatch on %s", req).that(req.value.int32Values.get(2))
+ .isEqualTo(userInfo.flags);
+ assertUsersInfo(req, mUsersInfo, 3);
+ }
+
private void assertHalSetCreateUserRequest(VehiclePropValue prop, CreateUserRequest request) {
assertThat(prop.prop).isEqualTo(CREATE_USER);
assertWithMessage("wrong request Id on %s", prop).that(prop.value.int32Values.get(0))
diff --git a/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
index 6100dbc..ae86874 100644
--- a/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
@@ -41,6 +41,7 @@
import com.android.car.systeminterface.DisplayInterface;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.systeminterface.SystemStateInterface;
+import com.android.internal.app.IVoiceInteractionManagerService;
import org.junit.After;
import org.junit.Before;
@@ -73,6 +74,8 @@
private Resources mResources;
@Mock
private Car mCar;
+ @Mock
+ private IVoiceInteractionManagerService mVoiceInteractionManagerService;
@Before
public void setUp() throws Exception {
@@ -204,7 +207,7 @@
+ ", maxGarageModeRunningDurationInSecs="
+ mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
- mSystemInterface, null, null, null);
+ mSystemInterface, null, null, null, mVoiceInteractionManagerService);
mService.init();
mService.setShutdownTimersForTest(0, 0);
assertStateReceived(MockedPowerHalService.SET_WAIT_FOR_VHAL, 0);
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
index 13cebbd..1003957 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
@@ -17,7 +17,6 @@
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;
import static android.os.UserHandle.USER_SYSTEM;
@@ -34,17 +33,22 @@
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.test.util.UserTestingHelper;
import android.car.user.CarUserManager;
import android.car.user.CarUserManager.UserLifecycleListener;
import android.car.user.CarUserManager.UserSwitchUiCallback;
+import android.car.user.UserCreationResult;
import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
import android.car.user.UserSwitchResult;
import android.content.pm.UserInfo;
+import android.content.pm.UserInfo.UserInfoFlag;
import android.os.RemoteException;
import android.os.UserManager;
@@ -54,8 +58,6 @@
import org.junit.Test;
import org.mockito.Mock;
-import java.util.List;
-
public final class CarUserManagerUnitTest extends AbstractExtendedMockitoTestCase {
@Mock
@@ -175,7 +177,7 @@
@Test
public void testSwitchUser_remoteException() throws Exception {
- expectServiceSwitchUserSucceeds(11);
+ expectServiceSwitchUserFails(11);
mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
AndroidFuture<UserSwitchResult> future = mMgr.switchUser(11);
@@ -187,6 +189,28 @@
}
@Test
+ public void testRemoveUser_success() throws Exception {
+ int userId = 11;
+ int status = UserRemovalResult.STATUS_SUCCESSFUL;
+ when(mService.removeUser(userId)).thenReturn(new UserRemovalResult(status));
+
+ UserRemovalResult result = mMgr.removeUser(11);
+
+ assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_SUCCESSFUL);
+ }
+
+ @Test
+ public void testRemoveUser_remoteException() throws Exception {
+ int userId = 11;
+ doThrow(new RemoteException("D'OH!")).when(mService).removeUser(eq(userId));
+ mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+ UserRemovalResult result = mMgr.removeUser(11);
+
+ assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_HAL_INTERNAL_FAILURE);
+ }
+
+ @Test
public void testSetSwitchUserUICallback_success() throws Exception {
UserSwitchUiCallback callback = (u)-> { };
@@ -201,6 +225,42 @@
}
@Test
+ public void testCreateUser_success() throws Exception {
+ expectServiceCreateUserSucceeds("dude", "sweet", 42, UserCreationResult.STATUS_SUCCESSFUL,
+ 108);
+
+ AndroidFuture<UserCreationResult> future = mMgr.createUser("dude", "sweet", 42);
+
+ assertThat(future).isNotNull();
+ UserCreationResult result = getResult(future);
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_SUCCESSFUL);
+ assertThat(result.isSuccess()).isTrue();
+ assertThat(result.getErrorMessage()).isNull();
+
+ UserInfo newUser = result.getUser();
+ assertThat(newUser).isNotNull();
+ assertThat(newUser.id).isEqualTo(108);
+ assertThat(newUser.name).isEqualTo("dude");
+ assertThat(newUser.userType).isEqualTo("sweet");
+ assertThat(newUser.flags).isEqualTo(42);
+ }
+
+ @Test
+ public void testCreateUser_remoteException() throws Exception {
+ expectServiceCreateUserFails("dude", "sweet", 42);
+ mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+ AndroidFuture<UserCreationResult> future = mMgr.createUser("dude", "sweet", 42);
+
+ assertThat(future).isNotNull();
+ UserCreationResult result = getResult(future);
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE);
+ assertThat(result.isSuccess()).isFalse();
+ assertThat(result.getErrorMessage()).isNull();
+ assertThat(result.getUser()).isNull();
+ }
+
+ @Test
public void testGetUserIdentificationAssociation_nullTypes() throws Exception {
assertThrows(IllegalArgumentException.class,
() -> mMgr.getUserIdentificationAssociation(null));
@@ -321,13 +381,32 @@
}).when(mService).switchUser(eq(userId), anyInt(), notNull());
}
- private void expectServiceSwitchUserSucceeds(@UserIdInt int userId) throws RemoteException {
+ private void expectServiceSwitchUserFails(@UserIdInt int userId) throws RemoteException {
doThrow(new RemoteException("D'OH!")).when(mService)
.switchUser(eq(userId), anyInt(), notNull());
}
+ private void expectServiceCreateUserSucceeds(@Nullable String name,
+ @NonNull String userType, @UserInfoFlag int flags,
+ @UserCreationResult.Status int status, @UserIdInt int userId) throws RemoteException {
+ doAnswer((invocation) -> {
+ @SuppressWarnings("unchecked")
+ AndroidFuture<UserCreationResult> future =
+ (AndroidFuture<UserCreationResult>) invocation.getArguments()[4];
+ UserInfo newUser = new UserTestingHelper.UserInfoBuilder(108)
+ .setName(name).setType(userType).setFlags(flags).build();
+ future.complete(new UserCreationResult(status, newUser, /* errorMessage= */ null));
+ return null;
+ }).when(mService).createUser(eq(name), eq(userType), eq(flags), anyInt(), notNull());
+ }
+
+ private void expectServiceCreateUserFails(@Nullable String name,
+ @NonNull String userType, @UserInfoFlag int flags) throws RemoteException {
+ doThrow(new RemoteException("D'OH!")).when(mService)
+ .createUser(eq(name), eq(userType), eq(flags), anyInt(), notNull());
+ }
+
private void setExistingUsers(int... userIds) {
- List<UserInfo> users = newUsers(userIds);
- mockUmGetUsers(mUserManager, users);
+ mockUmGetUsers(mUserManager, userIds);
}
}
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 4dc2923..79d3282 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
@@ -17,10 +17,11 @@
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.mockUmCreateUser;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserInfo;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUsers;
import static android.car.test.util.UserTestingHelper.UserInfoBuilder;
+import static android.content.pm.UserInfo.FLAG_ADMIN;
import static android.content.pm.UserInfo.FLAG_EPHEMERAL;
import static android.content.pm.UserInfo.FLAG_GUEST;
@@ -63,10 +64,13 @@
import android.car.user.CarUserManager.UserLifecycleEvent;
import android.car.user.CarUserManager.UserLifecycleEventType;
import android.car.user.CarUserManager.UserLifecycleListener;
+import android.car.user.UserCreationResult;
import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
import android.car.user.UserSwitchResult;
import android.car.userlib.CarUserManagerHelper;
import android.car.userlib.HalCallback;
+import android.car.userlib.HalCallback.HalCallbackStatus;
import android.car.userlib.UserHalHelper;
import android.car.userlib.UserHelper;
import android.content.Context;
@@ -74,9 +78,14 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
+import android.hardware.automotive.vehicle.V2_0.CreateUserRequest;
+import android.hardware.automotive.vehicle.V2_0.CreateUserResponse;
+import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
@@ -136,6 +145,8 @@
private static final int NON_EXISTING_USER = 55; // must not be on mExistingUsers
+ private static final long DEFAULT_LIFECYCLE_TIMESTAMP = 1;
+
@Mock private Context mMockContext;
@Mock private Context mApplicationContext;
@Mock private LocationManager mLocationManager;
@@ -160,6 +171,7 @@
private final int mGetUserInfoRequestType = InitialUserInfoRequestType.COLD_BOOT;
private final AndroidFuture<UserSwitchResult> mUserSwitchFuture = new AndroidFuture<>();
+ private final AndroidFuture<UserCreationResult> mUserCreationFuture = new AndroidFuture<>();
private final AndroidFuture<UserIdentificationAssociationResponse> mUserAssociationRespFuture =
new AndroidFuture<>();
private final int mAsyncCallTimeoutMs = 100;
@@ -308,21 +320,6 @@
}
/**
- * Test that the {@link CarUserService} doesn't update last active user on user switch in
- * headless system user mode.
- */
- @Test
- public void testLastActiveUserUpdatedOnUserSwitch_headlessSystemUser() throws Exception {
- mockIsHeadlessSystemUser(mRegularUser.id, true);
- mockUmGetSystemUser(mMockedUserManager);
- mockExistingUsers();
-
- sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
-
- verifyLastActiveUserNotSet();
- }
-
- /**
* Test that the {@link CarUserService} sets default guest restrictions on first boot.
*/
@Test
@@ -468,33 +465,37 @@
}
@Test
- public void testCreateAdminDriver_IfCurrentUserIsAdminUser() {
- doReturn(true).when(mMockedUserManager).isSystemUser();
- String userName = "testUser";
- UserInfo userInfo = new UserInfo();
- doReturn(userInfo).when(mMockedUserManager).createUser(userName, UserInfo.FLAG_ADMIN);
- assertEquals(userInfo, mCarUserService.createDriver(userName, true));
+ public void testCreateAdminDriver_IfCurrentUserIsAdminUser() throws Exception {
+ when(mMockedUserManager.isSystemUser()).thenReturn(true);
+ mockUmCreateUser(mMockedUserManager, "testUser", UserManager.USER_TYPE_FULL_SECONDARY,
+ UserInfo.FLAG_ADMIN, 10);
+ mockHalCreateUser(HalCallback.STATUS_OK, CreateUserStatus.SUCCESS);
+
+ AndroidFuture<UserCreationResult> future = mCarUserService.createDriver("testUser", true);
+
+ assertThat(getResult(future).getUser().name).isEqualTo("testUser");
+ assertThat(getResult(future).getUser().id).isEqualTo(10);
}
@Test
- public void testCreateAdminDriver_IfCurrentUserIsNotSystemUser() {
- doReturn(false).when(mMockedUserManager).isSystemUser();
- assertEquals(null, mCarUserService.createDriver("testUser", true));
+ public void testCreateAdminDriver_IfCurrentUserIsNotSystemUser() throws Exception {
+ when(mMockedUserManager.isSystemUser()).thenReturn(false);
+ AndroidFuture<UserCreationResult> future = mCarUserService.createDriver("testUser", true);
+ assertThat(getResult(future).getStatus())
+ .isEqualTo(UserCreationResult.STATUS_INVALID_REQUEST);
}
@Test
- public void testCreateNonAdminDriver() {
- String userName = "testUser";
- UserInfo userInfo = new UserInfo();
- doReturn(userInfo).when(mMockedCarUserManagerHelper).createNewNonAdminUser(userName);
- assertEquals(userInfo, mCarUserService.createDriver(userName, false));
- }
+ public void testCreateNonAdminDriver() throws Exception {
+ mockUmCreateUser(mMockedUserManager, "testUser", UserManager.USER_TYPE_FULL_SECONDARY,
+ NO_USER_INFO_FLAGS, 10);
+ mockHalCreateUser(HalCallback.STATUS_OK, CreateUserStatus.SUCCESS);
- @Test
- public void testCreateNonAdminDriver_IfMaximumUserAlreadyCreated() {
- String userName = "testUser";
- doReturn(null).when(mMockedUserManager).createUser(userName, NO_USER_INFO_FLAGS);
- assertEquals(null, mCarUserService.createDriver(userName, false));
+ AndroidFuture<UserCreationResult> future = mCarUserService.createDriver("testUser", false);
+
+ UserInfo userInfo = getResult(future).getUser();
+ assertThat(userInfo.name).isEqualTo("testUser");
+ assertThat(userInfo.id).isEqualTo(10);
}
@Test
@@ -664,6 +665,81 @@
}
@Test
+ public void testRemoveUser_currentUserCannotBeRemoved() throws Exception {
+ mockCurrentUser(mAdminUser);
+
+ UserRemovalResult result = mCarUserService.removeUser(mAdminUser.id);
+
+ assertThat(result.getStatus())
+ .isEqualTo(UserRemovalResult.STATUS_TARGET_USER_IS_CURRENT_USER);
+ }
+
+ @Test
+ public void testRemoveUser_userNotExist() throws Exception {
+ UserRemovalResult result = mCarUserService.removeUser(15);
+
+ assertThat(result.getStatus())
+ .isEqualTo(UserRemovalResult.STATUS_USER_DOES_NOT_EXIST);
+ }
+
+ @Test
+ public void testRemoveUser_lastAdminUser() throws Exception {
+ mockCurrentUser(mRegularUser);
+ mockExistingUsers();
+
+ UserRemovalResult result = mCarUserService.removeUser(mAdminUser.id);
+
+ assertThat(result.getStatus())
+ .isEqualTo(UserRemovalResult.STATUS_TARGET_USER_IS_LAST_ADMIN_USER);
+ }
+
+ @Test
+ public void testRemoveUser_notLastAdminUser_success() throws Exception {
+ // Give admin rights to regular user.
+ UserInfo currentUser = mRegularUser;
+ currentUser.flags = currentUser.flags | FLAG_ADMIN;
+ mockExistingUsersAndCurrentUser(currentUser);
+ int removeUserId = mAdminUser.id;
+ when(mMockedUserManager.removeUser(removeUserId)).thenReturn(true);
+
+ UserRemovalResult result = mCarUserService.removeUser(removeUserId);
+
+ assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_SUCCESSFUL);
+ assertHalRemove(currentUser.id, removeUserId);
+ }
+
+ @Test
+ public void testRemoveUser_success() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ int removeUserId = mRegularUser.id;
+ when(mMockedUserManager.removeUser(removeUserId)).thenReturn(true);
+
+ UserRemovalResult result = mCarUserService.removeUser(removeUserId);
+
+ assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_SUCCESSFUL);
+ assertHalRemove(mAdminUser.id, removeUserId);
+ }
+
+ @Test
+ public void testRemoveUser_androidFailure() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ int targetUserId = mRegularUser.id;
+ when(mMockedUserManager.removeUser(targetUserId)).thenReturn(false);
+
+ UserRemovalResult result = mCarUserService.removeUser(targetUserId);
+
+ assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_ANDROID_FAILURE);
+ }
+
+ @Test
+ public void testSwitchUser_nullReceiver() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+
+ assertThrows(NullPointerException.class, () -> mCarUserService
+ .switchUser(mAdminUser.id, mAsyncCallTimeoutMs, null));
+ }
+
+ @Test
public void testSwitchUser_nonExistingTarget() throws Exception {
assertThrows(IllegalArgumentException.class, () -> mCarUserService
.switchUser(NON_EXISTING_USER, mAsyncCallTimeoutMs, mUserSwitchFuture));
@@ -671,9 +747,10 @@
@Test
public void testSwitchUser_targetSameAsCurrentUser() throws Exception {
- mockExistingUsers();
- mockGetCurrentUser(mAdminUser.id);
+ mockExistingUsersAndCurrentUser(mAdminUser);
+
mCarUserService.switchUser(mAdminUser.id, mAsyncCallTimeoutMs, mUserSwitchFuture);
+
assertThat(getUserSwitchResult().getStatus())
.isEqualTo(UserSwitchResult.STATUS_ALREADY_REQUESTED_USER);
}
@@ -961,21 +1038,17 @@
}
@Test
- public void testHalUserSwitchOnAndroidSwitch_successfulNoExitingUserSwitch() {
+ public void testLegacyUserSwitch_ok() throws Exception {
mockExistingUsers();
sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
- ArgumentCaptor<android.hardware.automotive.vehicle.V2_0.UserInfo> targetUser =
- ArgumentCaptor.forClass(android.hardware.automotive.vehicle.V2_0.UserInfo.class);
- ArgumentCaptor<UsersInfo> usersInfo = ArgumentCaptor.forClass(UsersInfo.class);
- verify(mUserHal).legacyUserSwitch(targetUser.capture(), usersInfo.capture());
- assertThat(targetUser.getValue().userId).isEqualTo(mRegularUser.id);
- assertThat(usersInfo.getValue().currentUser.userId).isEqualTo(mAdminUser.id);
+ verify(mUserHal).legacyUserSwitch(isSwitchUserRequest(0, mAdminUser.id, mRegularUser.id));
}
@Test
- public void testHalUserSwitchOnAndroidSwitch_failureExitingUserSwitch() throws Exception {
+ public void testLegacyUserSwitch_notCalledAfterNormalSwitch() throws Exception {
+ // Arrange - emulate normal switch
mockExistingUsersAndCurrentUser(mAdminUser);
int requestId = 42;
mSwitchUserResponse.status = SwitchUserStatus.SUCCESS;
@@ -984,9 +1057,11 @@
mockAmSwitchUser(mGuestUser, true);
mCarUserService.switchUser(mGuestUser.id, mAsyncCallTimeoutMs, mUserSwitchFuture);
+ // Act - trigger legacy switch
sendUserSwitchingEvent(mAdminUser.id, mGuestUser.id);
- verify(mUserHal, never()).legacyUserSwitch(any(), any());
+ // Assert
+ verify(mUserHal, never()).legacyUserSwitch(any());
}
@Test
@@ -1041,6 +1116,164 @@
}
@Test
+ public void testCreateUser_nullType() throws Exception {
+ assertThrows(NullPointerException.class, () -> mCarUserService
+ .createUser("dude", null, 108, mAsyncCallTimeoutMs, mUserCreationFuture));
+ }
+
+ @Test
+ public void testCreateUser_nullReceiver() throws Exception {
+ assertThrows(NullPointerException.class, () -> mCarUserService
+ .createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs, null));
+ }
+
+ @Test
+ public void testCreateUser_umCreateReturnsNull() throws Exception {
+ // No need to mock um.createUser() to return null
+
+ mCarUserService.createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs,
+ mUserCreationFuture);
+
+ UserCreationResult result = getUserCreationResult();
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_ANDROID_FAILURE);
+ assertThat(result.getUser()).isNull();
+ assertThat(result.getErrorMessage()).isNull();
+ assertNoHalUserCreation();
+ verifyNoUserRemoved();
+ }
+
+ @Test
+ public void testCreateUser_umCreateThrowsException() throws Exception {
+ mockUmCreateUser(mMockedUserManager, "dude", "TypeONegative", 108,
+ new RuntimeException("D'OH!"));
+
+ mCarUserService.createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs,
+ mUserCreationFuture);
+
+ UserCreationResult result = getUserCreationResult();
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_ANDROID_FAILURE);
+ assertThat(result.getUser()).isNull();
+ assertThat(result.getErrorMessage()).isNull();
+ assertNoHalUserCreation();
+ verifyNoUserRemoved();
+ }
+
+ @Test
+ public void testCreateUser_internalHalFailure() throws Exception {
+ mockUmCreateUser(mMockedUserManager, "dude", "TypeONegative", 108, 42);
+ mockHalCreateUser(HalCallback.STATUS_INVALID, /* not_used_status= */ -1);
+
+ mCarUserService.createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs,
+ mUserCreationFuture);
+
+ UserCreationResult result = getUserCreationResult();
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE);
+ assertThat(result.getUser()).isNull();
+ assertThat(result.getErrorMessage()).isNull();
+ verifyUserRemoved(42);
+ }
+
+ @Test
+ public void testCreateUser_halFailure() throws Exception {
+ mockUmCreateUser(mMockedUserManager, "dude", "TypeONegative", 108, 42);
+ mockHalCreateUser(HalCallback.STATUS_OK, CreateUserStatus.FAILURE);
+
+ mCarUserService.createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs,
+ mUserCreationFuture);
+
+ UserCreationResult result = getUserCreationResult();
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_HAL_FAILURE);
+ assertThat(result.getUser()).isNull();
+ assertThat(result.getErrorMessage()).isNull();
+
+ verifyUserRemoved(42);
+ }
+
+ @Test
+ public void testCreateUser_halServiceThrowsRuntimeException() throws Exception {
+ mockUmCreateUser(mMockedUserManager, "dude", "TypeONegative", 108, 42);
+ mockHalCreateUserThrowsRuntimeException();
+
+ mCarUserService.createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs,
+ mUserCreationFuture);
+
+ UserCreationResult result = getUserCreationResult();
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE);
+ assertThat(result.getUser()).isNull();
+ assertThat(result.getErrorMessage()).isNull();
+
+ verifyUserRemoved(42);
+ }
+
+ @Test
+ public void testCreateUser_success() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ int userId = mGuestUser.id;
+ mockUmCreateUser(mMockedUserManager, "dude", UserManager.USER_TYPE_FULL_GUEST,
+ UserInfo.FLAG_EPHEMERAL, userId);
+ ArgumentCaptor<CreateUserRequest> requestCaptor =
+ mockHalCreateUser(HalCallback.STATUS_OK, CreateUserStatus.SUCCESS);
+
+ mCarUserService.createUser("dude", UserManager.USER_TYPE_FULL_GUEST,
+ UserInfo.FLAG_EPHEMERAL, mAsyncCallTimeoutMs, mUserCreationFuture);
+
+ // Assert request
+ CreateUserRequest request = requestCaptor.getValue();
+ Log.d(TAG, "createUser() request: " + request);
+ assertThat(request.newUserName).isEqualTo("dude");
+ assertThat(request.newUserInfo.userId).isEqualTo(userId);
+ assertThat(request.newUserInfo.flags).isEqualTo(UserFlags.GUEST | UserFlags.EPHEMERAL);
+ assertDefaultUsersInfo(request.usersInfo, mAdminUser);
+
+ UserCreationResult result = getUserCreationResult();
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_SUCCESSFUL);
+ assertThat(result.getErrorMessage()).isNull();
+ UserInfo newUser = result.getUser();
+ assertThat(newUser).isNotNull();
+ assertThat(newUser.id).isEqualTo(userId);
+ assertThat(newUser.name).isEqualTo("dude");
+ assertThat(newUser.userType).isEqualTo(UserManager.USER_TYPE_FULL_GUEST);
+ assertThat(newUser.flags).isEqualTo(UserInfo.FLAG_EPHEMERAL);
+
+ verifyNoUserRemoved();
+ }
+
+ @Test
+ public void testCreateUser_success_nullName() throws Exception {
+ String nullName = null;
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ int userId = mGuestUser.id;
+ mockUmCreateUser(mMockedUserManager, nullName, UserManager.USER_TYPE_FULL_GUEST,
+ UserInfo.FLAG_EPHEMERAL, userId);
+ ArgumentCaptor<CreateUserRequest> requestCaptor =
+ mockHalCreateUser(HalCallback.STATUS_OK, CreateUserStatus.SUCCESS);
+
+ mCarUserService.createUser(nullName, UserManager.USER_TYPE_FULL_GUEST,
+ UserInfo.FLAG_EPHEMERAL, mAsyncCallTimeoutMs, mUserCreationFuture);
+
+ // Assert request
+ CreateUserRequest request = requestCaptor.getValue();
+ Log.d(TAG, "createUser() request: " + request);
+ assertThat(request.newUserName).isEmpty();
+ assertThat(request.newUserInfo.userId).isEqualTo(userId);
+ assertThat(request.newUserInfo.flags).isEqualTo(UserFlags.GUEST | UserFlags.EPHEMERAL);
+ assertDefaultUsersInfo(request.usersInfo, mAdminUser);
+
+ UserCreationResult result = getUserCreationResult();
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_SUCCESSFUL);
+ assertThat(result.getErrorMessage()).isNull();
+
+ UserInfo newUser = result.getUser();
+ assertThat(newUser).isNotNull();
+ assertThat(newUser.id).isEqualTo(userId);
+ assertThat(newUser.name).isNull();
+ assertThat(newUser.userType).isEqualTo(UserManager.USER_TYPE_FULL_GUEST);
+ assertThat(newUser.flags).isEqualTo(UserInfo.FLAG_EPHEMERAL);
+
+ verifyNoUserRemoved();
+ }
+
+ @Test
public void testGetUserInfo_nullReceiver() throws Exception {
assertThrows(NullPointerException.class, () -> mCarUserService
.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, null));
@@ -1341,7 +1574,7 @@
sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
verify(mUserMetrics).onEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
- 0, mAdminUser.id, mRegularUser.id);
+ DEFAULT_LIFECYCLE_TIMESTAMP, mAdminUser.id, mRegularUser.id);
}
@Test
@@ -1361,6 +1594,11 @@
}
@NonNull
+ private UserCreationResult getUserCreationResult() throws Exception {
+ return getResult(mUserCreationFuture);
+ }
+
+ @NonNull
private UserIdentificationAssociationResponse getUserAssociationRespResult()
throws Exception {
return getResult(mUserAssociationRespFuture);
@@ -1427,6 +1665,28 @@
mockHalSwitch(currentUserId, HalCallback.STATUS_OK, response, androidTargetUser);
}
+ @NonNull
+ private ArgumentCaptor<CreateUserRequest> mockHalCreateUser(
+ @HalCallbackStatus int callbackStatus, int responseStatus) {
+ CreateUserResponse response = new CreateUserResponse();
+ response.status = responseStatus;
+ ArgumentCaptor<CreateUserRequest> captor = ArgumentCaptor.forClass(CreateUserRequest.class);
+ doAnswer((invocation) -> {
+ Log.d(TAG, "Answering " + invocation + " with " + response);
+ @SuppressWarnings("unchecked")
+ HalCallback<CreateUserResponse> callback =
+ (HalCallback<CreateUserResponse>) invocation.getArguments()[2];
+ callback.onResponse(callbackStatus, response);
+ return null;
+ }).when(mUserHal).createUser(captor.capture(), eq(mAsyncCallTimeoutMs), notNull());
+ return captor;
+ }
+
+ private void mockHalCreateUserThrowsRuntimeException() {
+ doThrow(new RuntimeException("D'OH!"))
+ .when(mUserHal).createUser(any(), eq(mAsyncCallTimeoutMs), notNull());
+ }
+
private void mockCallerUid(int uid, boolean returnCorrectUid) throws Exception {
String packageName = "packageName";
String className = "className";
@@ -1448,18 +1708,20 @@
halTargetUser.userId = androidTargetUser.id;
halTargetUser.flags = UserHalHelper.convertFlags(androidTargetUser);
UsersInfo usersInfo = newUsersInfo(currentUserId);
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.targetUser = halTargetUser;
+ request.usersInfo = usersInfo;
BlockingAnswer<Void> blockingAnswer = BlockingAnswer.forVoidReturn(10_000, (invocation) -> {
Log.d(TAG, "Answering " + invocation + " with " + response);
@SuppressWarnings("unchecked")
HalCallback<SwitchUserResponse> callback = (HalCallback<SwitchUserResponse>) invocation
- .getArguments()[3];
+ .getArguments()[2];
callback.onResponse(HalCallback.STATUS_OK, response);
});
- doAnswer(blockingAnswer).when(mUserHal).switchUser(eq(halTargetUser),
- eq(mAsyncCallTimeoutMs), eq(usersInfo), notNull());
+ doAnswer(blockingAnswer).when(mUserHal).switchUser(eq(request), eq(mAsyncCallTimeoutMs),
+ notNull());
return blockingAnswer;
-
}
private void mockHalSwitch(@UserIdInt int currentUserId,
@@ -1470,15 +1732,18 @@
halTargetUser.userId = androidTargetUser.id;
halTargetUser.flags = UserHalHelper.convertFlags(androidTargetUser);
UsersInfo usersInfo = newUsersInfo(currentUserId);
+ SwitchUserRequest request = new SwitchUserRequest();
+ request.targetUser = halTargetUser;
+ request.usersInfo = usersInfo;
+
doAnswer((invocation) -> {
Log.d(TAG, "Answering " + invocation + " with " + response);
@SuppressWarnings("unchecked")
HalCallback<SwitchUserResponse> callback =
- (HalCallback<SwitchUserResponse>) invocation.getArguments()[3];
+ (HalCallback<SwitchUserResponse>) invocation.getArguments()[2];
callback.onResponse(callbackStatus, response);
return null;
- }).when(mUserHal).switchUser(eq(halTargetUser), eq(mAsyncCallTimeoutMs), eq(usersInfo),
- notNull());
+ }).when(mUserHal).switchUser(eq(request), eq(mAsyncCallTimeoutMs), notNull());
}
private void mockHalGetUserIdentificationAssociation(@NonNull UserInfo user,
@@ -1569,7 +1834,6 @@
for (int i = 0; i < actual.numberUsers; i++) {
assertSameUser(actual.existingUsers.get(i), mExistingUsers.get(i));
}
-
}
private void assertSameUser(android.hardware.automotive.vehicle.V2_0.UserInfo halUser,
@@ -1581,6 +1845,14 @@
.that(halUser.flags).isEqualTo(UserHalHelper.convertFlags(androidUser));
}
+ private void verifyUserRemoved(@UserIdInt int userId) {
+ verify(mMockedUserManager).removeUser(userId);
+ }
+
+ private void verifyNoUserRemoved() {
+ verify(mMockedUserManager, never()).removeUser(anyInt());
+ }
+
@NonNull
private UsersInfo newUsersInfo(@UserIdInt int currentUserId) {
UsersInfo infos = new UsersInfo();
@@ -1666,34 +1938,34 @@
}
private void assertNoPostSwitch() {
- verify(mUserHal, never()).postSwitchResponse(anyInt(), any(), any());
+ verify(mUserHal, never()).postSwitchResponse(any());
}
private void assertPostSwitch(int requestId, int currentId, int targetId) {
- // verify post switch response
- ArgumentCaptor<android.hardware.automotive.vehicle.V2_0.UserInfo> targetUser =
- ArgumentCaptor.forClass(android.hardware.automotive.vehicle.V2_0.UserInfo.class);
- ArgumentCaptor<UsersInfo> usersInfo = ArgumentCaptor.forClass(UsersInfo.class);
- verify(mUserHal).postSwitchResponse(eq(requestId), targetUser.capture(),
- usersInfo.capture());
- assertThat(targetUser.getValue().userId).isEqualTo(targetId);
- assertThat(usersInfo.getValue().currentUser.userId).isEqualTo(currentId);
+ verify(mUserHal).postSwitchResponse(isSwitchUserRequest(requestId, currentId, targetId));
}
private void assertHalSwitch(int currentId, int targetId) {
- verify(mUserHal).switchUser(isHalUser(targetId), eq(mAsyncCallTimeoutMs),
- isHalCurrentUser(currentId), any());
+ verify(mUserHal).switchUser(isSwitchUserRequest(0, currentId, targetId),
+ eq(mAsyncCallTimeoutMs), any());
+ }
+
+ private void assertNoHalUserCreation() {
+ verify(mUserHal, never()).createUser(any(), eq(mAsyncCallTimeoutMs), any());
+ }
+
+ private void assertHalRemove(int currentId, int removeUserId) {
+ ArgumentCaptor<RemoveUserRequest> request =
+ ArgumentCaptor.forClass(RemoveUserRequest.class);
+ verify(mUserHal).removeUser(request.capture());
+ assertThat(request.getValue().removedUserInfo.userId).isEqualTo(removeUserId);
+ assertThat(request.getValue().usersInfo.currentUser.userId).isEqualTo(currentId);
}
@NonNull
- private static android.hardware.automotive.vehicle.V2_0.UserInfo isHalUser(
- @UserIdInt int userId) {
- return argThat(new UserInfoMatcher(userId));
- }
-
- @NonNull
- private static UsersInfo isHalCurrentUser(@UserIdInt int userId) {
- return argThat(new UsersInfoCurrentUserIdMatcher(userId));
+ private static SwitchUserRequest isSwitchUserRequest(int requestId,
+ @UserIdInt int currentUserId, @UserIdInt int targetUserId) {
+ return argThat(new SwitchUserRequestMatcher(requestId, currentUserId, targetUserId));
}
static final class FakeCarOccupantZoneService {
@@ -1740,7 +2012,8 @@
private void sendUserLifecycleEvent(@UserIdInt int fromUserId, @UserIdInt int toUserId,
@UserLifecycleEventType int eventType) {
- mCarUserService.onUserLifecycleEvent(eventType, /* timestampMs= */ 0, fromUserId, toUserId);
+ mCarUserService.onUserLifecycleEvent(eventType, DEFAULT_LIFECYCLE_TIMESTAMP, fromUserId,
+ toUserId);
}
private void sendUserUnlockedEvent(@UserIdInt int userId) {
@@ -1818,53 +2091,46 @@
}
}
- private static final class UserInfoMatcher
- implements ArgumentMatcher<android.hardware.automotive.vehicle.V2_0.UserInfo> {
-
- private static final String MY_TAG =
- android.hardware.automotive.vehicle.V2_0.UserInfo.class.getSimpleName();
-
- private final @UserIdInt int mUserId;
-
- private UserInfoMatcher(@UserIdInt int userId) {
- mUserId = userId;
- }
-
- @Override
- public boolean matches(android.hardware.automotive.vehicle.V2_0.UserInfo argument) {
- if (argument == null) {
- Log.w(MY_TAG, "null argument");
- return false;
- }
- if (argument.userId != mUserId) {
- Log.w(MY_TAG, "wrong user id on " + argument + "; expected " + mUserId);
- return false;
- }
- Log.d(MY_TAG, "Good News, Everyone! " + argument + " matches " + this);
- return true;
- }
- }
-
- private static final class UsersInfoCurrentUserIdMatcher implements ArgumentMatcher<UsersInfo> {
-
+ private static final class SwitchUserRequestMatcher
+ implements ArgumentMatcher<SwitchUserRequest> {
private static final String MY_TAG = UsersInfo.class.getSimpleName();
- private final @UserIdInt int mUserId;
+ private final int mRequestId;
+ private final @UserIdInt int mCurrentUserId;
+ private final @UserIdInt int mTargetUserId;
- private UsersInfoCurrentUserIdMatcher(@UserIdInt int userId) {
- mUserId = userId;
+
+ private SwitchUserRequestMatcher(int requestId, @UserIdInt int currentUserId,
+ @UserIdInt int targetUserId) {
+ mCurrentUserId = currentUserId;
+ mTargetUserId = targetUserId;
+ mRequestId = requestId;
}
@Override
- public boolean matches(UsersInfo argument) {
+ public boolean matches(SwitchUserRequest argument) {
if (argument == null) {
Log.w(MY_TAG, "null argument");
return false;
}
- if (argument.currentUser.userId != mUserId) {
- Log.w(MY_TAG, "wrong user id on " + argument + "; expected " + mUserId);
+ if (argument.usersInfo.currentUser.userId != mCurrentUserId) {
+ Log.w(MY_TAG,
+ "wrong current user id on " + argument + "; expected " + mCurrentUserId);
return false;
}
+
+ if (argument.targetUser.userId != mTargetUserId) {
+ Log.w(MY_TAG,
+ "wrong target user id on " + argument + "; expected " + mTargetUserId);
+ return false;
+ }
+
+ if (argument.requestId != mRequestId) {
+ Log.w(MY_TAG,
+ "wrong request Id on " + argument + "; expected " + mTargetUserId);
+ return false;
+ }
+
Log.d(MY_TAG, "Good News, Everyone! " + argument + " matches " + this);
return true;
}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/ExperimentalCarUserManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/user/ExperimentalCarUserManagerUnitTest.java
index 8b4dc55..2869d5d 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
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.doAnswer;
@@ -33,6 +34,7 @@
import android.car.test.util.UserTestingHelper;
import android.car.user.CarUserManager;
import android.car.user.ExperimentalCarUserManager;
+import android.car.user.UserCreationResult;
import android.car.user.UserSwitchResult;
import android.content.pm.UserInfo;
import android.os.RemoteException;
@@ -66,23 +68,41 @@
@Test
public void testCreateDriver_Success_Admin() throws Exception {
- expectCreateDriverSucceed(10);
- int userId = mManager.createDriver("test driver", true);
- assertThat(userId).isEqualTo(10);
+ String name = "test driver";
+ int userId = 10;
+ expectCreateDriverSucceed(name, userId);
+
+ AndroidFuture<UserCreationResult> future = mManager.createDriver(name, true);
+
+ UserCreationResult result = getResult(future);
+ assertThat(result.getErrorMessage()).isNull();
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_SUCCESSFUL);
+ assertThat(result.getUser().id).isEqualTo(userId);
}
@Test
public void testCreateDriver_Success_NonAdmin() throws Exception {
- expectCreateDriverSucceed(10);
- int userId = mManager.createDriver("test driver", false);
- assertThat(userId).isEqualTo(10);
+ String name = "test driver";
+ int userId = 10;
+ expectCreateDriverSucceed(name, userId);
+
+ AndroidFuture<UserCreationResult> future = mManager.createDriver(name, false);
+
+ UserCreationResult result = getResult(future);
+ assertThat(result.getErrorMessage()).isNull();
+ assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_SUCCESSFUL);
+ assertThat(result.getUser().id).isEqualTo(userId);
}
@Test
public void testCreateDriver_Error() throws Exception {
expectCreateDriverFail();
- int userId = mManager.createDriver("test driver", false);
- assertThat(userId).isEqualTo(UserHandle.USER_NULL);
+
+ AndroidFuture<UserCreationResult> future = mManager.createDriver("test driver", false);
+
+ assertThat(future).isNotNull();
+ UserCreationResult result = getResult(future);
+ assertThat(result.getStatus()).isEqualTo(UserSwitchResult.STATUS_HAL_INTERNAL_FAILURE);
}
@Test
@@ -165,13 +185,16 @@
assertThat(success).isFalse();
}
- private void expectCreateDriverSucceed(@UserIdInt int userId) throws Exception {
- UserInfo userInfo = UserTestingHelper.newUser(userId);
- when(mService.createDriver(eq("test driver"), anyBoolean())).thenReturn(userInfo);
+ private void expectCreateDriverSucceed(String name, @UserIdInt int userId) throws Exception {
+ AndroidFuture<UserCreationResult> future = new AndroidFuture<>();
+ future.complete(new UserCreationResult(UserCreationResult.STATUS_SUCCESSFUL,
+ UserTestingHelper.newUser(userId), null));
+ when(mService.createDriver(eq(name), anyBoolean())).thenReturn(future);
}
private void expectCreateDriverFail() throws Exception {
- when(mService.createDriver(eq("test driver"), anyBoolean())).thenReturn(null);
+ doThrow(new RemoteException("D'OH!")).when(mService)
+ .createDriver(anyString(), anyBoolean());
}
private void expectCreatePassengerSucceed() throws Exception {
diff --git a/tests/carservice_unit_test/src/com/android/car/user/UserCreationResultTest.java b/tests/carservice_unit_test/src/com/android/car/user/UserCreationResultTest.java
new file mode 100644
index 0000000..7aba561
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/user/UserCreationResultTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import android.car.user.UserCreationResult;
+
+import org.junit.Test;
+
+public final class UserCreationResultTest {
+
+ @Test
+ public void testIsSuccess() {
+ assertThat(new UserCreationResult(UserCreationResult.STATUS_SUCCESSFUL, null, null)
+ .isSuccess()).isTrue();
+ assertThat(new UserCreationResult(UserCreationResult.STATUS_HAL_FAILURE, null, null)
+ .isSuccess()).isFalse();
+ assertThat(
+ new UserCreationResult(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE, null, null)
+ .isSuccess()).isFalse();
+ assertThat(new UserCreationResult(UserCreationResult.STATUS_ANDROID_FAILURE, null, null)
+ .isSuccess()).isFalse();
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/UserSwitchResultTest.java b/tests/carservice_unit_test/src/com/android/car/user/UserSwitchResultTest.java
new file mode 100644
index 0000000..385bb4e
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/user/UserSwitchResultTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import android.car.user.UserSwitchResult;
+
+import org.junit.Test;
+
+public final class UserSwitchResultTest {
+
+ @Test
+ public void testIUserSwitchResult_checkStatusAndMessage() {
+ String msg = "Test Message";
+ UserSwitchResult result =
+ new UserSwitchResult(UserSwitchResult.STATUS_SUCCESSFUL, msg);
+ assertThat(result.getStatus()).isEqualTo(UserSwitchResult.STATUS_SUCCESSFUL);
+ assertThat(result.getErrorMessage()).isEqualTo(msg);
+ }
+
+ @Test
+ public void testIUserSwitchResult_isSuccess_failure() {
+ UserSwitchResult result =
+ new UserSwitchResult(UserSwitchResult.STATUS_ANDROID_FAILURE, null);
+ assertThat(result.isSuccess()).isFalse();
+ }
+
+ @Test
+ public void testIUserSwitchResult_isSuccess_success() {
+ UserSwitchResult result =
+ new UserSwitchResult(UserSwitchResult.STATUS_SUCCESSFUL, null);
+ assertThat(result.isSuccess()).isTrue();
+ }
+
+ @Test
+ public void testIUserSwitchResult_isSuccess_requestedState() {
+ UserSwitchResult result =
+ new UserSwitchResult(UserSwitchResult.STATUS_ALREADY_REQUESTED_USER, null);
+ assertThat(result.isSuccess()).isTrue();
+ }
+}
diff --git a/user/car-user-lib/Android.bp b/user/car-user-lib/Android.bp
index a9a5fc1..12bc950 100644
--- a/user/car-user-lib/Android.bp
+++ b/user/car-user-lib/Android.bp
@@ -18,6 +18,7 @@
"src/**/*.java",
],
static_libs: [
+ "android.car.settings",
"android.hardware.automotive.vehicle-V2.0-java",
],
product_variables: {
diff --git a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
index 4079e28..15a7d1a 100644
--- a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
@@ -17,10 +17,12 @@
package android.car.userlib;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.car.settings.CarSettings;
import android.content.Context;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
@@ -30,7 +32,6 @@
import android.sysprop.CarProperties;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.UserIcons;
import com.google.android.collect.Sets;
@@ -57,6 +58,7 @@
public final class CarUserManagerHelper {
private static final String TAG = "CarUserManagerHelper";
+ private static final boolean DEBUG = false;
private static final int BOOT_USER_NOT_FOUND = -1;
/**
@@ -97,14 +99,41 @@
* Sets the last active user.
*/
public void setLastActiveUser(@UserIdInt int userId) {
- Settings.Global.putInt(
- mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID, userId);
+ if (UserHelper.isHeadlessSystemUser(userId)) {
+ if (DEBUG) Log.d(TAG, "setLastActiveUser(): ignoring headless system user " + userId);
+ return;
+ }
+ setUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_USER_ID, userId);
+
+ // TODO(b/155918094): change method to receive a UserInfo instead
+ UserInfo user = mUserManager.getUserInfo(userId);
+ if (user == null) {
+ Log.w(TAG, "setLastActiveUser(): user " + userId + " doesn't exist");
+ return;
+ }
+ if (!user.isEphemeral()) {
+ setUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID, userId);
+ }
}
- private int getLastActiveUser() {
- return Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID,
- /* default user id= */ UserHandle.USER_SYSTEM);
+ private void setUserIdGlobalProperty(@NonNull String name, @UserIdInt int userId) {
+ if (DEBUG) Log.d(TAG, "setting global property " + name + " to " + userId);
+
+ Settings.Global.putInt(mContext.getContentResolver(), name, userId);
+ }
+
+ private int getUserIdGlobalProperty(@NonNull String name) {
+ int userId = Settings.Global.getInt(mContext.getContentResolver(), name,
+ UserHandle.USER_NULL);
+ if (DEBUG) Log.d(TAG, "getting global property " + name + ": " + userId);
+
+ return userId;
+ }
+
+ private void resetUserIdGlobalProperty(@NonNull String name) {
+ if (DEBUG) Log.d(TAG, "resetting global property " + name);
+
+ Settings.Global.putInt(mContext.getContentResolver(), name, UserHandle.USER_NULL);
}
/**
@@ -125,7 +154,6 @@
* @return user id of the initial user to boot into on the device, or
* {@link UserHandle#USER_NULL} if there is no user available.
*/
- @VisibleForTesting
int getInitialUser(boolean usesOverrideUserIdProperty) {
List<Integer> allUsers = userInfoListToUserIdList(getAllUsers());
@@ -148,18 +176,29 @@
}
// If the last active user is not the SYSTEM user and is a real user, return it
- int lastActiveUser = getLastActiveUser();
- if (lastActiveUser != UserHandle.USER_SYSTEM
- && allUsers.contains(lastActiveUser)) {
- Log.i(TAG, "Last active user loaded for initial user, user id: "
- + lastActiveUser);
+ int lastActiveUser = getUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_USER_ID);
+ if (allUsers.contains(lastActiveUser)) {
+ Log.i(TAG, "Last active user loaded for initial user: " + lastActiveUser);
return lastActiveUser;
}
+ resetUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_USER_ID);
+
+ int lastPersistentUser = getUserIdGlobalProperty(
+ CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+ if (allUsers.contains(lastPersistentUser)) {
+ Log.i(TAG, "Last active, persistent user loaded for initial user: "
+ + lastPersistentUser);
+ return lastPersistentUser;
+ }
+ resetUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
// If all else fails, return the smallest user id
int returnId = Collections.min(allUsers);
- Log.i(TAG, "Saved ids were invalid. Returning smallest user id, user id: "
- + returnId);
+ // TODO(b/158101909): the smallest user id is not always the initial user; a better approach
+ // would be looking for the first ADMIN user, or keep track of all last active users (not
+ // just the very last)
+ Log.w(TAG, "Last active user (" + lastActiveUser + ") not found. Returning smallest user id"
+ + " instead: " + returnId);
return returnId;
}
diff --git a/user/car-user-lib/src/android/car/userlib/UserHalHelper.java b/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
index a9ddf0b..67a83c3 100644
--- a/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
@@ -27,6 +27,8 @@
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.UserFlags;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue;
@@ -60,9 +62,12 @@
private static final boolean DEBUG = false;
public static final int INITIAL_USER_INFO_PROPERTY = 299896583;
+ public static final int SWITCH_USER_PROPERTY = 299896584;
public static final int CREATE_USER_PROPERTY = 299896585;
+ public static final int REMOVE_USER_PROPERTY = 299896586;
public static final int USER_IDENTIFICATION_ASSOCIATION_PROPERTY = 299896587;
+
private static final String STRING_SEPARATOR = "\\|\\|";
/**
@@ -486,6 +491,8 @@
Objects.requireNonNull(request, "request cannot be null");
checkArgument(request.requestId > 0, "invalid requestId on %s", request);
checkValid(request.usersInfo);
+ checkArgument(request.newUserName != null, "newUserName cannot be null (should be empty "
+ + "instead) on %s", request);
boolean hasNewUser = false;
int newUserFlags = UserFlags.NONE;
@@ -513,10 +520,62 @@
}
/**
- * Creates a {@link UsersInfo} instance populated with the current users.
+ * Creates a generic {@link VehiclePropValue} (that can be sent to HAL) from a
+ * {@link SwitchUserRequest}.
+ *
+ * @throws IllegalArgumentException if the request doesn't have the proper format.
+ */
+ @NonNull
+ public static VehiclePropValue toVehiclePropValue(@NonNull SwitchUserRequest request) {
+ Objects.requireNonNull(request, "request cannot be null");
+ checkArgument(request.messageType > 0, "invalid messageType on %s", request);
+ android.hardware.automotive.vehicle.V2_0.UserInfo targetInfo = request.targetUser;
+ UsersInfo usersInfo = request.usersInfo;
+ Objects.requireNonNull(targetInfo);
+ checkValid(usersInfo);
+
+ VehiclePropValue propValue = createPropRequest(SWITCH_USER_PROPERTY, request.requestId,
+ request.messageType);
+ addUserInfo(propValue, targetInfo);
+ addUsersInfo(propValue, usersInfo);
+ return propValue;
+ }
+
+ /**
+ * Creates a generic {@link VehiclePropValue} (that can be sent to HAL) from a
+ * {@link RemoveUserRequest}.
+ *
+ * @throws IllegalArgumentException if the request doesn't have the proper format.
+ */
+ @NonNull
+ public static VehiclePropValue toVehiclePropValue(@NonNull RemoveUserRequest request) {
+ checkArgument(request.requestId > 0, "invalid requestId on %s", request);
+ android.hardware.automotive.vehicle.V2_0.UserInfo removedUserInfo = request.removedUserInfo;
+ Objects.requireNonNull(removedUserInfo);
+ UsersInfo usersInfo = request.usersInfo;
+ checkValid(usersInfo);
+
+ VehiclePropValue propValue = createPropRequest(REMOVE_USER_PROPERTY, request.requestId);
+ addUserInfo(propValue, removedUserInfo);
+ addUsersInfo(propValue, usersInfo);
+ return propValue;
+ }
+
+ /**
+ * Creates a {@link UsersInfo} instance populated with the current users, using
+ * {@link ActivityManager#getCurrentUser()} as the current user.
*/
@NonNull
public static UsersInfo newUsersInfo(@NonNull UserManager um) {
+ return newUsersInfo(um, ActivityManager.getCurrentUser());
+ }
+
+ /**
+ * Creates a {@link UsersInfo} instance populated with the current users, using
+ * {@code userId} as the current user.
+ */
+ @NonNull
+ public static UsersInfo newUsersInfo(@NonNull UserManager um, @UserIdInt int userId) {
Preconditions.checkArgument(um != null, "UserManager cannot be null");
List<UserInfo> users = um.getUsers(/*excludeDying= */ true);
@@ -527,7 +586,7 @@
}
UsersInfo usersInfo = new UsersInfo();
- usersInfo.currentUser.userId = ActivityManager.getCurrentUser();
+ usersInfo.currentUser.userId = userId;
UserInfo currentUser = null;
usersInfo.numberUsers = users.size();