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;
+        }
+    }
 }