Adding recovery logic for IAudioControl

When HAL dies
- AudioControlWrapper grabs a new HAL Handle
- AudioControlWrapper calls CarAudioService#resetHalAudioFocus
- resetHalAudioFocus resets HalAudioFocus and re-registers the listener

Bug: 153475563
Test: atest com.android.car.audio, also manually killed audiocontrol and
verified it recovered

Change-Id: If689af0dc7cc1c628ae750a46a2452079a093762
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 96ddc27..6d244d1 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -270,7 +270,11 @@
             if (mHalAudioFocus != null) {
                 mHalAudioFocus.unregisterFocusListener();
             }
-            mAudioControlWrapper = null;
+
+            if (mAudioControlWrapper != null) {
+                mAudioControlWrapper.unlinkToDeath();
+                mAudioControlWrapper = null;
+            }
         }
     }
 
@@ -1201,10 +1205,18 @@
     private AudioControlWrapper getAudioControlWrapperLocked() {
         if (mAudioControlWrapper == null) {
             mAudioControlWrapper = AudioControlFactory.newAudioControl();
+            mAudioControlWrapper.linkToDeath(this::resetHalAudioFocus);
         }
         return mAudioControlWrapper;
     }
 
+    private void resetHalAudioFocus() {
+        if (mHalAudioFocus != null) {
+            mHalAudioFocus.reset();
+            mHalAudioFocus.registerFocusListener();
+        }
+    }
+
     boolean isAudioZoneIdValid(int zoneId) {
         for (CarAudioZone zone : mCarAudioZones) {
             if (zone.getId() == zoneId) {
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapper.java b/service/src/com/android/car/audio/hal/AudioControlWrapper.java
index af1c02e..c491056 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapper.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapper.java
@@ -19,6 +19,8 @@
 import android.hardware.automotive.audiocontrol.V2_0.IFocusListener;
 import android.media.AudioAttributes.AttributeUsage;
 
+import androidx.annotation.Nullable;
+
 import java.io.PrintWriter;
 
 /**
@@ -75,4 +77,25 @@
      * @param value to set for the balance. Positive is towards the right.
      */
     void setBalanceTowardRight(float value);
+
+    /**
+     * Registers recipient to be notified if AudioControl HAL service dies.
+     * @param deathRecipient to be notified upon HAL service death.
+     */
+    void linkToDeath(@Nullable AudioControlDeathRecipient deathRecipient);
+
+    /**
+     * Unregisters recipient for AudioControl HAL service death.
+     */
+    void unlinkToDeath();
+
+    /**
+     * Recipient to be notified upon death of AudioControl HAL.
+     */
+    interface AudioControlDeathRecipient {
+        /**
+         * Called if AudioControl HAL dies.
+         */
+        void serviceDied();
+    }
 }
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java b/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java
index 02b11d3..433b5d5 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java
@@ -35,7 +35,9 @@
  */
 public final class AudioControlWrapperV1 implements AudioControlWrapper {
     private static final String TAG = AudioControlWrapperV1.class.getSimpleName();
+
     private IAudioControl mAudioControlV1;
+    private AudioControlDeathRecipient mDeathRecipient;
 
     /**
      * Gets IAudioControl@1.0 service if registered.
@@ -122,4 +124,35 @@
             throw new IllegalStateException("Failed to query IAudioControl#getBusForContext", e);
         }
     }
+
+    @Override
+    public void linkToDeath(@Nullable AudioControlDeathRecipient deathRecipient) {
+        try {
+            mAudioControlV1.linkToDeath(this::serviceDied, 0);
+            mDeathRecipient = deathRecipient;
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Call to IAudioControl@1.0#linkToDeath failed", e);
+        }
+    }
+
+    @Override
+    public void unlinkToDeath() {
+        try {
+            mAudioControlV1.unlinkToDeath(this::serviceDied);
+            mDeathRecipient = null;
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Call to IAudioControl@1.0#unlinkToDeath failed", e);
+        }
+    }
+
+    private void serviceDied(long cookie) {
+        Log.w(TAG, "IAudioControl@1.0 died. Fetching new handle");
+        mAudioControlV1 = AudioControlWrapperV1.getService();
+        linkToDeath(mDeathRecipient);
+        if (mDeathRecipient != null) {
+            mDeathRecipient.serviceDied();
+        }
+    }
+
+
 }
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java b/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java
index 6bf9a00..c623c15 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java
@@ -35,6 +35,7 @@
 
     private IAudioControl mAudioControlV2;
 
+    private AudioControlDeathRecipient mDeathRecipient;
     private ICloseHandle mCloseHandle;
 
     public static @Nullable IAudioControl getService() {
@@ -122,4 +123,33 @@
             Log.e(TAG, "setBalanceTowardRight failed", e);
         }
     }
+
+    @Override
+    public void linkToDeath(@Nullable AudioControlDeathRecipient deathRecipient) {
+        try {
+            mAudioControlV2.linkToDeath(this::serviceDied, 0);
+            mDeathRecipient = deathRecipient;
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Call to IAudioControl@2.0#linkToDeath failed", e);
+        }
+    }
+
+    @Override
+    public void unlinkToDeath() {
+        try {
+            mAudioControlV2.unlinkToDeath(this::serviceDied);
+            mDeathRecipient = null;
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Call to IAudioControl@2.0#unlinkToDeath failed", e);
+        }
+    }
+
+    private void serviceDied(long cookie) {
+        Log.w(TAG, "IAudioControl@2.0 died. Fetching new handle");
+        mAudioControlV2 = AudioControlWrapperV2.getService();
+        linkToDeath(mDeathRecipient);
+        if (mDeathRecipient != null) {
+            mDeathRecipient.serviceDied();
+        }
+    }
 }