Tuner FW: handle release frontend

This can unblock tune/scan CTS test cases.
If a frontend resource is acquired in a test case and not released,
it cannot be used by another test case.

Bug: 150952758
Test: atest android.media.tv.tuner.cts.TunerTest
Change-Id: Ib9bab003fc81fe008091a9d1aaefc43e454c3230
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 861eeea..fdd64f9 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -263,14 +263,14 @@
     }
 
     private void setFrontendInfoList() {
-        List<Integer> ids = nativeGetFrontendIds();
+        List<Integer> ids = getFrontendIds();
         if (ids == null) {
             return;
         }
         TunerFrontendInfo[] infos = new TunerFrontendInfo[ids.size()];
         for (int i = 0; i < ids.size(); i++) {
             int id = ids.get(i);
-            FrontendInfo frontendInfo = nativeGetFrontendInfo(id);
+            FrontendInfo frontendInfo = getFrontendInfoById(id);
             if (frontendInfo == null) {
                 continue;
             }
@@ -281,6 +281,11 @@
         mTunerResourceManager.setFrontendInfoList(infos);
     }
 
+    /** @hide */
+    public List<Integer> getFrontendIds() {
+        return nativeGetFrontendIds();
+    }
+
     private void setLnbIds() {
         int[] ids = nativeGetLnbIds();
         if (ids == null) {
@@ -345,14 +350,17 @@
     @Override
     public void close() {
         if (mFrontendHandle != null) {
+            nativeCloseFrontendByHandle(mFrontendHandle);
             mTunerResourceManager.releaseFrontend(mFrontendHandle);
             mFrontendHandle = null;
+            mFrontend = null;
         }
         if (mLnb != null) {
             mTunerResourceManager.releaseLnb(mLnbHandle);
             mLnb = null;
+            mLnbHandle = null;
         }
-        nativeClose();
+        TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
     }
 
     /**
@@ -374,6 +382,8 @@
      * Native method to open frontend of the given ID.
      */
     private native Frontend nativeOpenFrontendByHandle(int handle);
+    @Result
+    private native int nativeCloseFrontendByHandle(int handle);
     private native int nativeTune(int type, FrontendSettings settings);
     private native int nativeStopTune();
     private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
@@ -522,6 +532,7 @@
     public int tune(@NonNull FrontendSettings settings) {
         mFrontendType = settings.getType();
         checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+
         mFrontendInfo = null;
         return nativeTune(settings.getType(), settings);
     }
@@ -706,11 +717,16 @@
             throw new IllegalStateException("frontend is not initialized");
         }
         if (mFrontendInfo == null) {
-            mFrontendInfo = nativeGetFrontendInfo(mFrontend.mId);
+            mFrontendInfo = getFrontendInfoById(mFrontend.mId);
         }
         return mFrontendInfo;
     }
 
+    /** @hide */
+    public FrontendInfo getFrontendInfoById(int id) {
+        return nativeGetFrontendInfo(id);
+    }
+
     /**
      * Gets Demux capabilities.
      *
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index 2c8899c..a9f3dcf 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -416,11 +416,11 @@
      * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
      * before this release.
      *
-     * @param frontendId the id of the released frontend.
+     * @param frontendHandle the handle of the released frontend.
      */
-    public void releaseFrontend(int frontendId) {
+    public void releaseFrontend(int frontendHandle) {
         try {
-            mService.releaseFrontend(frontendId);
+            mService.releaseFrontend(frontendHandle);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 7579ca5..909394f 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -833,6 +833,12 @@
 }
 
 JTuner::~JTuner() {
+    if (mFe != NULL) {
+        mFe->close();
+    }
+    if (mDemux != NULL) {
+        mDemux->close();
+    }
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
     env->DeleteWeakGlobalRef(mObject);
@@ -908,6 +914,14 @@
             (jint) jId);
 }
 
+jint JTuner::closeFrontendById(int id) {
+    if (mFe != NULL && mFeId == id) {
+        Result r = mFe->close();
+        return (jint) r;
+    }
+    return (jint) Result::SUCCESS;
+}
+
 jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
@@ -1271,6 +1285,23 @@
     return res;
 }
 
+jint JTuner::close() {
+    Result res = Result::SUCCESS;
+    if (mFe != NULL) {
+        res = mFe->close();
+        if (res != Result::SUCCESS) {
+            return (jint) res;
+        }
+    }
+    if (mDemux != NULL) {
+        res = mDemux->close();
+        if (res != Result::SUCCESS) {
+            return (jint) res;
+        }
+    }
+    return (jint) res;
+}
+
 jobject JTuner::getAvSyncHwId(sp<Filter> filter) {
     if (mDemux == NULL) {
         return NULL;
@@ -2362,6 +2393,13 @@
     return tuner->openFrontendById(id);
 }
 
+static jint android_media_tv_Tuner_close_frontend_by_handle(
+        JNIEnv *env, jobject thiz, jint handle) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    uint32_t id = getResourceIdFromHandle(handle);
+    return tuner->closeFrontendById(id);
+}
+
 static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, jobject settings) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->tune(getFrontendSettings(env, type, settings));
