Merge "Added audio zone info to occupant zone service." into rvc-dev
diff --git a/car-lib/src/android/car/ICarOccupantZone.aidl b/car-lib/src/android/car/ICarOccupantZone.aidl
index 904bc84..09335a7 100644
--- a/car-lib/src/android/car/ICarOccupantZone.aidl
+++ b/car-lib/src/android/car/ICarOccupantZone.aidl
@@ -30,7 +30,6 @@
     int getDisplayType(in int displayId);
     int getUserForOccupant(in int occupantZoneId);
     int getOccupantZoneIdForUserId(in int userId);
-    void setAudioZoneIdsForOccupantZoneIds(in int[] audioZoneIds, in int[] occupantZoneIds);
     void registerCallback(in ICarOccupantZoneCallback callback);
     void unregisterCallback(in ICarOccupantZoneCallback callback);
 }
diff --git a/service/src/com/android/car/CarOccupantZoneService.java b/service/src/com/android/car/CarOccupantZoneService.java
index 9fd84fb..dae1667 100644
--- a/service/src/com/android/car/CarOccupantZoneService.java
+++ b/service/src/com/android/car/CarOccupantZoneService.java
@@ -39,13 +39,13 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.DisplayAddress;
 
 import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -61,6 +61,8 @@
 public final class CarOccupantZoneService extends ICarOccupantZone.Stub
         implements CarServiceBase {
 
+    private static final int INVALID_OCCUPANT_ZONE_ID = -1;
+
     private final Object mLock = new Object();
     private final Context mContext;
     private final DisplayManager mDisplayManager;
@@ -98,7 +100,7 @@
 
     /** key: audio zone id */
     @GuardedBy("mLock")
-    private final HashMap<Integer, Integer> mAudioZoneConfig = new HashMap<>();
+    private final SparseIntArray mAudioZoneIdToOccupantZoneIdMapping = new SparseIntArray();
 
     @VisibleForTesting
     static class DisplayInfo {
@@ -314,7 +316,7 @@
         synchronized (mLock) {
             mOccupantsConfig.clear();
             mDisplayConfigs.clear();
-            mAudioZoneConfig.clear();
+            mAudioZoneIdToOccupantZoneIdMapping.clear();
             mActiveOccupantConfigs.clear();
         }
     }
@@ -340,9 +342,9 @@
     /** Return cloned mAudioConfigs for testing */
     @VisibleForTesting
     @NonNull
-    HashMap<Integer, Integer> getAudioConfigs() {
+    SparseIntArray getAudioConfigs() {
         synchronized (mLock) {
-            return (HashMap<Integer, Integer>) mAudioZoneConfig.clone();
+            return mAudioZoneIdToOccupantZoneIdMapping.clone();
         }
     }
 
@@ -369,10 +371,11 @@
                 writer.println(" port=" + Integer.toHexString(entry.getKey())
                         + " config=" + entry.getValue().toString());
             }
-            writer.println("**mAudioZoneConfigs**");
-            for (Map.Entry<Integer, Integer> entry : mAudioZoneConfig.entrySet()) {
-                writer.println(" audioZoneId=" + Integer.toHexString(entry.getKey())
-                        + " zoneId=" + entry.getValue());
+            writer.println("**mAudioZoneIdToOccupantZoneIdMapping**");
+            for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
+                int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
+                writer.println(" audioZoneId=" + Integer.toHexString(audioZoneId)
+                        + " zoneId=" + mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId));
             }
             writer.println("**mActiveOccupantConfigs**");
             for (Map.Entry<Integer, OccupantConfig> entry : mActiveOccupantConfigs.entrySet()) {
@@ -445,9 +448,10 @@
     }
 
     private int getAudioZoneIdForOccupantLocked(int occupantZoneId) {
-        for (Map.Entry<Integer, Integer> entry : mAudioZoneConfig.entrySet()) {
-            if (occupantZoneId == entry.getValue()) {
-                return entry.getKey();
+        for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
+            int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
+            if (occupantZoneId == mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId)) {
+                return audioZoneId;
             }
         }
         return CarAudioManager.INVALID_AUDIO_ZONE;
@@ -457,8 +461,9 @@
     public CarOccupantZoneManager.OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) {
         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
         synchronized (mLock) {
-            Integer occupantZoneId = mAudioZoneConfig.get(audioZoneId);
-            if (occupantZoneId == null) {
+            int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId,
+                    INVALID_OCCUPANT_ZONE_ID);
+            if (occupantZoneId == INVALID_OCCUPANT_ZONE_ID) {
                 return null;
             }
             // To support headless zones return the occupant configuration.
@@ -521,19 +526,23 @@
         }
     }
 
