Added audio zone info to occupant zone service.

Added car audio zone info to occupant zone service. This only sets the
information from car audio service. Upcomming CLs will take care of
comsuming the information accordingly. Also added more test to car
occupant zone service.

Test: atest CarOccupantZoneServiceTest
Bug: 148292262
Change-Id: I2c25dcf426ce8801e3dff4581c03d86b9e754ddb
Merged-In: I2c25dcf426ce8801e3dff4581c03d86b9e754ddb
(cherry picked from commit b0e7468ac3c7554f9c486073891e08a8baf39727)
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 af66305..3212d8f 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -259,6 +259,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 c6c98d8..1d45c8a 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -55,11 +55,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.user.CarUserService;
@@ -123,6 +125,8 @@
 
     private final CarUserService.UserCallback  mReceiver = new CarAudioServiceUserCallback();
 
+    private CarOccupantZoneService mOccupantZoneService;
+
     private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
             new AudioPolicy.AudioPolicyVolumeCallback() {
         @Override
@@ -192,6 +196,7 @@
     private AudioPolicy mAudioPolicy;
     private CarZonesAudioFocus mFocusHandler;
     private String mCarAudioConfigurationPath;
+    private SparseIntArray mAudioZoneIdToOccupantZoneIdMapping;
     private CarAudioZone[] mCarAudioZones;
     private final CarVolumeCallbackHandler mCarVolumeCallbackHandler;
 
@@ -219,6 +224,7 @@
     public void init() {
         synchronized (mImplLock) {
             CarLocalServices.getService(CarUserService.class).addUserCallback(mReceiver);
+            mOccupantZoneService = CarLocalServices.getService(CarOccupantZoneService.class);
             if (mUseDynamicRouting) {
                 setupDynamicRouting();
             } else {
@@ -423,6 +429,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);
@@ -495,6 +503,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();
+    }
 }