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