-    @Override
-    public void setAudioZoneIdsForOccupantZoneIds(@NonNull int[] audioZoneIds,
-            @NonNull int[] occupantZoneIds) {
-        Objects.requireNonNull(audioZoneIds, "audioZoneIds can not be null");
-        Objects.requireNonNull(audioZoneIds, "occupantZoneIds can not be null");
-        Preconditions.checkArgument(audioZoneIds.length == occupantZoneIds.length,
-                "audioZoneIds and occupantZoneIds must have the same size.");
-        boolean activeConfigChange = false;
+    /**
+     * Sets the mapping for audio zone id to occupant zone id.
+     *
+     * @param audioZoneIdToOccupantZoneMapping map for audio zone id, where key is the audio zone id
+     * and value is the occupant zone id.
+     */
+    public void setAudioZoneIdsForOccupantZoneIds(
+            @NonNull SparseIntArray audioZoneIdToOccupantZoneMapping) {
+        Objects.requireNonNull(audioZoneIdToOccupantZoneMapping,
+                "audioZoneIdToOccupantZoneMapping can not be null");
         synchronized (mLock) {
-            validateOccupantZoneIdsLocked(occupantZoneIds);
-            mAudioZoneConfig.clear();
-            for (int i = 0; i < audioZoneIds.length; i++) {
-                mAudioZoneConfig.put(audioZoneIds[i], occupantZoneIds[i]);
+            validateOccupantZoneIdsLocked(audioZoneIdToOccupantZoneMapping);
+            mAudioZoneIdToOccupantZoneIdMapping.clear();
+            for (int index = 0; index < audioZoneIdToOccupantZoneMapping.size(); index++) {
+                int audioZoneId = audioZoneIdToOccupantZoneMapping.keyAt(index);
+                mAudioZoneIdToOccupantZoneIdMapping.put(audioZoneId,
+                        audioZoneIdToOccupantZoneMapping.get(audioZoneId));
             }
             //If there are any active displays for the zone send change event
             handleAudioZoneChangesLocked();
@@ -541,10 +550,12 @@
         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_AUDIO);
     }
 
-    private void validateOccupantZoneIdsLocked(int[] occupantZoneIds) {
-        for (int i = 0; i < occupantZoneIds.length; i++) {
-            if (!mOccupantsConfig.containsKey(occupantZoneIds[i])) {
-                throw new IllegalArgumentException("occupantZoneId " + occupantZoneIds[i]
+    private void validateOccupantZoneIdsLocked(SparseIntArray audioZoneIdToOccupantZoneMapping) {
+        for (int i = 0; i < audioZoneIdToOccupantZoneMapping.size(); i++) {
+            int occupantZoneId =
+                    audioZoneIdToOccupantZoneMapping.get(audioZoneIdToOccupantZoneMapping.keyAt(i));
+            if (!mOccupantsConfig.containsKey(occupantZoneId)) {
+                throw new IllegalArgumentException("occupantZoneId " + occupantZoneId
                         + " does not exist.");
             }
         }
@@ -813,8 +824,9 @@
     }
 
     private void handleAudioZoneChangesLocked() {
-        for (Map.Entry<Integer, Integer> entry: mAudioZoneConfig.entrySet()) {
-            int occupantZoneId = entry.getValue();
+        for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
+            int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
+            int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId);
             OccupantConfig occupantConfig =
                     mActiveOccupantConfigs.get(occupantZoneId);
             if (occupantConfig == null) {
@@ -822,7 +834,7 @@
                 continue;
             }
             // Found an active configuration, add audio to it.
-            occupantConfig.audioZoneId = entry.getKey();
+            occupantConfig.audioZoneId = audioZoneId;
         }
     }
 
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 1923d9a..0926776 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -260,6 +260,7 @@
         CarLocalServices.addService(PerUserCarServiceHelper.class, mPerUserCarServiceHelper);
         CarLocalServices.addService(FixedActivityService.class, mFixedActivityService);
         CarLocalServices.addService(VmsNewBrokerService.class, mVmsBrokerService);
+        CarLocalServices.addService(CarOccupantZoneService.class, mCarOccupantZoneService);
 
         // Be careful with order. Service depending on other service should be inited later.
         List<CarServiceBase> allServices = new ArrayList<>();
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 63cda19..b0c80a1 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -51,11 +51,13 @@
 import android.telephony.TelephonyManager;
 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;
 import com.android.car.CarLog;
+import com.android.car.CarOccupantZoneService;
 import com.android.car.CarServiceBase;
 import com.android.car.R;
 import com.android.car.audio.CarAudioContext.AudioContext;
@@ -122,6 +124,8 @@
 
     private final CarUserService.UserCallback  mReceiver = new CarAudioServiceUserCallback();
 
+    private CarOccupantZoneService mOccupantZoneService;
+
     private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
             new AudioPolicy.AudioPolicyVolumeCallback() {
         @Override
@@ -191,6 +195,7 @@
     private AudioPolicy mAudioPolicy;
     private CarZonesAudioFocus mFocusHandler;
     private String mCarAudioConfigurationPath;
+    private SparseIntArray mAudioZoneIdToOccupantZoneIdMapping;
     private CarAudioZone[] mCarAudioZones;
     private final CarVolumeCallbackHandler mCarVolumeCallbackHandler;
 
@@ -218,6 +223,7 @@
     public void init() {
         synchronized (mImplLock) {
             CarLocalServices.getService(CarUserService.class).addUserCallback(mReceiver);
+            mOccupantZoneService = CarLocalServices.getService(CarOccupantZoneService.class);
             if (mUseDynamicRouting) {
                 setupDynamicRouting();
             } else {
@@ -422,6 +428,8 @@
         try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
             CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mContext, inputStream,
                     carAudioDeviceInfos, inputDevices);
+            mAudioZoneIdToOccupantZoneIdMapping =
+                    zonesHelper.getCarAudioZoneIdToOccupantZoneIdMapping();
             return zonesHelper.loadAudioZones();
         } catch (IOException | XmlPullParserException e) {
             throw new RuntimeException("Failed to parse audio zone configuration", e);
@@ -494,6 +502,12 @@
         if (r != AudioManager.SUCCESS) {
             throw new RuntimeException("registerAudioPolicy failed " + r);
         }
+
+        setupOccupantZoneInfo();
+    }
+
+    private void setupOccupantZoneInfo() {
+        mOccupantZoneService.setAudioZoneIdsForOccupantZoneIds(mAudioZoneIdToOccupantZoneIdMapping);
     }
 
     /**
diff --git a/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
index 691bdf4..2db5555 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
@@ -24,17 +24,20 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
 
 import android.app.ActivityManager;
 import android.car.Car;
 import android.car.CarOccupantZoneManager;
 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
 import android.car.VehicleAreaSeat;
+import android.car.media.CarAudioManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.display.DisplayManager;
 import android.os.Looper;
 import android.os.UserHandle;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.DisplayAddress;
 
@@ -104,6 +107,13 @@
             "occupantZoneId=3,occupantType=REAR_PASSENGER,seatRow=2,seatSide=right"
     };
 
+    private static final int PRIMARY_AUDIO_ZONE_ID = 0;
+    private static final int PRIMARY_AUDIO_ZONE_ID_OCCUPANT = 0;
+    private static final int SECONDARY_AUDIO_ZONE_ID = 1;
+    private static final int SECONDARY_AUDIO_ZONE_ID_OCCUPANT = 3;
+    private static final int UNMAPPED_AUDIO_ZONE_ID_OCCUPANT = 2;
+    private static final int INVALID_AUDIO_ZONE_ID_OCCUPANT = 100;
+
     // LHD : Left Hand Drive
     private final OccupantZoneInfo mZoneDriverLHD = new OccupantZoneInfo(0,
             CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER,
@@ -223,6 +233,13 @@
         assertThat(mZoneRearRight).isEqualTo(configs.get(3));
     }
 
+    @Test
+    public void testDefaultAudioZoneConfig() {
+        mService.init();
+        SparseIntArray audioConfigs = mService.getAudioConfigs();
+        assertThat(audioConfigs.size()).isEqualTo(0);
+    }
+
     /** RHD: Right Hand Drive */
     @Test
     public void testDefaultOccupantConfigForRHD() {
@@ -283,6 +300,84 @@
     }
 
     @Test
+    public void testSetAudioConfigMapping() {
+        mService.init();
+
+        SparseIntArray audioZoneIdToOccupantZoneMapping =
+                getDefaultAudioZoneToOccupantZoneMapping();
+
+        mService.setAudioZoneIdsForOccupantZoneIds(audioZoneIdToOccupantZoneMapping);
+
+        assertThat(mService.getAudioZoneIdForOccupant(PRIMARY_AUDIO_ZONE_ID_OCCUPANT))
+                .isEqualTo(PRIMARY_AUDIO_ZONE_ID);
+
+        assertThat(mService.getAudioZoneIdForOccupant(SECONDARY_AUDIO_ZONE_ID_OCCUPANT))
+                .isEqualTo(SECONDARY_AUDIO_ZONE_ID);
+    }
+
+    private SparseIntArray getDefaultAudioZoneToOccupantZoneMapping() {
+        SparseIntArray audioZoneIdToOccupantZoneMapping = new SparseIntArray(2);
+        audioZoneIdToOccupantZoneMapping.put(PRIMARY_AUDIO_ZONE_ID,
+                PRIMARY_AUDIO_ZONE_ID_OCCUPANT);
+        audioZoneIdToOccupantZoneMapping.put(SECONDARY_AUDIO_ZONE_ID,
+                SECONDARY_AUDIO_ZONE_ID_OCCUPANT);
+        return audioZoneIdToOccupantZoneMapping;
+    }
+
+    @Test
+    public void testOccupantZoneConfigInfoForAudio() {
+        mService.init();
+        SparseIntArray audioZoneIdToOccupantZoneMapping =
+                getDefaultAudioZoneToOccupantZoneMapping();
+
+        HashMap<Integer, CarOccupantZoneManager.OccupantZoneInfo> occupantZoneConfigs =
+                mService.getOccupantsConfig();
+
+        mService.setAudioZoneIdsForOccupantZoneIds(audioZoneIdToOccupantZoneMapping);
+
+        CarOccupantZoneManager.OccupantZoneInfo primaryOccupantInfo =
+                mService.getOccupantForAudioZoneId(PRIMARY_AUDIO_ZONE_ID);
+        assertThat(primaryOccupantInfo).isEqualTo(
+                occupantZoneConfigs.get(PRIMARY_AUDIO_ZONE_ID_OCCUPANT));
+
+        CarOccupantZoneManager.OccupantZoneInfo secondaryOccupantInfo =
+                mService.getOccupantForAudioZoneId(SECONDARY_AUDIO_ZONE_ID);
+        assertThat(secondaryOccupantInfo).isEqualTo(
+                occupantZoneConfigs.get(SECONDARY_AUDIO_ZONE_ID_OCCUPANT));
+
+        CarOccupantZoneManager.OccupantZoneInfo nullOccupantInfo =
+                mService.getOccupantForAudioZoneId(UNMAPPED_AUDIO_ZONE_ID_OCCUPANT);
+        assertThat(nullOccupantInfo).isNull();
+    }
+
+    @Test
+    public void testMissingAudioConfigMapping() {
+        mService.init();
+        SparseIntArray audioZoneIdToOccupantZoneMapping =
+                getDefaultAudioZoneToOccupantZoneMapping();
+
+        mService.setAudioZoneIdsForOccupantZoneIds(audioZoneIdToOccupantZoneMapping);
+
+        assertThat(mService.getAudioZoneIdForOccupant(UNMAPPED_AUDIO_ZONE_ID_OCCUPANT))
+                .isEqualTo(CarAudioManager.INVALID_AUDIO_ZONE);
+    }
+
+    @Test
+    public void testSetInvalidAudioConfigMapping() {
+        mService.init();
+        SparseIntArray audioZoneIdToOccupantZoneMapping = new SparseIntArray(2);
+        audioZoneIdToOccupantZoneMapping.put(PRIMARY_AUDIO_ZONE_ID,
+                PRIMARY_AUDIO_ZONE_ID_OCCUPANT);
+        audioZoneIdToOccupantZoneMapping.put(SECONDARY_AUDIO_ZONE_ID,
+                INVALID_AUDIO_ZONE_ID_OCCUPANT);
+        IllegalArgumentException thrown =
+                expectThrows(IllegalArgumentException.class,
+                        () -> mService.setAudioZoneIdsForOccupantZoneIds(
+                                audioZoneIdToOccupantZoneMapping));
+        thrown.getMessage().contains("does not exist");
+    }
+
+    @Test
     public void testActiveOccupantConfigs() {
         mService.init();
 
@@ -606,4 +701,29 @@
         mService.mUserCallback.onSwitchUser(0);
         assertThat(waitForConfigChangeEventAndAssertFlag(eventWaitTimeMs, 0)).isFalse();
     }
+
+    @Test
+    public void testManagerRegisterUnregisterForAudioConfigs() {
+        mService.init();
+
+        long eventWaitTimeMs = 300;
+
+        mManager.registerOccupantZoneConfigChangeListener(mChangeListener);
+
+        resetConfigChangeEventWait();
+
+        SparseIntArray audioZoneIdToOccupantZoneMapping =
+                getDefaultAudioZoneToOccupantZoneMapping();
+
+        mService.setAudioZoneIdsForOccupantZoneIds(audioZoneIdToOccupantZoneMapping);
+
+        assertThat(waitForConfigChangeEventAndAssertFlag(eventWaitTimeMs,
+                CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_AUDIO)).isTrue();
+
+        resetConfigChangeEventWait();
+        mManager.unregisterOccupantZoneConfigChangeListener(mChangeListener);
+        mService.setAudioZoneIdsForOccupantZoneIds(audioZoneIdToOccupantZoneMapping);
+        assertThat(waitForConfigChangeEventAndAssertFlag(eventWaitTimeMs,
+                CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_AUDIO)).isFalse();
+    }
 }