Handle frontend callbacks

Test: make; acloud;
Change-Id: I0b238c01311a77eac161bae210f9459fef34a424
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index d1c171a..4ed8f42 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -16,6 +16,11 @@
 
 package android.media.tv.tuner;
 
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
 import java.util.List;
 
 /**
@@ -34,8 +39,9 @@
         nativeInit();
     }
 
-    private FrontendCallback mFrontendCallback;
     private List<Integer> mFrontendIds;
+    private Frontend mFrontend;
+    private EventHandler mHandler;
 
     public Tuner() {
         nativeSetup();
@@ -80,11 +86,66 @@
         void onEvent(int frontendEventType);
     }
 
-    protected static class Frontend {
-        int mId;
+    @Nullable
+    private EventHandler createEventHandler() {
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            return new EventHandler(looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            return new EventHandler(looper);
+        }
+        return null;
+    }
+
+    private class EventHandler extends Handler {
+        private EventHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ON_FRONTEND_EVENT:
+                    if (mFrontend != null && mFrontend.mCallback != null) {
+                        mFrontend.mCallback.onEvent(msg.arg1);
+                    }
+                    break;
+                default:
+                    // fall through
+            }
+        }
+    }
+
+    protected class Frontend {
+        private int mId;
+        private FrontendCallback mCallback;
+
         private Frontend(int id) {
             mId = id;
         }
+
+        public void setCallback(@Nullable FrontendCallback callback, @Nullable Handler handler) {
+            mCallback = callback;
+
+            if (mCallback == null) {
+                return;
+            }
+
+            if (handler == null) {
+                // use default looper if handler is null
+                if (mHandler == null) {
+                    mHandler = createEventHandler();
+                }
+                return;
+            }
+
+            Looper looper = handler.getLooper();
+            if (mHandler != null && mHandler.getLooper() == looper) {
+                // the same looper. reuse mHandler
+                return;
+            }
+            mHandler = new EventHandler(looper);
+        }
     }
 
     private List<Integer> getFrontendIds() {
@@ -94,12 +155,19 @@
 
     private Frontend openFrontendById(int id) {
         if (mFrontendIds == null) {
-            getFrontendIds();
+            mFrontendIds = getFrontendIds();
         }
         if (!mFrontendIds.contains(id)) {
             return null;
         }
-        return nativeOpenFrontendById(id);
+        mFrontend = nativeOpenFrontendById(id);
+        return mFrontend;
+    }
+
+    private void onFrontendEvent(int eventType) {
+        if (mHandler != null) {
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_FRONTEND_EVENT, eventType, 0));
+        }
     }
 
     protected class Filter {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 9e96b74..f815097 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -36,6 +36,7 @@
     jfieldID context;
     jmethodID frontendInitID;
     jmethodID filterInitID;
+    jmethodID onFrontendEventID;
 };
 
 static fields_t gFields;
@@ -52,7 +53,32 @@
     return Void();
 }
 
+/////////////// FrontendCallback ///////////////////////
+
+FrontendCallback::FrontendCallback(jweak tunerObj, FrontendId id) : mObject(tunerObj), mId(id) {}
+
+Return<void> FrontendCallback::onEvent(FrontendEventType frontendEventType) {
+    ALOGD("FrontendCallback::onEvent, type=%d", frontendEventType);
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->CallVoidMethod(
+            mObject,
+            gFields.onFrontendEventID,
+            (jint)frontendEventType);
+    return Void();
+}
+Return<void> FrontendCallback::onDiseqcMessage(const hidl_vec<uint8_t>& /*diseqcMessage*/) {
+    ALOGD("FrontendCallback::onDiseqcMessage");
+    return Void();
+}
+
+Return<void> FrontendCallback::onScanMessage(
+        FrontendScanMessageType type, const FrontendScanMessage& /*message*/) {
+    ALOGD("FrontendCallback::onScanMessage, type=%d", type);
+    return Void();
+}
+
 /////////////// Tuner ///////////////////////
+
 sp<ITuner> JTuner::mTuner;
 
 JTuner::JTuner(JNIEnv *env, jobject thiz)
