• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "HdmiCecControllerJni"
18 
19 #define LOG_NDEBUG 1
20 
21 #include <nativehelper/JNIHelp.h>
22 #include <nativehelper/ScopedPrimitiveArray.h>
23 
24 #include <android/hardware/tv/cec/1.0/IHdmiCec.h>
25 #include <android/hardware/tv/cec/1.0/IHdmiCecCallback.h>
26 #include <android/hardware/tv/cec/1.0/types.h>
27 #include <android_os_MessageQueue.h>
28 #include <android_runtime/AndroidRuntime.h>
29 #include <android_runtime/Log.h>
30 #include <sys/param.h>
31 #include <utils/Errors.h>
32 #include <utils/Looper.h>
33 #include <utils/RefBase.h>
34 
35 using ::android::hardware::tv::cec::V1_0::CecLogicalAddress;
36 using ::android::hardware::tv::cec::V1_0::CecMessage;
37 using ::android::hardware::tv::cec::V1_0::HdmiPortInfo;
38 using ::android::hardware::tv::cec::V1_0::HotplugEvent;
39 using ::android::hardware::tv::cec::V1_0::IHdmiCec;
40 using ::android::hardware::tv::cec::V1_0::IHdmiCecCallback;
41 using ::android::hardware::tv::cec::V1_0::MaxLength;
42 using ::android::hardware::tv::cec::V1_0::OptionKey;
43 using ::android::hardware::tv::cec::V1_0::Result;
44 using ::android::hardware::tv::cec::V1_0::SendMessageResult;
45 using ::android::hardware::Return;
46 using ::android::hardware::Void;
47 using ::android::hardware::hidl_vec;
48 using ::android::hardware::hidl_string;
49 
50 namespace android {
51 
52 static struct {
53     jmethodID handleIncomingCecCommand;
54     jmethodID handleHotplug;
55 } gHdmiCecControllerClassInfo;
56 
57 class HdmiCecController {
58 public:
59     HdmiCecController(sp<IHdmiCec> hdmiCec, jobject callbacksObj, const sp<Looper>& looper);
60     ~HdmiCecController();
61 
62     // Send message to other device. Note that it runs in IO thread.
63     int sendMessage(const CecMessage& message);
64     // Add a logical address to device.
65     int addLogicalAddress(CecLogicalAddress address);
66     // Clear all logical address registered to the device.
67     void clearLogicaladdress();
68     // Get physical address of device.
69     int getPhysicalAddress();
70     // Get CEC version from driver.
71     int getVersion();
72     // Get vendor id used for vendor command.
73     uint32_t getVendorId();
74     // Get Port information on all the HDMI ports.
75     jobjectArray getPortInfos();
76     // Set an option to CEC HAL.
77     void setOption(OptionKey key, bool enabled);
78     // Informs CEC HAL about the current system language.
79     void setLanguage(hidl_string language);
80     // Enable audio return channel.
81     void enableAudioReturnChannel(int port, bool flag);
82     // Whether to hdmi device is connected to the given port.
83     bool isConnected(int port);
84 
getCallbacksObj() const85     jobject getCallbacksObj() const {
86         return mCallbacksObj;
87     }
88 
89 private:
90     class HdmiCecCallback : public IHdmiCecCallback {
91     public:
HdmiCecCallback(HdmiCecController * controller)92         HdmiCecCallback(HdmiCecController* controller) : mController(controller) {};
93         Return<void> onCecMessage(const CecMessage& event)  override;
94         Return<void> onHotplugEvent(const HotplugEvent& event)  override;
95     private:
96         HdmiCecController* mController;
97     };
98 
99     static const int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
100 
101     sp<IHdmiCec> mHdmiCec;
102     jobject mCallbacksObj;
103     sp<IHdmiCecCallback> mHdmiCecCallback;
104     sp<Looper> mLooper;
105 };
106 
107 // Handler class to delegate incoming message to service thread.
108 class HdmiCecEventHandler : public MessageHandler {
109 public:
110     enum EventType {
111         CEC_MESSAGE,
112         HOT_PLUG
113     };
114 
HdmiCecEventHandler(HdmiCecController * controller,const CecMessage & cecMessage)115     HdmiCecEventHandler(HdmiCecController* controller, const CecMessage& cecMessage)
116             : mController(controller),
117               mCecMessage(cecMessage) {}
118 
HdmiCecEventHandler(HdmiCecController * controller,const HotplugEvent & hotplugEvent)119     HdmiCecEventHandler(HdmiCecController* controller, const HotplugEvent& hotplugEvent)
120             : mController(controller),
121               mHotplugEvent(hotplugEvent) {}
122 
~HdmiCecEventHandler()123     virtual ~HdmiCecEventHandler() {}
124 
handleMessage(const Message & message)125     void handleMessage(const Message& message) {
126         switch (message.what) {
127         case EventType::CEC_MESSAGE:
128             propagateCecCommand(mCecMessage);
129             break;
130         case EventType::HOT_PLUG:
131             propagateHotplugEvent(mHotplugEvent);
132             break;
133         default:
134             // TODO: add more type whenever new type is introduced.
135             break;
136         }
137     }
138 
139 private:
140     // Propagate the message up to Java layer.
propagateCecCommand(const CecMessage & message)141     void propagateCecCommand(const CecMessage& message) {
142         JNIEnv* env = AndroidRuntime::getJNIEnv();
143         jint srcAddr = static_cast<jint>(message.initiator);
144         jint dstAddr = static_cast<jint>(message.destination);
145         jbyteArray body = env->NewByteArray(message.body.size());
146         const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body.data());
147         env->SetByteArrayRegion(body, 0, message.body.size(), bodyPtr);
148         env->CallVoidMethod(mController->getCallbacksObj(),
149                 gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr,
150                 dstAddr, body);
151         env->DeleteLocalRef(body);
152 
153         checkAndClearExceptionFromCallback(env, __FUNCTION__);
154     }
155 
propagateHotplugEvent(const HotplugEvent & event)156     void propagateHotplugEvent(const HotplugEvent& event) {
157         // Note that this method should be called in service thread.
158         JNIEnv* env = AndroidRuntime::getJNIEnv();
159         jint port = static_cast<jint>(event.portId);
160         jboolean connected = (jboolean) event.connected;
161         env->CallVoidMethod(mController->getCallbacksObj(),
162                 gHdmiCecControllerClassInfo.handleHotplug, port, connected);
163 
164         checkAndClearExceptionFromCallback(env, __FUNCTION__);
165     }
166 
167     // static
checkAndClearExceptionFromCallback(JNIEnv * env,const char * methodName)168     static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
169         if (env->ExceptionCheck()) {
170             ALOGE("An exception was thrown by callback '%s'.", methodName);
171             LOGE_EX(env);
172             env->ExceptionClear();
173         }
174     }
175 
176     HdmiCecController* mController;
177     CecMessage mCecMessage;
178     HotplugEvent mHotplugEvent;
179 };
180 
HdmiCecController(sp<IHdmiCec> hdmiCec,jobject callbacksObj,const sp<Looper> & looper)181 HdmiCecController::HdmiCecController(sp<IHdmiCec> hdmiCec,
182         jobject callbacksObj, const sp<Looper>& looper)
183         : mHdmiCec(hdmiCec),
184           mCallbacksObj(callbacksObj),
185           mLooper(looper) {
186     mHdmiCecCallback = new HdmiCecCallback(this);
187     Return<void> ret = mHdmiCec->setCallback(mHdmiCecCallback);
188     if (!ret.isOk()) {
189         ALOGE("Failed to set a cec callback.");
190     }
191 }
192 
~HdmiCecController()193 HdmiCecController::~HdmiCecController() {
194     Return<void> ret = mHdmiCec->setCallback(nullptr);
195     if (!ret.isOk()) {
196         ALOGE("Failed to set a cec callback.");
197     }
198 }
199 
sendMessage(const CecMessage & message)200 int HdmiCecController::sendMessage(const CecMessage& message) {
201     // TODO: propagate send_message's return value.
202     Return<SendMessageResult> ret = mHdmiCec->sendMessage(message);
203     if (!ret.isOk()) {
204         ALOGE("Failed to send CEC message.");
205         return static_cast<int>(SendMessageResult::FAIL);
206     }
207     return static_cast<int>((SendMessageResult) ret);
208 }
209 
addLogicalAddress(CecLogicalAddress address)210 int HdmiCecController::addLogicalAddress(CecLogicalAddress address) {
211     Return<Result> ret = mHdmiCec->addLogicalAddress(address);
212     if (!ret.isOk()) {
213         ALOGE("Failed to add a logical address.");
214         return static_cast<int>(Result::FAILURE_UNKNOWN);
215     }
216     return static_cast<int>((Result) ret);
217 }
218 
clearLogicaladdress()219 void HdmiCecController::clearLogicaladdress() {
220     Return<void> ret = mHdmiCec->clearLogicalAddress();
221     if (!ret.isOk()) {
222         ALOGE("Failed to clear logical address.");
223     }
224 }
225 
getPhysicalAddress()226 int HdmiCecController::getPhysicalAddress() {
227     Result result;
228     uint16_t addr;
229     Return<void> ret = mHdmiCec->getPhysicalAddress([&result, &addr](Result res, uint16_t paddr) {
230             result = res;
231             addr = paddr;
232         });
233     if (!ret.isOk()) {
234         ALOGE("Failed to get physical address.");
235         return INVALID_PHYSICAL_ADDRESS;
236     }
237     return result == Result::SUCCESS ? addr : INVALID_PHYSICAL_ADDRESS;
238 }
239 
getVersion()240 int HdmiCecController::getVersion() {
241     Return<int32_t> ret = mHdmiCec->getCecVersion();
242     if (!ret.isOk()) {
243         ALOGE("Failed to get cec version.");
244     }
245     return ret;
246 }
247 
getVendorId()248 uint32_t HdmiCecController::getVendorId() {
249     Return<uint32_t> ret = mHdmiCec->getVendorId();
250     if (!ret.isOk()) {
251         ALOGE("Failed to get vendor id.");
252     }
253     return ret;
254 }
255 
getPortInfos()256 jobjectArray HdmiCecController::getPortInfos() {
257     JNIEnv* env = AndroidRuntime::getJNIEnv();
258     jclass hdmiPortInfo = env->FindClass("android/hardware/hdmi/HdmiPortInfo");
259     if (hdmiPortInfo == NULL) {
260         return NULL;
261     }
262     jmethodID ctor = env->GetMethodID(hdmiPortInfo, "<init>", "(IIIZZZ)V");
263     if (ctor == NULL) {
264         return NULL;
265     }
266     hidl_vec<HdmiPortInfo> ports;
267     Return<void> ret = mHdmiCec->getPortInfo([&ports](hidl_vec<HdmiPortInfo> list) {
268             ports = list;
269         });
270     if (!ret.isOk()) {
271         ALOGE("Failed to get port information.");
272         return NULL;
273     }
274     jobjectArray res = env->NewObjectArray(ports.size(), hdmiPortInfo, NULL);
275 
276     // MHL support field will be obtained from MHL HAL. Leave it to false.
277     jboolean mhlSupported = (jboolean) 0;
278     for (size_t i = 0; i < ports.size(); ++i) {
279         jboolean cecSupported = (jboolean) ports[i].cecSupported;
280         jboolean arcSupported = (jboolean) ports[i].arcSupported;
281         jobject infoObj = env->NewObject(hdmiPortInfo, ctor, ports[i].portId, ports[i].type,
282                 ports[i].physicalAddress, cecSupported, mhlSupported, arcSupported);
283         env->SetObjectArrayElement(res, i, infoObj);
284     }
285     return res;
286 }
287 
setOption(OptionKey key,bool enabled)288 void HdmiCecController::setOption(OptionKey key, bool enabled) {
289     Return<void> ret = mHdmiCec->setOption(key, enabled);
290     if (!ret.isOk()) {
291         ALOGE("Failed to set option.");
292     }
293 }
294 
setLanguage(hidl_string language)295 void HdmiCecController::setLanguage(hidl_string language) {
296     Return<void> ret = mHdmiCec->setLanguage(language);
297     if (!ret.isOk()) {
298         ALOGE("Failed to set language.");
299     }
300 }
301 
302 // Enable audio return channel.
enableAudioReturnChannel(int port,bool enabled)303 void HdmiCecController::enableAudioReturnChannel(int port, bool enabled) {
304     Return<void> ret = mHdmiCec->enableAudioReturnChannel(port, enabled);
305     if (!ret.isOk()) {
306         ALOGE("Failed to enable/disable ARC.");
307     }
308 }
309 
310 // Whether to hdmi device is connected to the given port.
isConnected(int port)311 bool HdmiCecController::isConnected(int port) {
312     Return<bool> ret = mHdmiCec->isConnected(port);
313     if (!ret.isOk()) {
314         ALOGE("Failed to get connection info.");
315     }
316     return ret;
317 }
318 
onCecMessage(const CecMessage & message)319 Return<void> HdmiCecController::HdmiCecCallback::onCecMessage(const CecMessage& message) {
320     sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(mController, message));
321     mController->mLooper->sendMessage(handler, HdmiCecEventHandler::EventType::CEC_MESSAGE);
322     return Void();
323 }
324 
onHotplugEvent(const HotplugEvent & event)325 Return<void> HdmiCecController::HdmiCecCallback::onHotplugEvent(const HotplugEvent& event) {
326     sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(mController, event));
327     mController->mLooper->sendMessage(handler, HdmiCecEventHandler::EventType::HOT_PLUG);
328     return Void();
329 }
330 
331 //------------------------------------------------------------------------------
332 #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
333         var = env->GetMethodID(clazz, methodName, methodDescriptor); \
334         LOG_FATAL_IF(! (var), "Unable to find method " methodName);
335 
nativeInit(JNIEnv * env,jclass clazz,jobject callbacksObj,jobject messageQueueObj)336 static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj,
337         jobject messageQueueObj) {
338     // TODO(b/31632518)
339     sp<IHdmiCec> hdmiCec = IHdmiCec::getService();
340     if (hdmiCec == nullptr) {
341         ALOGE("Couldn't get tv.cec service.");
342         return 0;
343     }
344     sp<MessageQueue> messageQueue =
345             android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
346 
347     HdmiCecController* controller = new HdmiCecController(
348             hdmiCec,
349             env->NewGlobalRef(callbacksObj),
350             messageQueue->getLooper());
351 
352     GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
353             "handleIncomingCecCommand", "(II[B)V");
354     GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz,
355             "handleHotplug", "(IZ)V");
356 
357     return reinterpret_cast<jlong>(controller);
358 }
359 
nativeSendCecCommand(JNIEnv * env,jclass clazz,jlong controllerPtr,jint srcAddr,jint dstAddr,jbyteArray body)360 static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr,
361         jint srcAddr, jint dstAddr, jbyteArray body) {
362     CecMessage message;
363     message.initiator = static_cast<CecLogicalAddress>(srcAddr);
364     message.destination = static_cast<CecLogicalAddress>(dstAddr);
365 
366     jsize len = env->GetArrayLength(body);
367     ScopedByteArrayRO bodyPtr(env, body);
368     size_t bodyLength = MIN(static_cast<size_t>(len),
369             static_cast<size_t>(MaxLength::MESSAGE_BODY));
370     message.body.resize(bodyLength);
371     for (size_t i = 0; i < bodyLength; ++i) {
372         message.body[i] = static_cast<uint8_t>(bodyPtr[i]);
373     }
374 
375     HdmiCecController* controller =
376             reinterpret_cast<HdmiCecController*>(controllerPtr);
377     return controller->sendMessage(message);
378 }
379 
nativeAddLogicalAddress(JNIEnv * env,jclass clazz,jlong controllerPtr,jint logicalAddress)380 static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr,
381         jint logicalAddress) {
382     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
383     return controller->addLogicalAddress(static_cast<CecLogicalAddress>(logicalAddress));
384 }
385 
nativeClearLogicalAddress(JNIEnv * env,jclass clazz,jlong controllerPtr)386 static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) {
387     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
388     controller->clearLogicaladdress();
389 }
390 
nativeGetPhysicalAddress(JNIEnv * env,jclass clazz,jlong controllerPtr)391 static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) {
392     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
393     return controller->getPhysicalAddress();
394 }
395 
nativeGetVersion(JNIEnv * env,jclass clazz,jlong controllerPtr)396 static jint nativeGetVersion(JNIEnv* env, jclass clazz, jlong controllerPtr) {
397     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
398     return controller->getVersion();
399 }
400 
nativeGetVendorId(JNIEnv * env,jclass clazz,jlong controllerPtr)401 static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) {
402     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
403     return controller->getVendorId();
404 }
405 
nativeGetPortInfos(JNIEnv * env,jclass clazz,jlong controllerPtr)406 static jobjectArray nativeGetPortInfos(JNIEnv* env, jclass clazz, jlong controllerPtr) {
407     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
408     return controller->getPortInfos();
409 }
410 
nativeSetOption(JNIEnv * env,jclass clazz,jlong controllerPtr,jint flag,jint value)411 static void nativeSetOption(JNIEnv* env, jclass clazz, jlong controllerPtr, jint flag, jint value) {
412     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
413     controller->setOption(static_cast<OptionKey>(flag), value > 0 ? true : false);
414 }
415 
nativeSetLanguage(JNIEnv * env,jclass clazz,jlong controllerPtr,jstring language)416 static void nativeSetLanguage(JNIEnv* env, jclass clazz, jlong controllerPtr, jstring language) {
417     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
418     const char *languageStr = env->GetStringUTFChars(language, NULL);
419     controller->setLanguage(languageStr);
420     env->ReleaseStringUTFChars(language, languageStr);
421 }
422 
nativeEnableAudioReturnChannel(JNIEnv * env,jclass clazz,jlong controllerPtr,jint port,jboolean enabled)423 static void nativeEnableAudioReturnChannel(JNIEnv* env, jclass clazz, jlong controllerPtr,
424         jint port, jboolean enabled) {
425     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
426     controller->enableAudioReturnChannel(port, enabled == JNI_TRUE);
427 }
428 
nativeIsConnected(JNIEnv * env,jclass clazz,jlong controllerPtr,jint port)429 static jboolean nativeIsConnected(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port) {
430     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
431     return controller->isConnected(port) ? JNI_TRUE : JNI_FALSE ;
432 }
433 
434 static const JNINativeMethod sMethods[] = {
435     /* name, signature, funcPtr */
436     { "nativeInit",
437       "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J",
438       (void *) nativeInit },
439     { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand },
440     { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress },
441     { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress },
442     { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress },
443     { "nativeGetVersion", "(J)I", (void *) nativeGetVersion },
444     { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId },
445     { "nativeGetPortInfos",
446       "(J)[Landroid/hardware/hdmi/HdmiPortInfo;",
447       (void *) nativeGetPortInfos },
448     { "nativeSetOption", "(JIZ)V", (void *) nativeSetOption },
449     { "nativeSetLanguage", "(JLjava/lang/String;)V", (void *) nativeSetLanguage },
450     { "nativeEnableAudioReturnChannel", "(JIZ)V", (void *) nativeEnableAudioReturnChannel },
451     { "nativeIsConnected", "(JI)Z", (void *) nativeIsConnected },
452 };
453 
454 #define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
455 
register_android_server_hdmi_HdmiCecController(JNIEnv * env)456 int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
457     int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
458     LOG_FATAL_IF(res < 0, "Unable to register native methods.");
459     (void)res; // Don't scream about unused variable in the LOG_NDEBUG case
460     return 0;
461 }
462 
463 }  /* namespace android */
464