Merge "Add CarPackageManager#isPendingIntentDistractionOptimized(PendingIntent)"
diff --git a/car-lib/api/current.txt b/car-lib/api/current.txt
index 9f9fda6..096dd6b 100644
--- a/car-lib/api/current.txt
+++ b/car-lib/api/current.txt
@@ -295,6 +295,7 @@
field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_MAKE = 286261505; // 0x11100101
field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_MODEL = 286261506; // 0x11100102
field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_MODEL_YEAR = 289407235; // 0x11400103
+ field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_MULTI_EV_PORT_LOCATIONS = 289472780; // 0x1141010c
field @RequiresPermission(android.car.Car.PERMISSION_IDENTIFICATION) public static final int INFO_VIN = 286261504; // 0x11100100
field public static final int INITIAL_USER_INFO = 299896583; // 0x11e00f07
field public static final int INVALID = 0; // 0x0
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 90f6357..1af4c36 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -238,7 +238,9 @@
method @MainThread public void onKeyEvent(@NonNull android.view.KeyEvent);
method @MainThread public void onNavigationComponentLaunched();
method @MainThread public void onNavigationComponentReleased();
+ method public boolean startFixedActivityModeForDisplayAndUser(@NonNull android.content.Intent, @NonNull android.app.ActivityOptions, int);
method protected boolean startNavigationActivity(@NonNull android.content.ComponentName);
+ method public void stopFixedActivityMode(int);
}
@UiThread public abstract class NavigationRenderer {
@@ -847,6 +849,20 @@
field public static final android.os.Parcelable.Creator<android.car.media.CarAudioPatchHandle> CREATOR;
}
+ public final class CarMediaManager {
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addMediaSourceListener(@NonNull android.car.media.CarMediaManager.MediaSourceChangedListener, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public java.util.List<android.content.ComponentName> getLastMediaSources(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.content.ComponentName getMediaSource(int);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeMediaSourceListener(@NonNull android.car.media.CarMediaManager.MediaSourceChangedListener, int);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void setMediaSource(@NonNull android.content.ComponentName, int);
+ field public static final int MEDIA_SOURCE_MODE_BROWSE = 1; // 0x1
+ field public static final int MEDIA_SOURCE_MODE_PLAYBACK = 0; // 0x0
+ }
+
+ public static interface CarMediaManager.MediaSourceChangedListener {
+ method public void onMediaSourceChanged(@NonNull android.content.ComponentName);
+ }
+
}
package android.car.navigation {
diff --git a/car-lib/src/android/car/ICarUserService.aidl b/car-lib/src/android/car/ICarUserService.aidl
index 1ff1522..1561ad9 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -30,4 +30,6 @@
boolean stopPassenger(int passengerId);
oneway void setLifecycleListenerForUid(in IResultReceiver listener);
oneway void resetLifecycleListenerForUid();
+ oneway void getInitialUserInfo(int requestType, int timeoutMs, in UserInfo[] existingUsers,
+ int currentUserId, in IResultReceiver receiver);
}
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
index f6959fe..60b3b0a 100644
--- a/car-lib/src/android/car/VehiclePropertyIds.java
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -72,24 +72,50 @@
@RequiresPermission(Car.PERMISSION_CAR_INFO)
public static final int INFO_EV_BATTERY_CAPACITY = 291504390;
/**
- * List of connectors this EV may use
+ * List of connectors this vehicle may use
+ *
+ * <p>Applications can query the property value by
+ * {@link android.car.hardware.property.CarPropertyManager#getIntArrayProperty(int, int)}. The
+ * return value is an integer array containing enums in {@link EvConnectorType}
+ *
* Requires permission: {@link Car#PERMISSION_CAR_INFO}.
*/
@RequiresPermission(Car.PERMISSION_CAR_INFO)
public static final int INFO_EV_CONNECTOR_TYPE = 289472775;
/**
* Fuel door location
+ *
+ * <p> Applications can query the property value by
+ * {@link android.car.hardware.property.CarPropertyManager#getIntProperty(int, int)}. The return
+ * value is one of enums in {@link PortLocationType}.
+ *
* Requires permission: {@link Car#PERMISSION_CAR_INFO}.
*/
@RequiresPermission(Car.PERMISSION_CAR_INFO)
public static final int INFO_FUEL_DOOR_LOCATION = 289407240;
/**
* EV port location
+ *
+ * <p> Applications can query the property value by
+ * {@link android.car.hardware.property.CarPropertyManager#getIntProperty(int, int)}. The return
+ * value is one of enums in {@link PortLocationType}.
+ *
* Requires permission: {@link Car#PERMISSION_CAR_INFO}.
*/
@RequiresPermission(Car.PERMISSION_CAR_INFO)
public static final int INFO_EV_PORT_LOCATION = 289407241;
/**
+ * Multiple EV port locations
+ *
+ * <p> Applications can query the property value by
+ * {@link android.car.hardware.property.CarPropertyManager#getIntArrayProperty(int, int)}. The
+ * return value is an integer array containing enums in {@link PortLocationType}.
+ *
+ * Requires permission: {@link Car#PERMISSION_CAR_INFO}.
+ */
+ @RequiresPermission(Car.PERMISSION_CAR_INFO)
+ public static final int INFO_MULTI_EV_PORT_LOCATIONS = 289472780;
+ /**
* Driver's seat location
* Requires permission: {@link Car#PERMISSION_CAR_INFO}.
*/
@@ -928,6 +954,8 @@
return "INFO_FUEL_TYPE";
case INFO_EV_BATTERY_CAPACITY:
return "INFO_EV_BATTERY_CAPACITY";
+ case INFO_MULTI_EV_PORT_LOCATIONS:
+ return "INFO_MULTI_EV_PORT_LOCATIONS";
case INFO_EV_CONNECTOR_TYPE:
return "INFO_EV_CONNECTOR_TYPE";
case INFO_FUEL_DOOR_LOCATION:
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index 582a872..5098ee7 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -255,10 +255,8 @@
* target Activity is in normal state and client should retry when it fails. Once it is
* successfully launched, car service will guarantee that it is running across crash or
* other events.
- *
- * @hide
*/
- protected boolean startFixedActivityModeForDisplayAndUser(@NonNull Intent intent,
+ public boolean startFixedActivityModeForDisplayAndUser(@NonNull Intent intent,
@NonNull ActivityOptions options, @UserIdInt int userId) {
IInstrumentClusterHelper helper = getClusterHelper();
if (helper == null) {
@@ -278,10 +276,8 @@
/**
* Stop fixed mode for top Activity in the display. Crashing or launching other Activity
* will not re-launch the top Activity any more.
- *
- * @hide
*/
- protected void stopFixedActivityMode(int displayId) {
+ public void stopFixedActivityMode(int displayId) {
IInstrumentClusterHelper helper = getClusterHelper();
if (helper == null) {
return;
diff --git a/car-lib/src/android/car/media/CarMediaManager.java b/car-lib/src/android/car/media/CarMediaManager.java
index 8537ed6..d286403 100644
--- a/car-lib/src/android/car/media/CarMediaManager.java
+++ b/car-lib/src/android/car/media/CarMediaManager.java
@@ -15,27 +15,45 @@
*/
package android.car.media;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.car.Car;
import android.car.CarManagerBase;
import android.content.ComponentName;
import android.os.IBinder;
import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
* API for updating and receiving updates to the primary media source in the car.
* @hide
*/
+@SystemApi
public final class CarMediaManager extends CarManagerBase {
+ public static final int MEDIA_SOURCE_MODE_PLAYBACK = 0;
+ public static final int MEDIA_SOURCE_MODE_BROWSE = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "MEDIA_SOURCE_MODE_" }, value = {
+ MEDIA_SOURCE_MODE_PLAYBACK,
+ MEDIA_SOURCE_MODE_BROWSE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MediaSourceMode {}
+
private final ICarMedia mService;
private Map<MediaSourceChangedListener, ICarMediaSourceListener> mCallbackMap = new HashMap();
/**
- * Get an instance of the CarPowerManager.
+ * Get an instance of the CarMediaManager.
*
* Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
* @hide
@@ -47,15 +65,13 @@
/**
* Listener for updates to the primary media source
- * @hide
*/
public interface MediaSourceChangedListener {
/**
* Called when the primary media source is changed
- * @hide
*/
- void onMediaSourceChanged(ComponentName componentName);
+ void onMediaSourceChanged(@NonNull ComponentName componentName);
}
/**
@@ -121,6 +137,49 @@
handleRemoteExceptionFromCarService(e);
}
}
+ /**
+ * Gets the currently active media source for the provided mode
+ */
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public @NonNull ComponentName getMediaSource(@MediaSourceMode int mode) {
+ // STUB
+ return null;
+ }
+
+ /**
+ * Sets the currently active media source for the provided mode
+ */
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void setMediaSource(@NonNull ComponentName componentName, @MediaSourceMode int mode) {
+ // STUB
+ }
+
+ /**
+ * Register a callback that receives updates to the active media source.
+ */
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void addMediaSourceListener(@NonNull MediaSourceChangedListener callback,
+ @MediaSourceMode int mode) {
+ // STUB
+ }
+
+ /**
+ * Unregister a callback that receives updates to the active media source.
+ */
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void removeMediaSourceListener(@NonNull MediaSourceChangedListener callback,
+ @MediaSourceMode int mode) {
+ // STUB
+ }
+
+ /**
+ * Retrieve a list of media sources, ordered by most recently used.
+ */
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public @NonNull List<ComponentName> getLastMediaSources(@MediaSourceMode int mode) {
+ // STUB
+ return null;
+ }
/** @hide */
@Override
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index 26f81b4..a38a437 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -96,9 +96,8 @@
<bool name="config_automotiveHideNavBarForKeyboard">true</bool>
- <!-- Turn on Wallpaper service -->
- <!-- Enabled temporarily to watch the dashboard-->
- <bool name="config_enableWallpaperService">true</bool>
+ <!-- Turn off Wallpaper service -->
+ <bool name="config_enableWallpaperService">false</bool>
<!-- Whether to only install system packages on a user if they're whitelisted for that user
type. Override the default value in framework config file.
diff --git a/car_product/overlay/packages/apps/Bluetooth/res/values/config.xml b/car_product/overlay/packages/apps/Bluetooth/res/values/config.xml
index 810549a..199e0ae 100644
--- a/car_product/overlay/packages/apps/Bluetooth/res/values/config.xml
+++ b/car_product/overlay/packages/apps/Bluetooth/res/values/config.xml
@@ -44,6 +44,7 @@
<bool name="profile_supported_hfpclient">true</bool>
<bool name="hfp_client_connection_service_enabled">true</bool>
<bool name="profile_supported_avrcp_controller">true</bool>
+ <bool name="avrcp_controller_enable_cover_art">true</bool>
<bool name="profile_supported_a2dp_sink">true</bool>
<bool name="profile_supported_pbapclient">true</bool>
<bool name="profile_supported_pan">true</bool>
diff --git a/service/src/com/android/car/hal/PropertyHalServiceIds.java b/service/src/com/android/car/hal/PropertyHalServiceIds.java
index 2f7ae61..b9f3134 100644
--- a/service/src/com/android/car/hal/PropertyHalServiceIds.java
+++ b/service/src/com/android/car/hal/PropertyHalServiceIds.java
@@ -319,6 +319,9 @@
mProps.put(VehicleProperty.INFO_FUEL_DOOR_LOCATION, new Pair<>(
Car.PERMISSION_CAR_INFO,
null));
+ mProps.put(VehicleProperty.INFO_MULTI_EV_PORT_LOCATIONS, new Pair<>(
+ Car.PERMISSION_CAR_INFO,
+ null));
mProps.put(VehicleProperty.INFO_EV_PORT_LOCATION, new Pair<>(
Car.PERMISSION_CAR_INFO,
null));
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index e2f21b5..5ec0933 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -34,6 +34,9 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.UserFlags;
+import android.hardware.automotive.vehicle.V2_0.UsersInfo;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
@@ -52,6 +55,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.UserIcons;
import java.io.PrintWriter;
@@ -74,6 +78,12 @@
*/
public final class CarUserService extends ICarUserService.Stub implements CarServiceBase {
+ /** Extra used to represent a user id in a {@link IResultReceiver} response. */
+ public static final String BUNDLE_USER_ID = "user.id";
+ /** Extra used to represent user flags in a {@link IResultReceiver} response. */
+ public static final String BUNDLE_USER_FLAGS = "user.flags";
+ /** Extra used to represent a user name in a {@link IResultReceiver} response. */
+ public static final String BUNDLE_USER_NAME = "user.name";
private final Context mContext;
private final CarUserManagerHelper mCarUserManagerHelper;
@@ -466,6 +476,75 @@
mLifecycleListeners.remove(uid);
}
+ @Override
+ public void getInitialUserInfo(int requestType, int timeoutMs,
+ @NonNull UserInfo[] existingUsers, @UserIdInt int currentUserId,
+ @NonNull IResultReceiver receiver) {
+
+ // TODO(b/144120654): use helper to generate USerInfo
+ UsersInfo usersInfo = new UsersInfo();
+ usersInfo.numberUsers = existingUsers.length;
+ boolean foundCurrentUser = false;
+ for (int i = 0; i < existingUsers.length; i++) {
+ UserInfo user = existingUsers[i];
+ android.hardware.automotive.vehicle.V2_0.UserInfo halUser =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ halUser.userId = user.id;
+ int flags = UserFlags.NONE;
+ if (user.id == UserHandle.USER_SYSTEM) {
+ flags |= UserFlags.SYSTEM;
+ }
+ if (user.isAdmin()) {
+ flags |= UserFlags.ADMIN;
+ }
+ if (user.isGuest()) {
+ flags |= UserFlags.GUEST;
+ }
+ if (user.isEphemeral()) {
+ flags |= UserFlags.EPHEMERAL;
+ }
+ halUser.flags = flags;
+ usersInfo.existingUsers.add(halUser);
+ if (user.id == currentUserId) {
+ // TODO(b/144120654): unit test for when it isn't there (on helper method), or
+ // change signature so the currentUser argument is a UserInfo)
+ foundCurrentUser = true;
+ usersInfo.currentUser.userId = currentUserId;
+ usersInfo.currentUser.flags = flags;
+ }
+ }
+ Preconditions.checkArgument(foundCurrentUser,
+ "no user with id " + currentUserId + " on " + existingUsers);
+
+ mHal.getInitialUserInfo(requestType, timeoutMs, usersInfo, (status, resp) -> {
+ try {
+ Bundle resultData = null;
+ if (resp != null) {
+ switch (resp.action) {
+ case InitialUserInfoResponseAction.SWITCH:
+ resultData = new Bundle();
+ resultData.putInt(BUNDLE_USER_ID, resp.userToSwitchOrCreate.userId);
+ break;
+ case InitialUserInfoResponseAction.CREATE:
+ resultData = new Bundle();
+ resultData.putInt(BUNDLE_USER_FLAGS, resp.userToSwitchOrCreate.flags);
+ resultData.putString(BUNDLE_USER_NAME, resp.userNameToCreate);
+ break;
+ case InitialUserInfoResponseAction.DEFAULT:
+ // do nothing
+ break;
+ default:
+ // That's ok, it will be the same as DEFAULT...
+ Log.w(TAG_USER, "invalid response action on " + resp);
+ }
+ }
+ receiver.send(status, resultData);
+ } catch (RemoteException e) {
+ Log.w(TAG_USER, "Could not send result back to receiver", e);
+ }
+ });
+ }
+
/** Returns whether the given user is a system user. */
private static boolean isSystemUser(@UserIdInt int userId) {
return userId == UserHandle.USER_SYSTEM;
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 0e36211..3124a60 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
@@ -22,6 +22,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -29,6 +30,8 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -46,18 +49,28 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
+import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.UserFlags;
+import android.hardware.automotive.vehicle.V2_0.UsersInfo;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.Log;
import android.util.SparseArray;
+import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import com.android.car.hal.UserHalService;
+import com.android.car.hal.UserHalService.HalCallback;
import com.android.internal.R;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.Preconditions;
import org.junit.After;
import org.junit.Before;
@@ -73,6 +86,9 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
/**
* This class contains unit tests for the {@link CarUserService}.
*
@@ -87,6 +103,8 @@
*/
@RunWith(MockitoJUnitRunner.class)
public class CarUserServiceTest {
+
+ private static final String TAG = CarUserServiceTest.class.getSimpleName();
private static final int NO_USER_INFO_FLAGS = 0;
@Mock private Context mMockContext;
@@ -104,6 +122,24 @@
private boolean mUser0TaskExecuted;
private FakeCarOccupantZoneService mFakeCarOccupantZoneService;
+ private final int mGetUserInfoRequestType = InitialUserInfoRequestType.COLD_BOOT;
+ private final int mAsyncCallTimeoutMs = 100;
+ private final BlockingResultReceiver mReceiver =
+ new BlockingResultReceiver(mAsyncCallTimeoutMs);
+ private final InitialUserInfoResponse mGetUserInfoResponse = new InitialUserInfoResponse();
+
+ private final @NonNull UserInfo mSystemUser = UserInfoBuilder.newSystemUserInfo();
+ private final @NonNull UserInfo mAdminUser = new UserInfoBuilder(10)
+ .setAdmin(true)
+ .build();
+ private final @NonNull UserInfo mGuestUser = new UserInfoBuilder(11)
+ .setGuest(true)
+ .setEphemeral(true)
+ .build();
+ private final UserInfo[] mExistingUsers = new UserInfo[] {
+ mSystemUser, mAdminUser, mGuestUser
+ };
+
/**
* Initialize all of the objects with the @Mock annotation.
*/
@@ -542,6 +578,146 @@
}
}
+ @Test
+ public void testGetUserInfo_defaultResponse() throws Exception {
+ int currentUserId = mAdminUser.id;
+
+ mGetUserInfoResponse.action = InitialUserInfoResponseAction.DEFAULT;
+ mockGetInitialInfo(currentUserId, mGetUserInfoResponse);
+
+ mCarUserService.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs,
+ mExistingUsers, currentUserId, mReceiver);
+
+ assertThat(mReceiver.getResultCode()).isEqualTo(HalCallback.STATUS_OK);
+ assertThat(mReceiver.getResultData()).isNull();
+ }
+
+ @Test
+ public void testGetUserInfo_switchUserResponse() throws Exception {
+ int currentUserId = mAdminUser.id;
+ int switchUserId = mGuestUser.id;
+
+ mGetUserInfoResponse.action = InitialUserInfoResponseAction.SWITCH;
+ mGetUserInfoResponse.userToSwitchOrCreate.userId = switchUserId;
+ mockGetInitialInfo(currentUserId, mGetUserInfoResponse);
+
+ mCarUserService.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs,
+ mExistingUsers, currentUserId, mReceiver);
+
+ assertThat(mReceiver.getResultCode()).isEqualTo(HalCallback.STATUS_OK);
+ Bundle resultData = mReceiver.getResultData();
+ assertThat(resultData).isNotNull();
+ assertUserId(resultData, switchUserId);
+ assertNoUserFlags(resultData);
+ assertNoUserName(resultData);
+ }
+
+ @Test
+ public void testGetUserInfo_createUserResponse() throws Exception {
+ int currentUserId = mAdminUser.id;
+ int newUserFlags = 42;
+ String newUserName = "TheDude";
+
+ mGetUserInfoResponse.action = InitialUserInfoResponseAction.CREATE;
+ mGetUserInfoResponse.userToSwitchOrCreate.flags = newUserFlags;
+ mGetUserInfoResponse.userNameToCreate = newUserName;
+ mockGetInitialInfo(currentUserId, mGetUserInfoResponse);
+
+ mCarUserService.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs,
+ mExistingUsers, currentUserId, mReceiver);
+
+ assertThat(mReceiver.getResultCode()).isEqualTo(HalCallback.STATUS_OK);
+ Bundle resultData = mReceiver.getResultData();
+ assertThat(resultData).isNotNull();
+ assertNoUserId(resultData);
+ assertUserFlags(resultData, newUserFlags);
+ assertUserName(resultData, newUserName);
+ }
+
+ private void mockGetInitialInfo(@UserIdInt int currentUserId,
+ @NonNull InitialUserInfoResponse response) {
+ UsersInfo usersInfo = newUsersInfo(currentUserId);
+ doAnswer((invocation) -> {
+ Log.d(TAG, "Answering " + invocation + " with " + response);
+ @SuppressWarnings("unchecked")
+ HalCallback<InitialUserInfoResponse> callback =
+ (HalCallback<InitialUserInfoResponse>) invocation.getArguments()[3];
+ callback.onResponse(HalCallback.STATUS_OK, response);
+ return null;
+ }).when(mUserHal).getInitialUserInfo(eq(mGetUserInfoRequestType), eq(mAsyncCallTimeoutMs),
+ eq(usersInfo), notNull());
+ }
+
+ @NonNull
+ private UsersInfo newUsersInfo(@UserIdInt int currentUserId) {
+ UsersInfo infos = new UsersInfo();
+ infos.numberUsers = mExistingUsers.length;
+ boolean foundCurrentUser = false;
+ for (UserInfo info : mExistingUsers) {
+ android.hardware.automotive.vehicle.V2_0.UserInfo existingUser =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ int flags = UserFlags.NONE;
+ if (info.id == UserHandle.USER_SYSTEM) {
+ flags |= UserFlags.SYSTEM;
+ }
+ if (info.isAdmin()) {
+ flags |= UserFlags.ADMIN;
+ }
+ if (info.isGuest()) {
+ flags |= UserFlags.GUEST;
+ }
+ if (info.isEphemeral()) {
+ flags |= UserFlags.EPHEMERAL;
+ }
+ existingUser.userId = info.id;
+ existingUser.flags = flags;
+ if (info.id == currentUserId) {
+ foundCurrentUser = true;
+ infos.currentUser.userId = info.id;
+ infos.currentUser.flags = flags;
+ }
+ infos.existingUsers.add(existingUser);
+ }
+ Preconditions.checkArgument(foundCurrentUser,
+ "no user with id " + currentUserId + " on " + mExistingUsers);
+ return infos;
+ }
+
+ private void assertUserId(@NonNull Bundle resultData, int expectedUserId) {
+ int actualUserId = resultData.getInt(CarUserService.BUNDLE_USER_ID);
+ assertWithMessage("wrong user id on bundle extra %s", CarUserService.BUNDLE_USER_ID)
+ .that(actualUserId).isEqualTo(expectedUserId);
+ }
+
+ private void assertNoUserId(@NonNull Bundle resultData) {
+ assertNoExtra(resultData, CarUserService.BUNDLE_USER_ID);
+ }
+
+ private void assertUserFlags(@NonNull Bundle resultData, int expectedUserFlags) {
+ int actualUserFlags = resultData.getInt(CarUserService.BUNDLE_USER_FLAGS);
+ assertWithMessage("wrong user flags on bundle extra %s", CarUserService.BUNDLE_USER_FLAGS)
+ .that(actualUserFlags).isEqualTo(expectedUserFlags);
+ }
+
+ private void assertNoUserFlags(@NonNull Bundle resultData) {
+ assertNoExtra(resultData, CarUserService.BUNDLE_USER_FLAGS);
+ }
+
+ private void assertUserName(@NonNull Bundle resultData, @NonNull String expectedName) {
+ String actualName = resultData.getString(CarUserService.BUNDLE_USER_NAME);
+ assertWithMessage("wrong user name on bundle extra %s",
+ CarUserService.BUNDLE_USER_FLAGS).that(actualName).isEqualTo(expectedName);
+ }
+
+ private void assertNoUserName(@NonNull Bundle resultData) {
+ assertNoExtra(resultData, CarUserService.BUNDLE_USER_NAME);
+ }
+
+ private void assertNoExtra(@NonNull Bundle resultData, @NonNull String extra) {
+ Object value = resultData.get(extra);
+ assertWithMessage("should not have extra %s", extra).that(value).isNull();
+ }
+
static final class FakeCarOccupantZoneService {
private final SparseArray<Integer> mZoneUserMap = new SparseArray<Integer>();
private final CarUserService.ZoneUserBindingHelper mZoneUserBindigHelper =
@@ -585,11 +761,6 @@
}
- private void putSettingsInt(String key, int value) {
- Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
- key, value);
- }
-
// TODO(b/148403316): Refactor to use common fake settings provider
private void mockSettingsGlobal() {
when(Settings.Global.putInt(any(), eq(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET),
@@ -603,9 +774,160 @@
);
}
+ private void putSettingsInt(String key, int value) {
+ Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
+ key, value);
+ }
+
private int getSettingsInt(String key) {
return Settings.Global.getInt(
InstrumentationRegistry.getTargetContext().getContentResolver(),
key, /* default= */ 0);
}
+
+ // TODO(b/149099817): move stuff below to common code
+
+ /**
+ * Builder for {@link UserInfo} objects.
+ *
+ */
+ public static final class UserInfoBuilder {
+
+ @UserIdInt
+ private final int mUserId;
+
+ @Nullable
+ private String mName;
+
+ private boolean mGuest;
+ private boolean mEphemeral;
+ private boolean mAdmin;
+
+ /**
+ * Default constructor.
+ */
+ public UserInfoBuilder(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * Sets the user name.
+ */
+ @NonNull
+ public UserInfoBuilder setName(@Nullable String name) {
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Sets whether the user is a guest.
+ */
+ @NonNull
+ public UserInfoBuilder setGuest(boolean guest) {
+ mGuest = guest;
+ return this;
+ }
+
+ /**
+ * Sets whether the user is ephemeral.
+ */
+ @NonNull
+ public UserInfoBuilder setEphemeral(boolean ephemeral) {
+ mEphemeral = ephemeral;
+ return this;
+ }
+
+ /**
+ * Sets whether the user is an admin.
+ */
+ @NonNull
+ public UserInfoBuilder setAdmin(boolean admin) {
+ mAdmin = admin;
+ return this;
+ }
+
+ /**
+ * Creates a new {@link UserInfo}.
+ */
+ @NonNull
+ public UserInfo build() {
+ int flags = 0;
+ if (mEphemeral) {
+ flags |= UserInfo.FLAG_EPHEMERAL;
+ }
+ if (mAdmin) {
+ flags |= UserInfo.FLAG_ADMIN;
+ }
+ UserInfo info = new UserInfo(mUserId, mName, flags);
+ if (mGuest) {
+ info.userType = UserManager.USER_TYPE_FULL_GUEST;
+ }
+ return info;
+ }
+
+ /**
+ * Creates a new {@link UserInfo} for a system user.
+ */
+ @NonNull
+ public static UserInfo newSystemUserInfo() {
+ UserInfo info = new UserInfo();
+ info.id = UserHandle.USER_SYSTEM;
+ return info;
+ }
+ }
+
+ /**
+ * Implementation of {@link IResultReceiver} that blocks waiting for the result.
+ */
+ public static final class BlockingResultReceiver extends IResultReceiver.Stub {
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final long mTimeoutMs;
+
+ private int mResultCode;
+ @Nullable private Bundle mResultData;
+
+ /**
+ * Default constructor.
+ *
+ * @param timeoutMs how long to wait for before failing.
+ */
+ public BlockingResultReceiver(long timeoutMs) {
+ mTimeoutMs = timeoutMs;
+ }
+
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ Log.d(TAG, "send() received: code=" + resultCode + ", data=" + resultData + ", count="
+ + mLatch.getCount());
+ Preconditions.checkState(mLatch.getCount() == 1,
+ "send() already called (code=" + mResultCode + ", data=" + mResultData);
+ mResultCode = resultCode;
+ mResultData = resultData;
+ mLatch.countDown();
+ }
+
+ private void assertCalled() throws InterruptedException {
+ boolean called = mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
+ Log.d(TAG, "assertCalled(): " + called);
+ assertWithMessage("receiver not called in %sms", mTimeoutMs).that(called).isTrue();
+ }
+
+ /**
+ * Gets the {@code resultCode} or fails if it times out before {@code send()} is called.
+ */
+ public int getResultCode() throws InterruptedException {
+ assertCalled();
+ return mResultCode;
+ }
+
+ /**
+ * Gets the {@code resultData} or fails if it times out before {@code send()} is called.
+ */
+ @Nullable
+ public Bundle getResultData() throws InterruptedException {
+ assertCalled();
+ return mResultData;
+ }
+ }
}