Added API to manage uids in each zone

Added API to set, get zones for each uid. Also added API to
request zone ids.

Bug: 121344631
Test: Though the api is not currently used I build and ran emulator and
everything continues to work as expected.

Change-Id: Iec2ffc7fa78936692ca32a9c8a2be02b0454b03e
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index 6c2e1cd..c3338a9 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -412,6 +412,71 @@
     }
 
     /**
+     * Gets the audio zones currently available
+     *
+     * @return audio zone ids
+     * @hide
+     */
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    public @NonNull int[] getAudioZoneIds() {
+        try {
+            return mService.getAudioZoneIds();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the audio zone id currently mapped to uId,
+     * defaults to PRIMARY_AUDIO_ZONE if no mapping exist
+     *
+     * @param uid The uid to map
+     * @return zone id mapped to uid
+     * @hide
+     */
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    public int getZoneIdForUid(int uid) {
+        try {
+            return mService.getZoneIdForUid(uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Maps the audio zone id to uid
+     *
+     * @param zoneId The audio zone id
+     * @param uid The uid to map
+     * @return true if the uid is successfully mapped
+     * @hide
+     */
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    public boolean setZoneIdForUid(int zoneId, int uid) {
+        try {
+            return mService.setZoneIdForUid(zoneId, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Clears the current zone mapping of the uid
+     *
+     * @param uid The uid to clear
+     * @return true if the zone was successfully cleared
+     * @hide
+     */
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    public boolean clearZoneIdForUid(int uid) {
+        try {
+            return mService.clearZoneIdForUid(uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets array of {@link AudioAttributes} usages for a volume group in a zone.
      *
      * @param zoneId The zone id whose volume group is queried.
diff --git a/car-lib/src/android/car/media/ICarAudio.aidl b/car-lib/src/android/car/media/ICarAudio.aidl
index 178f75a..a2f33ad 100644
--- a/car-lib/src/android/car/media/ICarAudio.aidl
+++ b/car-lib/src/android/car/media/ICarAudio.aidl
@@ -43,6 +43,11 @@
     int getVolumeGroupIdForUsage(int zoneId, int usage);
     int[] getUsagesForVolumeGroupId(int zoneId, int groupId);
 
+    int[] getAudioZoneIds();
+    int getZoneIdForUid(int uid);
+    boolean setZoneIdForUid(int zoneId, int uid);
+    boolean clearZoneIdForUid(int uid);
+
     /**
      * IBinder is ICarVolumeCallback but passed as IBinder due to aidl hidden.
      */
diff --git a/service/src/com/android/car/audio/CarAudioFocus.java b/service/src/com/android/car/audio/CarAudioFocus.java
index 45c2903..c2e0a1b 100644
--- a/service/src/com/android/car/audio/CarAudioFocus.java
+++ b/service/src/com/android/car/audio/CarAudioFocus.java
@@ -490,17 +490,22 @@
         }
     }
 
-    public synchronized void dump(PrintWriter writer) {
-        writer.println("*CarAudioFocus*");
+    /**
+     * dumps the current state of the CarAudioFocus object
+     * @param indent indent to add to each line in the current stream
+     * @param writer stream to write to
+     */
+    public synchronized void dump(String indent, PrintWriter writer) {
+        writer.printf("%s*CarAudioFocus*\n", indent);
 
-        writer.println("  Current Focus Holders:");
+        writer.printf("%s\tCurrent Focus Holders:\n", indent);
         for (String clientId : mFocusHolders.keySet()) {
-            System.out.println(clientId);
+            writer.printf("%s\t\t%s\n", indent, clientId);
         }
 
-        writer.println("  Transient Focus Losers:");
+        writer.printf("%s\tTransient Focus Losers:\n", indent);
         for (String clientId : mFocusLosers.keySet()) {
-            System.out.println(clientId);
+            writer.printf("%s\t\t%s\n", indent, clientId);
         }
     }
 
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 88d3463..8cde85b 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -65,7 +65,9 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -191,6 +193,10 @@
     private String mCarAudioConfigurationPath;
     private CarAudioZone[] mCarAudioZones;
 
+    // TODO do not store uid mapping here instead use the uid
+    //  device affinity in audio policy when available
+    private Map<Integer, Integer> mUidToZoneMap;
+
     public CarAudioService(Context context) {
         mContext = context;
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
@@ -198,6 +204,7 @@
         mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
         mPersistMasterMuteState = mContext.getResources().getBoolean(
                 R.bool.audioPersistMasterMuteState);
+        mUidToZoneMap = new HashMap<>();
     }
 
     /**
@@ -277,7 +284,18 @@
             for (CarAudioZone zone : mCarAudioZones) {
                 zone.dump("\t", writer);
             }
+            writer.println();
+            writer.println("\tUID to Zone Mapping:");
+            for (int callingId : mUidToZoneMap.keySet()) {
+                writer.printf("\t\tUID %d mapped to zone %d\n",
+                        callingId,
+                        mUidToZoneMap.get(callingId));
+            }
+            //Print focus handler info
+            writer.println();
+            mFocusHandler.dump("\t", writer);
         }
+
     }
 
     @Override
@@ -736,6 +754,127 @@
         }
     }
 
+    /**
+     * Gets the ids of all available audio zones
+     *
+     * @return Array of available audio zones ids
+     */
+    @Override
+    public @NonNull int[] getAudioZoneIds() {
+        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
+        synchronized (mImplLock) {
+            return Arrays.stream(mCarAudioZones).mapToInt(CarAudioZone::getId).toArray();
+        }
+    }
+
+    /**
+     * Gets the audio zone id currently mapped to uid,
+     * defaults to PRIMARY_AUDIO_ZONE if no mapping exist
+     *
+     * @param uid The uid
+     * @return zone id mapped to uid
+     */
+    @Override
+    public int getZoneIdForUid(int uid) {
+        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
+        synchronized (mImplLock) {
+            if (!mUidToZoneMap.containsKey(uid)) {
+                Log.i(CarLog.TAG_AUDIO, "getZoneIdForUid uid "
+                        + uid + " does not have a zone. Defaulting to PRIMARY_AUDIO_ZONE: "
+                        + CarAudioManager.PRIMARY_AUDIO_ZONE);
+
+                //Must be added to PRIMARY_AUDIO_ZONE otherwise audio may be routed to other devices
+                // that match the audio criterion (i.e. usage)
+                setZoneIdForUidNoCheckLocked(CarAudioManager.PRIMARY_AUDIO_ZONE, uid);
+            }
+
+            return mUidToZoneMap.get(uid);
+        }
+    }
+
+    /**
+     * Maps the audio zone id to uid
+     *
+     * @param zoneId The audio zone id
+     * @param uid The uid to map
+     * @return true if the device affinities, for devices in zone, are successfully set
+     */
+    @Override
+    public boolean setZoneIdForUid(int zoneId, int uid) {
+        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
+        synchronized (mImplLock) {
+            Log.i(CarLog.TAG_AUDIO, "setZoneIdForUid Calling uid "
+                    + uid + " mapped to : "
+                    + zoneId);
+            //if the current uid is in the list
+            //remove it from the list
+            if (checkAndRemoveUidLocked(uid)) {
+                return setZoneIdForUidNoCheckLocked(zoneId, uid);
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Removes the current mapping of the Uid
+     *
+     * @param uid The uid to remove
+     * return true if all the devices affinities currently
+     *            mapped to uid are successfully removed
+     */
+    @Override
+    public boolean clearZoneIdForUid(int uid) {
+        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
+        synchronized (mImplLock) {
+            return checkAndRemoveUidLocked(uid);
+        }
+    }
+
+    /**
+     * Sets the zone id for uid
+     * @param zoneId zone id to map to uid
+     * @param uid uid to map
+     * @return true if setting uid device affinity is successful
+     */
+    private boolean setZoneIdForUidNoCheckLocked(int zoneId, int uid) {
+        Log.d(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Calling uid "
+                + uid + " mapped to " + zoneId);
+        //Request to add uid device affinity
+        if (mAudioPolicy.setUidDeviceAffinity(uid, mCarAudioZones[zoneId].getAudioDeviceInfos())) {
+            // TODO do not store uid mapping here instead use the uid
+            //  device affinity in audio policy when available
+            mUidToZoneMap.put(uid, zoneId);
+            return true;
+        }
+        Log.w(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Failed set device affinity for uid "
+                + uid + " in zone " + zoneId);
+        return false;
+    }
+
+    /**
+     * Checks if the uid is contained in map and removes it if so
+     * @param uid uid to remove from mapping
+     * @return true if the removing the uid mapping succeeds
+     */
+    private boolean checkAndRemoveUidLocked(int uid) {
+        if (mUidToZoneMap.containsKey(uid)) {
+            int zoneId = mUidToZoneMap.get(uid);
+            Log.i(CarLog.TAG_AUDIO, "checkAndRemoveUid removing Calling uid "
+                    + uid + " from zone " + zoneId);
+            if (mAudioPolicy.removeUidDeviceAffinity(uid)) {
+                // TODO use the uid device affinity in audio policy when available
+                mUidToZoneMap.remove(uid);
+                return true;
+            }
+            //failed to remove device affinity from zone devices
+            Log.w(CarLog.TAG_AUDIO,
+                    "checkAndRemoveUid Failed remove device affinity for uid "
+                            + uid + " in zone " +  zoneId);
+            return false;
+        }
+        return true;
+    }
+
     @Override
     public void registerVolumeCallback(@NonNull IBinder binder) {
         synchronized (mImplLock) {
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
index 36bbf42..4bdf0b7 100644
--- a/service/src/com/android/car/audio/CarAudioZone.java
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -16,6 +16,7 @@
 package com.android.car.audio;
 
 import android.car.media.CarAudioManager;
+import android.media.AudioDeviceInfo;
 import android.util.Log;
 import android.view.DisplayAddress;
 
@@ -74,6 +75,19 @@
         return mVolumeGroups.get(groupId);
     }
 
+    /**
+     * @return Snapshot of available {@link AudioDeviceInfo}s in List.
+     */
+    List<AudioDeviceInfo> getAudioDeviceInfos() {
+        final List<AudioDeviceInfo> devices = new ArrayList<>();
+        for (CarVolumeGroup group : mVolumeGroups) {
+            for (int busNumber : group.getBusNumbers()) {
+                devices.add(group.getCarAudioDeviceInfoForBus(busNumber).getAudioDeviceInfo());
+            }
+        }
+        return devices;
+    }
+
     int getVolumeGroupCount() {
         return mVolumeGroups.size();
     }