Implement client death monitoring.

Bug: b/36863239
Test: one-off integration test (not committed)
Change-Id: I0eef3522b62a3ab5d13071c5118026548b3d052c
diff --git a/services/core/java/com/android/server/radio/RadioService.java b/services/core/java/com/android/server/radio/RadioService.java
index 227ea6b..87a6fc1 100644
--- a/services/core/java/com/android/server/radio/RadioService.java
+++ b/services/core/java/com/android/server/radio/RadioService.java
@@ -98,7 +98,6 @@
                 throw new IllegalArgumentException("Callback must not be empty");
             }
             synchronized (mLock) {
-                // TODO(b/36863239): add death monitoring for binder
                 return nativeOpenTuner(mNativeContext, moduleId, bandConfig, withAudio, callback);
             }
         }
diff --git a/services/core/java/com/android/server/radio/Tuner.java b/services/core/java/com/android/server/radio/Tuner.java
index 3c7babd..5bd7c81 100644
--- a/services/core/java/com/android/server/radio/Tuner.java
+++ b/services/core/java/com/android/server/radio/Tuner.java
@@ -20,6 +20,8 @@
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
+import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Slog;
 
 import java.util.List;
@@ -33,18 +35,28 @@
      */
     private final long mNativeContext;
 
-    @NonNull private final TunerCallback mTunerCallback;
     private final Object mLock = new Object();
+    @NonNull private final TunerCallback mTunerCallback;
+    @NonNull private final ITunerCallback mClientCallback;
+    @NonNull private final IBinder.DeathRecipient mDeathRecipient;
+
     private boolean mIsClosed = false;
     private boolean mIsMuted = false;
     private int mRegion;  // TODO(b/62710330): find better solution to handle regions
     private final boolean mWithAudio;
 
     Tuner(@NonNull ITunerCallback clientCallback, int halRev, int region, boolean withAudio) {
+        mClientCallback = clientCallback;
         mTunerCallback = new TunerCallback(this, clientCallback, halRev);
         mRegion = region;
         mWithAudio = withAudio;
         mNativeContext = nativeInit(halRev, withAudio);
+        mDeathRecipient = this::close;
+        try {
+            mClientCallback.asBinder().linkToDeath(mDeathRecipient, 0);
+        } catch (RemoteException ex) {
+            close();
+        }
     }
 
     @Override
@@ -81,6 +93,7 @@
         synchronized (mLock) {
             if (mIsClosed) return;
             mTunerCallback.detach();
+            mClientCallback.asBinder().unlinkToDeath(mDeathRecipient, 0);
             nativeClose(mNativeContext);
             mIsClosed = true;
         }
diff --git a/services/core/jni/com_android_server_radio_Tuner.cpp b/services/core/jni/com_android_server_radio_Tuner.cpp
index e6dbfec..2a71238 100644
--- a/services/core/jni/com_android_server_radio_Tuner.cpp
+++ b/services/core/jni/com_android_server_radio_Tuner.cpp
@@ -66,6 +66,7 @@
 struct TunerContext {
     TunerContext() {}
 
+    bool mIsClosed = false;
     HalRevision mHalRev;
     bool mWithAudio;
     sp<V1_0::ITuner> mHalTuner;
@@ -127,6 +128,12 @@
     AutoMutex _l(gContextMutex);
     auto& ctx = getNativeContext(env, jTuner);
 
+    if (ctx.mIsClosed) {
+        ALOGI("Tuner was closed during initialization");
+        // dropping the last reference will close HAL tuner
+        return;
+    }
+
     ctx.mHalTuner = halTuner;
     ctx.mHalTuner11 = V1_1::ITuner::castFrom(halTuner).withDefault(nullptr);
     ALOGW_IF(ctx.mHalRev >= HalRevision::V1_1 && ctx.mHalTuner11 == nullptr,
@@ -159,7 +166,13 @@
 static void nativeClose(JNIEnv *env, jobject obj, jlong nativeContext) {
     AutoMutex _l(gContextMutex);
     auto& ctx = getNativeContext(nativeContext);
-    if (ctx.mHalTuner == nullptr) return;
+    if (ctx.mIsClosed) return;
+    ctx.mIsClosed = true;
+
+    if (ctx.mHalTuner == nullptr) {
+        ALOGI("Tuner closed during initialization");
+        return;
+    }
 
     ALOGI("Closing tuner %p", ctx.mHalTuner.get());
     notifyAudioService(ctx, false);