@@ -89,11 +115,10 @@
 
 jobject JTuner::getFrontendIds() {
     ALOGD("JTuner::getFrontendIds()");
-    hidl_vec<FrontendId> feIds;
     mTuner->getFrontendIds([&](Result, const hidl_vec<FrontendId>& frontendIds) {
-        feIds = frontendIds;
+        mFeIds = frontendIds;
     });
-    if (feIds.size() == 0) {
+    if (mFeIds.size() == 0) {
         ALOGW("Frontend isn't available");
         return NULL;
     }
@@ -106,21 +131,25 @@
     jclass integerClazz = env->FindClass("java/lang/Integer");
     jmethodID intInit = env->GetMethodID(integerClazz, "<init>", "(I)V");
 
-    for (int i=0; i < feIds.size(); i++) {
-       jobject idObj = env->NewObject(integerClazz, intInit, feIds[i]);
+    for (int i=0; i < mFeIds.size(); i++) {
+       jobject idObj = env->NewObject(integerClazz, intInit, mFeIds[i]);
        env->CallBooleanMethod(obj, arrayListAdd, idObj);
     }
     return obj;
 }
 
 jobject JTuner::openFrontendById(int id) {
+    sp<IFrontend> fe;
     mTuner->openFrontendById(id, [&](Result, const sp<IFrontend>& frontend) {
-        mFe = frontend;
+        fe = frontend;
     });
-    if (mFe == nullptr) {
+    if (fe == nullptr) {
         ALOGE("Failed to open frontend");
         return NULL;
     }
+    mFe = fe;
+    sp<FrontendCallback> feCb = new FrontendCallback(mObject, id);
+    fe->setCallback(feCb);
 
     jint jId = (jint) id;
     JNIEnv *env = AndroidRuntime::getJNIEnv();
@@ -128,6 +157,7 @@
     return env->NewObject(
             env->FindClass("android/media/tv/tuner/Tuner$Frontend"),
             gFields.frontendInitID,
+            mObject,
             (jint) jId);
 }
 
@@ -210,8 +240,11 @@
     gFields.context = env->GetFieldID(clazz, "mNativeContext", "J");
     CHECK(gFields.context != NULL);
 
+    gFields.onFrontendEventID = env->GetMethodID(clazz, "onFrontendEvent", "(I)V");
+
     jclass frontendClazz = env->FindClass("android/media/tv/tuner/Tuner$Frontend");
-    gFields.frontendInitID = env->GetMethodID(frontendClazz, "<init>", "(I)V");
+    gFields.frontendInitID =
+            env->GetMethodID(frontendClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V");
 
     jclass filterClazz = env->FindClass("android/media/tv/tuner/Tuner$Filter");
     gFields.filterInitID =
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index b23b394..7a889c3 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -24,14 +24,19 @@
 #include "jni.h"
 
 using ::android::hardware::Return;
+using ::android::hardware::hidl_vec;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterEvent;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterStatus;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterType;
+using ::android::hardware::tv::tuner::V1_0::FrontendEventType;
 using ::android::hardware::tv::tuner::V1_0::FrontendId;
+using ::android::hardware::tv::tuner::V1_0::FrontendScanMessage;
+using ::android::hardware::tv::tuner::V1_0::FrontendScanMessageType;
 using ::android::hardware::tv::tuner::V1_0::IDemux;
 using ::android::hardware::tv::tuner::V1_0::IFilter;
 using ::android::hardware::tv::tuner::V1_0::IFilterCallback;
 using ::android::hardware::tv::tuner::V1_0::IFrontend;
+using ::android::hardware::tv::tuner::V1_0::IFrontendCallback;
 using ::android::hardware::tv::tuner::V1_0::ITuner;
 
 namespace android {
@@ -41,6 +46,18 @@
     virtual Return<void> onFilterStatus(const DemuxFilterStatus status);
 };
 
+struct FrontendCallback : public IFrontendCallback {
+    FrontendCallback(jweak tunerObj, FrontendId id);
+
+    virtual Return<void> onEvent(FrontendEventType frontendEventType);
+    virtual Return<void> onDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage);
+    virtual Return<void> onScanMessage(
+            FrontendScanMessageType type, const FrontendScanMessage& message);
+
+    jweak mObject;
+    FrontendId mId;
+};
+
 struct JTuner : public RefBase {
     JTuner(JNIEnv *env, jobject thiz);
     sp<ITuner> getTunerService();
@@ -55,6 +72,7 @@
     jclass mClass;
     jweak mObject;
     static sp<ITuner> mTuner;
+    hidl_vec<FrontendId> mFeIds;
     sp<IFrontend> mFe;
     sp<IDemux> mDemux;
     int mDemuxId;