@@ -3135,6 +3173,11 @@
     return (jint) tuner->openDemux();
 }
 
+static jint android_media_tv_Tuner_close_tuner(JNIEnv* env, jobject thiz) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return (jint) tuner->close();
+}
+
 static jint android_media_tv_Tuner_attach_filter(JNIEnv *env, jobject dvr, jobject filter) {
     sp<Dvr> dvrSp = getDvr(env, dvr);
     if (dvrSp == NULL) {
@@ -3424,6 +3467,8 @@
             (void *)android_media_tv_Tuner_get_frontend_ids },
     { "nativeOpenFrontendByHandle", "(I)Landroid/media/tv/tuner/Tuner$Frontend;",
             (void *)android_media_tv_Tuner_open_frontend_by_handle },
+    { "nativeCloseFrontendByHandle", "(I)I",
+            (void *)android_media_tv_Tuner_close_frontend_by_handle },
     { "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I",
             (void *)android_media_tv_Tuner_tune },
     { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune },
@@ -3460,6 +3505,7 @@
     { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;",
             (void *)android_media_tv_Tuner_get_demux_caps },
     { "nativeOpenDemuxByhandle", "(I)I", (void *)android_media_tv_Tuner_open_demux },
+    {"nativeClose", "()I", (void *)android_media_tv_Tuner_close_tuner },
 };
 
 static const JNINativeMethod gFilterMethods[] = {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 6749ba0..750b146 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -172,6 +172,7 @@
     int disconnectCiCam();
     jobject getFrontendIds();
     jobject openFrontendById(int id);
+    jint closeFrontendById(int id);
     jobject getFrontendInfo(int id);
     int tune(const FrontendSettings& settings);
     int stopTune();
@@ -189,6 +190,7 @@
     jobject getDemuxCaps();
     jobject getFrontendStatus(jintArray types);
     Result openDemux();
+    jint close();
 
 protected:
     virtual ~JTuner();
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 6dded00..921bd1d 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -247,12 +247,15 @@
         }
 
         @Override
-        public void releaseFrontend(int frontendId) {
+        public void releaseFrontend(int frontendHandle) {
             enforceTunerAccessPermission("releaseFrontend");
             enforceTrmAccessPermission("releaseFrontend");
+            int frontendId = getResourceId(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, frontendHandle);
             if (DEBUG) {
                 Slog.d(TAG, "releaseFrontend(id=" + frontendId + ")");
             }
+            updateFrontendClientMappingOnRelease(frontendId);
         }
 
         @Override
@@ -568,6 +571,17 @@
         }
     }
 
+    private void updateFrontendClientMappingOnRelease(int frontendId) {
+        FrontendResource releasingFrontend = getFrontendResource(frontendId);
+        ClientProfile ownerProfile = getClientProfile(releasingFrontend.getOwnerClientId());
+        releasingFrontend.removeOwner();
+        ownerProfile.releaseFrontend(frontendId);
+        for (int exclusiveGroupMember : releasingFrontend.getExclusiveGroupMemberFeIds()) {
+            getFrontendResource(exclusiveGroupMember).removeOwner();
+            ownerProfile.releaseFrontend(frontendId);
+        }
+    }
+
     /**
      * Get the owner client's priority from the frontend id.
      *
@@ -651,6 +665,11 @@
                 | (mResourceRequestCount++ & 0xffff);
     }
 
+    private int getResourceId(
+            @TunerResourceManager.TunerResourceType int resourceType, int resourceHandle) {
+        return (resourceHandle & 0x00ff0000) >> 16;
+    }
+
     private void enforceTrmAccessPermission(String apiName) {
         getContext().enforceCallingPermission("android.permission.TUNER_RESOURCE_ACCESS",
                 TAG + ": " + apiName);