• 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 <JNIHelp.h>
22 #include <ScopedPrimitiveArray.h>
23 
24 #include <cstring>
25 
26 #include <android_os_MessageQueue.h>
27 #include <android_runtime/AndroidRuntime.h>
28 #include <android_runtime/Log.h>
29 #include <hardware/hdmi_cec.h>
30 #include <sys/param.h>
31 #include <utils/Looper.h>
32 #include <utils/RefBase.h>
33 
34 namespace android {
35 
36 static struct {
37     jmethodID handleIncomingCecCommand;
38     jmethodID handleHotplug;
39 } gHdmiCecControllerClassInfo;
40 
41 class HdmiCecController {
42 public:
43     HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj,
44             const sp<Looper>& looper);
45 
46     void init();
47 
48     // Send message to other device. Note that it runs in IO thread.
49     int sendMessage(const cec_message_t& message);
50     // Add a logical address to device.
51     int addLogicalAddress(cec_logical_address_t address);
52     // Clear all logical address registered to the device.
53     void clearLogicaladdress();
54     // Get physical address of device.
55     int getPhysicalAddress();
56     // Get CEC version from driver.
57     int getVersion();
58     // Get vendor id used for vendor command.
59     uint32_t getVendorId();
60     // Get Port information on all the HDMI ports.
61     jobjectArray getPortInfos();
62     // Set a flag and its value.
63     void setOption(int flag, int value);
64     // Set audio return channel status.
65     void setAudioReturnChannel(int port, bool flag);
66     // Whether to hdmi device is connected to the given port.
67     bool isConnected(int port);
68 
getCallbacksObj() const69     jobject getCallbacksObj() const {
70         return mCallbacksObj;
71     }
72 
73 private:
74     static const int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
75     static void onReceived(const hdmi_event_t* event, void* arg);
76 
77     hdmi_cec_device_t* mDevice;
78     jobject mCallbacksObj;
79     sp<Looper> mLooper;
80 };
81 
82 // RefBase wrapper for hdmi_event_t. As hdmi_event_t coming from HAL
83 // may keep its own lifetime, we need to copy it in order to delegate
84 // it to service thread.
85 class CecEventWrapper : public LightRefBase<CecEventWrapper> {
86 public:
CecEventWrapper(const hdmi_event_t & event)87     CecEventWrapper(const hdmi_event_t& event) {
88         // Copy message.
89         switch (event.type) {
90         case HDMI_EVENT_CEC_MESSAGE:
91             mEvent.cec.initiator = event.cec.initiator;
92             mEvent.cec.destination = event.cec.destination;
93             mEvent.cec.length = event.cec.length;
94             std::memcpy(mEvent.cec.body, event.cec.body, event.cec.length);
95             break;
96         case HDMI_EVENT_HOT_PLUG:
97             mEvent.hotplug.connected = event.hotplug.connected;
98             mEvent.hotplug.port_id = event.hotplug.port_id;
99             break;
100         default:
101             // TODO: add more type whenever new type is introduced.
102             break;
103         }
104     }
105 
cec() const106     const cec_message_t& cec() const {
107         return mEvent.cec;
108     }
109 
hotplug() const110     const hotplug_event_t& hotplug() const {
111         return mEvent.hotplug;
112     }
113 
~CecEventWrapper()114     virtual ~CecEventWrapper() {}
115 
116 private:
117     hdmi_event_t mEvent;
118 };
119 
120 // Handler class to delegate incoming message to service thread.
121 class HdmiCecEventHandler : public MessageHandler {
122 public:
HdmiCecEventHandler(HdmiCecController * controller,const sp<CecEventWrapper> & event)123     HdmiCecEventHandler(HdmiCecController* controller, const sp<CecEventWrapper>& event)
124         : mController(controller),
125           mEventWrapper(event) {
126     }
127 
~HdmiCecEventHandler()128     virtual ~HdmiCecEventHandler() {}
129 
handleMessage(const Message & message)130     void handleMessage(const Message& message) {
131         switch (message.what) {
132         case HDMI_EVENT_CEC_MESSAGE:
133             propagateCecCommand(mEventWrapper->cec());
134             break;
135         case HDMI_EVENT_HOT_PLUG:
136             propagateHotplugEvent(mEventWrapper->hotplug());
137             break;
138         default:
139             // TODO: add more type whenever new type is introduced.
140             break;
141         }
142     }
143 
144 private:
145     // Propagate the message up to Java layer.
propagateCecCommand(const cec_message_t & message)146     void propagateCecCommand(const cec_message_t& message) {
147         jint srcAddr = message.initiator;
148         jint dstAddr = message.destination;
149         JNIEnv* env = AndroidRuntime::getJNIEnv();
150         jbyteArray body = env->NewByteArray(message.length);
151         const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body);
152         env->SetByteArrayRegion(body, 0, message.length, bodyPtr);
153 
154         env->CallVoidMethod(mController->getCallbacksObj(),
155                 gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr,
156                 dstAddr, body);
157         env->DeleteLocalRef(body);
158 
159         checkAndClearExceptionFromCallback(env, __FUNCTION__);
160     }
161 
propagateHotplugEvent(const hotplug_event_t & event)162     void propagateHotplugEvent(const hotplug_event_t& event) {
163         // Note that this method should be called in service thread.
164         JNIEnv* env = AndroidRuntime::getJNIEnv();
165         jint port = event.port_id;
166         jboolean connected = (jboolean) event.connected;
167         env->CallVoidMethod(mController->getCallbacksObj(),
168                 gHdmiCecControllerClassInfo.handleHotplug, port, connected);
169 
170         checkAndClearExceptionFromCallback(env, __FUNCTION__);
171     }
172 
173     // static
checkAndClearExceptionFromCallback(JNIEnv * env,const char * methodName)174     static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
175         if (env->ExceptionCheck()) {
176             ALOGE("An exception was thrown by callback '%s'.", methodName);
177             LOGE_EX(env);
178             env->ExceptionClear();
179         }
180     }
181 
182     HdmiCecController* mController;
183     sp<CecEventWrapper> mEventWrapper;
184 };
185 
HdmiCecController(hdmi_cec_device_t * device,jobject callbacksObj,const sp<Looper> & looper)186 HdmiCecController::HdmiCecController(hdmi_cec_device_t* device,
187         jobject callbacksObj, const sp<Looper>& looper) :
188     mDevice(device),
189     mCallbacksObj(callbacksObj),
190     mLooper(looper) {
191 }
192 
init()193 void HdmiCecController::init() {
194     mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this);
195 }
196 
sendMessage(const cec_message_t & message)197 int HdmiCecController::sendMessage(const cec_message_t& message) {
198     // TODO: propagate send_message's return value.
199     return mDevice->send_message(mDevice, &message);
200 }
201 
addLogicalAddress(cec_logical_address_t address)202 int HdmiCecController::addLogicalAddress(cec_logical_address_t address) {
203     return mDevice->add_logical_address(mDevice, address);
204 }
205 
clearLogicaladdress()206 void HdmiCecController::clearLogicaladdress() {
207     mDevice->clear_logical_address(mDevice);
208 }
209 
getPhysicalAddress()210 int HdmiCecController::getPhysicalAddress() {
211     uint16_t addr;
212     if (!mDevice->get_physical_address(mDevice, &addr)) {
213         return addr;
214     }
215     return INVALID_PHYSICAL_ADDRESS;
216 }
217 
getVersion()218 int HdmiCecController::getVersion() {
219     int version = 0;
220     mDevice->get_version(mDevice, &version);
221     return version;
222 }
223 
getVendorId()224 uint32_t HdmiCecController::getVendorId() {
225     uint32_t vendorId = 0;
226     mDevice->get_vendor_id(mDevice, &vendorId);
227     return vendorId;
228 }
229 
getPortInfos()230 jobjectArray HdmiCecController::getPortInfos() {
231     JNIEnv* env = AndroidRuntime::getJNIEnv();
232     jclass hdmiPortInfo = env->FindClass("android/hardware/hdmi/HdmiPortInfo");
233     if (hdmiPortInfo == NULL) {
234         return NULL;
235     }
236     jmethodID ctor = env->GetMethodID(hdmiPortInfo, "<init>", "(IIIZZZ)V");
237     if (ctor == NULL) {
238         return NULL;
239     }
240     hdmi_port_info* ports;
241     int numPorts;
242     mDevice->get_port_info(mDevice, &ports, &numPorts);
243     jobjectArray res = env->NewObjectArray(numPorts, hdmiPortInfo, NULL);
244 
245     // MHL support field will be obtained from MHL HAL. Leave it to false.
246     jboolean mhlSupported = (jboolean) 0;
247     for (int i = 0; i < numPorts; ++i) {
248         hdmi_port_info* info = &ports[i];
249         jboolean cecSupported = (jboolean) info->cec_supported;
250         jboolean arcSupported = (jboolean) info->arc_supported;
251         jobject infoObj = env->NewObject(hdmiPortInfo, ctor, info->port_id, info->type,
252                 info->physical_address, cecSupported, mhlSupported, arcSupported);
253         env->SetObjectArrayElement(res, i, infoObj);
254     }
255     return res;
256 }
257 
setOption(int flag,int value)258 void HdmiCecController::setOption(int flag, int value) {
259     mDevice->set_option(mDevice, flag, value);
260 }
261 
262 // Set audio return channel status.
setAudioReturnChannel(int port,bool enabled)263   void HdmiCecController::setAudioReturnChannel(int port, bool enabled) {
264     mDevice->set_audio_return_channel(mDevice, port, enabled ? 1 : 0);
265 }
266 
267 // Whether to hdmi device is connected to the given port.
isConnected(int port)268 bool HdmiCecController::isConnected(int port) {
269     return mDevice->is_connected(mDevice, port) == HDMI_CONNECTED;
270 }
271 
272 // static
onReceived(const hdmi_event_t * event,void * arg)273 void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
274     HdmiCecController* controller = static_cast<HdmiCecController*>(arg);
275     if (controller == NULL) {
276         return;
277     }
278 
279     sp<CecEventWrapper> spEvent(new CecEventWrapper(*event));
280     sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(controller, spEvent));
281     controller->mLooper->sendMessage(handler, event->type);
282 }
283 
284 //------------------------------------------------------------------------------
285 #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
286         var = env->GetMethodID(clazz, methodName, methodDescriptor); \
287         LOG_FATAL_IF(! var, "Unable to find method " methodName);
288 
nativeInit(JNIEnv * env,jclass clazz,jobject callbacksObj,jobject messageQueueObj)289 static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj,
290         jobject messageQueueObj) {
291     int err;
292     hw_module_t* module;
293     err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID,
294             const_cast<const hw_module_t **>(&module));
295     if (err != 0) {
296         ALOGE("Error acquiring hardware module: %d", err);
297         return 0;
298     }
299 
300     hw_device_t* device;
301     err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device);
302     if (err != 0) {
303         ALOGE("Error opening hardware module: %d", err);
304         return 0;
305     }
306 
307     sp<MessageQueue> messageQueue =
308             android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
309 
310     HdmiCecController* controller = new HdmiCecController(
311             reinterpret_cast<hdmi_cec_device*>(device),
312             env->NewGlobalRef(callbacksObj),
313             messageQueue->getLooper());
314     controller->init();
315 
316     GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
317             "handleIncomingCecCommand", "(II[B)V");
318     GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz,
319             "handleHotplug", "(IZ)V");
320 
321     return reinterpret_cast<jlong>(controller);
322 }
323 
nativeSendCecCommand(JNIEnv * env,jclass clazz,jlong controllerPtr,jint srcAddr,jint dstAddr,jbyteArray body)324 static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr,
325         jint srcAddr, jint dstAddr, jbyteArray body) {
326     cec_message_t message;
327     message.initiator = static_cast<cec_logical_address_t>(srcAddr);
328     message.destination = static_cast<cec_logical_address_t>(dstAddr);
329 
330     jsize len = env->GetArrayLength(body);
331     message.length = MIN(len, CEC_MESSAGE_BODY_MAX_LENGTH);
332     ScopedByteArrayRO bodyPtr(env, body);
333     std::memcpy(message.body, bodyPtr.get(), message.length);
334 
335     HdmiCecController* controller =
336             reinterpret_cast<HdmiCecController*>(controllerPtr);
337     return controller->sendMessage(message);
338 }
339 
nativeAddLogicalAddress(JNIEnv * env,jclass clazz,jlong controllerPtr,jint logicalAddress)340 static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr,
341         jint logicalAddress) {
342     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
343     return controller->addLogicalAddress(static_cast<cec_logical_address_t>(logicalAddress));
344 }
345 
nativeClearLogicalAddress(JNIEnv * env,jclass clazz,jlong controllerPtr)346 static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) {
347     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
348     controller->clearLogicaladdress();
349 }
350 
nativeGetPhysicalAddress(JNIEnv * env,jclass clazz,jlong controllerPtr)351 static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) {
352     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
353     return controller->getPhysicalAddress();
354 }
355 
nativeGetVersion(JNIEnv * env,jclass clazz,jlong controllerPtr)356 static jint nativeGetVersion(JNIEnv* env, jclass clazz, jlong controllerPtr) {
357     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
358     return controller->getVersion();
359 }
360 
nativeGetVendorId(JNIEnv * env,jclass clazz,jlong controllerPtr)361 static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) {
362     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
363     return controller->getVendorId();
364 }
365 
nativeGetPortInfos(JNIEnv * env,jclass clazz,jlong controllerPtr)366 static jobjectArray nativeGetPortInfos(JNIEnv* env, jclass clazz, jlong controllerPtr) {
367     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
368     return controller->getPortInfos();
369 }
370 
nativeSetOption(JNIEnv * env,jclass clazz,jlong controllerPtr,jint flag,jint value)371 static void nativeSetOption(JNIEnv* env, jclass clazz, jlong controllerPtr, jint flag, jint value) {
372     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
373     controller->setOption(flag, value);
374 }
375 
nativeSetAudioReturnChannel(JNIEnv * env,jclass clazz,jlong controllerPtr,jint port,jboolean enabled)376 static void nativeSetAudioReturnChannel(JNIEnv* env, jclass clazz, jlong controllerPtr,
377         jint port, jboolean enabled) {
378     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
379     controller->setAudioReturnChannel(port, enabled == JNI_TRUE);
380 }
381 
nativeIsConnected(JNIEnv * env,jclass clazz,jlong controllerPtr,jint port)382 static jboolean nativeIsConnected(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port) {
383     HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
384     return controller->isConnected(port) ? JNI_TRUE : JNI_FALSE ;
385 }
386 
387 static const JNINativeMethod sMethods[] = {
388     /* name, signature, funcPtr */
389     { "nativeInit",
390       "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J",
391       (void *) nativeInit },
392     { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand },
393     { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress },
394     { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress },
395     { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress },
396     { "nativeGetVersion", "(J)I", (void *) nativeGetVersion },
397     { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId },
398     { "nativeGetPortInfos",
399       "(J)[Landroid/hardware/hdmi/HdmiPortInfo;",
400       (void *) nativeGetPortInfos },
401     { "nativeSetOption", "(JII)V", (void *) nativeSetOption },
402     { "nativeSetAudioReturnChannel", "(JIZ)V", (void *) nativeSetAudioReturnChannel },
403     { "nativeIsConnected", "(JI)Z", (void *) nativeIsConnected },
404 };
405 
406 #define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
407 
register_android_server_hdmi_HdmiCecController(JNIEnv * env)408 int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
409     int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
410     LOG_FATAL_IF(res < 0, "Unable to register native methods.");
411     (void)res; // Don't scream about unused variable in the LOG_NDEBUG case
412     return 0;
413 }
414 
415 }  /* namespace android */
416