android_hardware_SensorManager.cpp revision fa2672b71d9501463d8b9de5162f069b9e48384b
1/* 2 * Copyright 2008, 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#define LOG_TAG "SensorManager" 17 18#include "JNIHelp.h" 19#include "android_os_MessageQueue.h" 20#include "core_jni_helpers.h" 21#include "jni.h" 22 23#include <ScopedUtfChars.h> 24#include <ScopedLocalRef.h> 25#include <android_runtime/AndroidRuntime.h> 26#include <gui/Sensor.h> 27#include <gui/SensorEventQueue.h> 28#include <gui/SensorManager.h> 29#include <cutils/native_handle.h> 30#include <utils/Log.h> 31#include <utils/Looper.h> 32#include <utils/Vector.h> 33 34#include <map> 35 36namespace { 37 38using namespace android; 39 40struct { 41 jclass clazz; 42 jmethodID dispatchSensorEvent; 43 jmethodID dispatchFlushCompleteEvent; 44 jmethodID dispatchAdditionalInfoEvent; 45} gBaseEventQueueClassInfo; 46 47struct SensorOffsets 48{ 49 jclass clazz; 50 //fields 51 jfieldID name; 52 jfieldID vendor; 53 jfieldID version; 54 jfieldID handle; 55 jfieldID range; 56 jfieldID resolution; 57 jfieldID power; 58 jfieldID minDelay; 59 jfieldID fifoReservedEventCount; 60 jfieldID fifoMaxEventCount; 61 jfieldID stringType; 62 jfieldID requiredPermission; 63 jfieldID maxDelay; 64 jfieldID flags; 65 //methods 66 jmethodID setType; 67 jmethodID setUuid; 68 jmethodID init; 69} gSensorOffsets; 70 71struct ListOffsets { 72 jclass clazz; 73 jmethodID add; 74} gListOffsets; 75 76/* 77 * nativeClassInit is not inteneded to be thread-safe. It should be called before other native... 78 * functions (except nativeCreate). 79 */ 80static void 81nativeClassInit (JNIEnv *_env, jclass _this) 82{ 83 //android.hardware.Sensor 84 SensorOffsets& sensorOffsets = gSensorOffsets; 85 jclass sensorClass = (jclass) _env->NewGlobalRef(_env->FindClass("android/hardware/Sensor")); 86 sensorOffsets.clazz = sensorClass; 87 sensorOffsets.name = _env->GetFieldID(sensorClass, "mName", "Ljava/lang/String;"); 88 sensorOffsets.vendor = _env->GetFieldID(sensorClass, "mVendor", "Ljava/lang/String;"); 89 sensorOffsets.version = _env->GetFieldID(sensorClass, "mVersion", "I"); 90 sensorOffsets.handle = _env->GetFieldID(sensorClass, "mHandle", "I"); 91 sensorOffsets.range = _env->GetFieldID(sensorClass, "mMaxRange", "F"); 92 sensorOffsets.resolution = _env->GetFieldID(sensorClass, "mResolution","F"); 93 sensorOffsets.power = _env->GetFieldID(sensorClass, "mPower", "F"); 94 sensorOffsets.minDelay = _env->GetFieldID(sensorClass, "mMinDelay", "I"); 95 sensorOffsets.fifoReservedEventCount = 96 _env->GetFieldID(sensorClass, "mFifoReservedEventCount", "I"); 97 sensorOffsets.fifoMaxEventCount = _env->GetFieldID(sensorClass, "mFifoMaxEventCount", "I"); 98 sensorOffsets.stringType = _env->GetFieldID(sensorClass, "mStringType", "Ljava/lang/String;"); 99 sensorOffsets.requiredPermission = _env->GetFieldID(sensorClass, "mRequiredPermission", 100 "Ljava/lang/String;"); 101 sensorOffsets.maxDelay = _env->GetFieldID(sensorClass, "mMaxDelay", "I"); 102 sensorOffsets.flags = _env->GetFieldID(sensorClass, "mFlags", "I"); 103 104 sensorOffsets.setType = _env->GetMethodID(sensorClass, "setType", "(I)Z"); 105 sensorOffsets.setUuid = _env->GetMethodID(sensorClass, "setUuid", "(JJ)V"); 106 sensorOffsets.init = _env->GetMethodID(sensorClass, "<init>", "()V"); 107 108 // java.util.List; 109 ListOffsets& listOffsets = gListOffsets; 110 jclass listClass = (jclass) _env->NewGlobalRef(_env->FindClass("java/util/List")); 111 listOffsets.clazz = listClass; 112 listOffsets.add = _env->GetMethodID(listClass, "add", "(Ljava/lang/Object;)Z"); 113} 114 115/** 116 * A key comparator predicate. 117 * It is used to intern strings associated with Sensor data. 118 * It defines a 'Strict weak ordering' for the interned strings. 119 */ 120class InternedStringCompare { 121public: 122 bool operator()(const String8* string1, const String8* string2) const { 123 if (string1 == NULL) { 124 return string2 != NULL; 125 } 126 if (string2 == NULL) { 127 return false; 128 } 129 return string1->compare(*string2) < 0; 130 } 131}; 132 133/** 134 * A localized interning mechanism for Sensor strings. 135 * We implement our own interning to avoid the overhead of using java.lang.String#intern(). 136 * It is common that Vendor, StringType, and RequirePermission data is common between many of the 137 * Sensors, by interning the memory usage to represent Sensors is optimized. 138 */ 139static jstring 140getInternedString(JNIEnv *env, const String8* string) { 141 static std::map<const String8*, jstring, InternedStringCompare> internedStrings; 142 143 jstring internedString; 144 std::map<const String8*, jstring>::iterator iterator = internedStrings.find(string); 145 if (iterator != internedStrings.end()) { 146 internedString = iterator->second; 147 } else { 148 jstring localString = env->NewStringUTF(string->string()); 149 // we are implementing our own interning so expect these strings to be backed by global refs 150 internedString = (jstring) env->NewGlobalRef(localString); 151 internedStrings.insert(std::make_pair(string, internedString)); 152 env->DeleteLocalRef(localString); 153 } 154 return internedString; 155} 156 157static jlong 158nativeCreate 159(JNIEnv *env, jclass clazz, jstring opPackageName) 160{ 161 ScopedUtfChars opPackageNameUtf(env, opPackageName); 162 return (jlong) &SensorManager::getInstanceForPackage(String16(opPackageNameUtf.c_str())); 163} 164 165static jobject 166translateNativeSensorToJavaSensor(JNIEnv *env, jobject sensor, const Sensor& nativeSensor) { 167 const SensorOffsets& sensorOffsets(gSensorOffsets); 168 169 if (sensor == NULL) { 170 // Sensor sensor = new Sensor(); 171 sensor = env->NewObject(sensorOffsets.clazz, sensorOffsets.init, ""); 172 } 173 174 if (sensor != NULL) { 175 jstring name = env->NewStringUTF(nativeSensor.getName().string()); 176 jstring vendor = env->NewStringUTF(nativeSensor.getVendor().string()); 177 jstring requiredPermission = 178 env->NewStringUTF(nativeSensor.getRequiredPermission().string()); 179 180 env->SetObjectField(sensor, sensorOffsets.name, name); 181 env->SetObjectField(sensor, sensorOffsets.vendor, vendor); 182 env->SetIntField(sensor, sensorOffsets.version, nativeSensor.getVersion()); 183 env->SetIntField(sensor, sensorOffsets.handle, nativeSensor.getHandle()); 184 env->SetFloatField(sensor, sensorOffsets.range, nativeSensor.getMaxValue()); 185 env->SetFloatField(sensor, sensorOffsets.resolution, nativeSensor.getResolution()); 186 env->SetFloatField(sensor, sensorOffsets.power, nativeSensor.getPowerUsage()); 187 env->SetIntField(sensor, sensorOffsets.minDelay, nativeSensor.getMinDelay()); 188 env->SetIntField(sensor, sensorOffsets.fifoReservedEventCount, 189 nativeSensor.getFifoReservedEventCount()); 190 env->SetIntField(sensor, sensorOffsets.fifoMaxEventCount, 191 nativeSensor.getFifoMaxEventCount()); 192 env->SetObjectField(sensor, sensorOffsets.requiredPermission, 193 requiredPermission); 194 env->SetIntField(sensor, sensorOffsets.maxDelay, nativeSensor.getMaxDelay()); 195 env->SetIntField(sensor, sensorOffsets.flags, nativeSensor.getFlags()); 196 197 if (env->CallBooleanMethod(sensor, sensorOffsets.setType, nativeSensor.getType()) 198 == JNI_FALSE) { 199 jstring stringType = getInternedString(env, &nativeSensor.getStringType()); 200 env->SetObjectField(sensor, sensorOffsets.stringType, stringType); 201 } 202 203 // TODO(b/29547335): Rename "setUuid" method to "setId". 204 int64_t id = nativeSensor.getId(); 205 env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, 0); 206 } 207 return sensor; 208} 209 210static jboolean 211nativeGetSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensor, jint index) 212{ 213 SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); 214 215 Sensor const* const* sensorList; 216 ssize_t count = mgr->getSensorList(&sensorList); 217 if (ssize_t(index) >= count) { 218 return false; 219 } 220 221 return translateNativeSensorToJavaSensor(env, sensor, *sensorList[index]) != NULL; 222} 223 224static void 225nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensorList) { 226 227 SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); 228 const ListOffsets& listOffsets(gListOffsets); 229 230 Vector<Sensor> nativeList; 231 232 mgr->getDynamicSensorList(nativeList); 233 234 ALOGI("DYNS native SensorManager.getDynamicSensorList return %zu sensors", nativeList.size()); 235 for (size_t i = 0; i < nativeList.size(); ++i) { 236 jobject sensor = translateNativeSensorToJavaSensor(env, NULL, nativeList[i]); 237 // add to list 238 env->CallBooleanMethod(sensorList, listOffsets.add, sensor); 239 } 240} 241 242static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong sensorManager) { 243 SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); 244 return mgr->isDataInjectionEnabled(); 245} 246 247static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager, 248 jlong size, jint channelType, jlongArray channelData) { 249 jint ret = -1; 250 jsize len = _env->GetArrayLength(channelData); 251 if (len > 2) { 252 jlong *data = _env->GetLongArrayElements(channelData, NULL); 253 if (data != nullptr) { 254 // construct native handle from jlong* 255 jlong numFd = data[0]; 256 jlong numInt = data[1]; 257 if ((numFd + numInt + 2) == len) { 258 native_handle_t *nativeHandle = native_handle_create(numFd, numInt); 259 if (nativeHandle != nullptr) { 260 const jlong *readPointer = data + 2; 261 int *writePointer = nativeHandle->data; 262 size_t n = static_cast<size_t>(numFd + numInt); 263 while (n--) { 264 // native type of data is int, jlong is just to ensure Java does not 265 // truncate data on 64-bit system. The cast here is safe. 266 *writePointer++ = static_cast<int>(*readPointer++); 267 } 268 269 SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); 270 ret = mgr->createDirectChannel(size, channelType, nativeHandle); 271 272 // do not native_handle_close() here as handle is owned by java 273 native_handle_delete(nativeHandle); 274 } 275 } 276 // unidirectional parameter passing, thus JNI_ABORT 277 _env->ReleaseLongArrayElements(channelData, data, JNI_ABORT); 278 } 279 } 280 return ret; 281} 282 283static void nativeDestroyDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager, 284 jint channelHandle) { 285 SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); 286 mgr->destroyDirectChannel(channelHandle); 287} 288 289static jint nativeConfigDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager, 290 jint channelHandle, jint sensorHandle, jint rate) { 291 SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); 292 return mgr->configureDirectChannel(channelHandle, sensorHandle, rate); 293} 294 295//---------------------------------------------------------------------------- 296 297class Receiver : public LooperCallback { 298 sp<SensorEventQueue> mSensorQueue; 299 sp<MessageQueue> mMessageQueue; 300 jobject mReceiverWeakGlobal; 301 jfloatArray mFloatScratch; 302 jintArray mIntScratch; 303public: 304 Receiver(const sp<SensorEventQueue>& sensorQueue, 305 const sp<MessageQueue>& messageQueue, 306 jobject receiverWeak) { 307 JNIEnv* env = AndroidRuntime::getJNIEnv(); 308 mSensorQueue = sensorQueue; 309 mMessageQueue = messageQueue; 310 mReceiverWeakGlobal = env->NewGlobalRef(receiverWeak); 311 312 mIntScratch = (jintArray) env->NewGlobalRef(env->NewIntArray(16)); 313 mFloatScratch = (jfloatArray) env->NewGlobalRef(env->NewFloatArray(16)); 314 } 315 ~Receiver() { 316 JNIEnv* env = AndroidRuntime::getJNIEnv(); 317 env->DeleteGlobalRef(mReceiverWeakGlobal); 318 env->DeleteGlobalRef(mFloatScratch); 319 env->DeleteGlobalRef(mIntScratch); 320 } 321 sp<SensorEventQueue> getSensorEventQueue() const { 322 return mSensorQueue; 323 } 324 325 void destroy() { 326 mMessageQueue->getLooper()->removeFd( mSensorQueue->getFd() ); 327 } 328 329private: 330 virtual void onFirstRef() { 331 LooperCallback::onFirstRef(); 332 mMessageQueue->getLooper()->addFd(mSensorQueue->getFd(), 0, 333 ALOOPER_EVENT_INPUT, this, mSensorQueue.get()); 334 } 335 336 virtual int handleEvent(int fd, int events, void* data) { 337 JNIEnv* env = AndroidRuntime::getJNIEnv(); 338 sp<SensorEventQueue> q = reinterpret_cast<SensorEventQueue *>(data); 339 ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal)); 340 341 ssize_t n; 342 ASensorEvent buffer[16]; 343 while ((n = q->read(buffer, 16)) > 0) { 344 for (int i=0 ; i<n ; i++) { 345 if (buffer[i].type == SENSOR_TYPE_STEP_COUNTER) { 346 // step-counter returns a uint64, but the java API only deals with floats 347 float value = float(buffer[i].u64.step_counter); 348 env->SetFloatArrayRegion(mFloatScratch, 0, 1, &value); 349 } else if (buffer[i].type == SENSOR_TYPE_DYNAMIC_SENSOR_META) { 350 float value[2]; 351 value[0] = buffer[i].dynamic_sensor_meta.connected ? 1.f: 0.f; 352 value[1] = float(buffer[i].dynamic_sensor_meta.handle); 353 env->SetFloatArrayRegion(mFloatScratch, 0, 2, value); 354 } else if (buffer[i].type == SENSOR_TYPE_ADDITIONAL_INFO) { 355 env->SetIntArrayRegion(mIntScratch, 0, 14, 356 buffer[i].additional_info.data_int32); 357 env->SetFloatArrayRegion(mFloatScratch, 0, 14, 358 buffer[i].additional_info.data_float); 359 } else { 360 env->SetFloatArrayRegion(mFloatScratch, 0, 16, buffer[i].data); 361 } 362 363 if (buffer[i].type == SENSOR_TYPE_META_DATA) { 364 // This is a flush complete sensor event. Call dispatchFlushCompleteEvent 365 // method. 366 if (receiverObj.get()) { 367 env->CallVoidMethod(receiverObj.get(), 368 gBaseEventQueueClassInfo.dispatchFlushCompleteEvent, 369 buffer[i].meta_data.sensor); 370 } 371 } else if (buffer[i].type == SENSOR_TYPE_ADDITIONAL_INFO) { 372 // This is a flush complete sensor event. Call dispatchAdditionalInfoEvent 373 // method. 374 if (receiverObj.get()) { 375 int type = buffer[i].additional_info.type; 376 int serial = buffer[i].additional_info.serial; 377 env->CallVoidMethod(receiverObj.get(), 378 gBaseEventQueueClassInfo.dispatchAdditionalInfoEvent, 379 buffer[i].sensor, 380 type, serial, 381 mFloatScratch, 382 mIntScratch, 383 buffer[i].timestamp); 384 } 385 }else { 386 int8_t status; 387 switch (buffer[i].type) { 388 case SENSOR_TYPE_ORIENTATION: 389 case SENSOR_TYPE_MAGNETIC_FIELD: 390 case SENSOR_TYPE_ACCELEROMETER: 391 case SENSOR_TYPE_GYROSCOPE: 392 case SENSOR_TYPE_GRAVITY: 393 case SENSOR_TYPE_LINEAR_ACCELERATION: 394 status = buffer[i].vector.status; 395 break; 396 case SENSOR_TYPE_HEART_RATE: 397 status = buffer[i].heart_rate.status; 398 break; 399 default: 400 status = SENSOR_STATUS_ACCURACY_HIGH; 401 break; 402 } 403 if (receiverObj.get()) { 404 env->CallVoidMethod(receiverObj.get(), 405 gBaseEventQueueClassInfo.dispatchSensorEvent, 406 buffer[i].sensor, 407 mFloatScratch, 408 status, 409 buffer[i].timestamp); 410 } 411 } 412 if (env->ExceptionCheck()) { 413 mSensorQueue->sendAck(buffer, n); 414 ALOGE("Exception dispatching input event."); 415 return 1; 416 } 417 } 418 mSensorQueue->sendAck(buffer, n); 419 } 420 if (n<0 && n != -EAGAIN) { 421 // FIXME: error receiving events, what to do in this case? 422 } 423 return 1; 424 } 425}; 426 427static jlong nativeInitSensorEventQueue(JNIEnv *env, jclass clazz, jlong sensorManager, 428 jobject eventQWeak, jobject msgQ, jstring packageName, jint mode) { 429 SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); 430 ScopedUtfChars packageUtf(env, packageName); 431 String8 clientName(packageUtf.c_str()); 432 sp<SensorEventQueue> queue(mgr->createEventQueue(clientName, mode)); 433 434 sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, msgQ); 435 if (messageQueue == NULL) { 436 jniThrowRuntimeException(env, "MessageQueue is not initialized."); 437 return 0; 438 } 439 440 sp<Receiver> receiver = new Receiver(queue, messageQueue, eventQWeak); 441 receiver->incStrong((void*)nativeInitSensorEventQueue); 442 return jlong(receiver.get()); 443} 444 445static jint nativeEnableSensor(JNIEnv *env, jclass clazz, jlong eventQ, jint handle, jint rate_us, 446 jint maxBatchReportLatency) { 447 sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ)); 448 return receiver->getSensorEventQueue()->enableSensor(handle, rate_us, maxBatchReportLatency, 449 0); 450} 451 452static jint nativeDisableSensor(JNIEnv *env, jclass clazz, jlong eventQ, jint handle) { 453 sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ)); 454 return receiver->getSensorEventQueue()->disableSensor(handle); 455} 456 457static void nativeDestroySensorEventQueue(JNIEnv *env, jclass clazz, jlong eventQ) { 458 sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ)); 459 receiver->destroy(); 460 receiver->decStrong((void*)nativeInitSensorEventQueue); 461} 462 463static jint nativeFlushSensor(JNIEnv *env, jclass clazz, jlong eventQ) { 464 sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ)); 465 return receiver->getSensorEventQueue()->flush(); 466} 467 468static jint nativeInjectSensorData(JNIEnv *env, jclass clazz, jlong eventQ, jint handle, 469 jfloatArray values, jint accuracy, jlong timestamp) { 470 sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ)); 471 // Create a sensor_event from the above data which can be injected into the HAL. 472 ASensorEvent sensor_event; 473 memset(&sensor_event, 0, sizeof(sensor_event)); 474 sensor_event.sensor = handle; 475 sensor_event.timestamp = timestamp; 476 env->GetFloatArrayRegion(values, 0, env->GetArrayLength(values), sensor_event.data); 477 return receiver->getSensorEventQueue()->injectSensorEvent(sensor_event); 478} 479//---------------------------------------------------------------------------- 480 481static const JNINativeMethod gSystemSensorManagerMethods[] = { 482 {"nativeClassInit", 483 "()V", 484 (void*)nativeClassInit }, 485 {"nativeCreate", 486 "(Ljava/lang/String;)J", 487 (void*)nativeCreate }, 488 489 {"nativeGetSensorAtIndex", 490 "(JLandroid/hardware/Sensor;I)Z", 491 (void*)nativeGetSensorAtIndex }, 492 493 {"nativeGetDynamicSensors", 494 "(JLjava/util/List;)V", 495 (void*)nativeGetDynamicSensors }, 496 497 {"nativeIsDataInjectionEnabled", 498 "(J)Z", 499 (void*)nativeIsDataInjectionEnabled }, 500 501 {"nativeCreateDirectChannel", 502 "(JJI[J)I", 503 (void*)nativeCreateDirectChannel }, 504 505 {"nativeDestroyDirectChannel", 506 "(JI)V", 507 (void*)nativeDestroyDirectChannel }, 508 509 {"nativeConfigDirectChannel", 510 "(JIII)I", 511 (void*)nativeConfigDirectChannel }, 512}; 513 514static const JNINativeMethod gBaseEventQueueMethods[] = { 515 {"nativeInitBaseEventQueue", 516 "(JLjava/lang/ref/WeakReference;Landroid/os/MessageQueue;Ljava/lang/String;ILjava/lang/String;)J", 517 (void*)nativeInitSensorEventQueue }, 518 519 {"nativeEnableSensor", 520 "(JIII)I", 521 (void*)nativeEnableSensor }, 522 523 {"nativeDisableSensor", 524 "(JI)I", 525 (void*)nativeDisableSensor }, 526 527 {"nativeDestroySensorEventQueue", 528 "(J)V", 529 (void*)nativeDestroySensorEventQueue }, 530 531 {"nativeFlushSensor", 532 "(J)I", 533 (void*)nativeFlushSensor }, 534 535 {"nativeInjectSensorData", 536 "(JI[FIJ)I", 537 (void*)nativeInjectSensorData }, 538}; 539 540} //unnamed namespace 541 542int register_android_hardware_SensorManager(JNIEnv *env) 543{ 544 RegisterMethodsOrDie(env, "android/hardware/SystemSensorManager", 545 gSystemSensorManagerMethods, NELEM(gSystemSensorManagerMethods)); 546 547 RegisterMethodsOrDie(env, "android/hardware/SystemSensorManager$BaseEventQueue", 548 gBaseEventQueueMethods, NELEM(gBaseEventQueueMethods)); 549 550 gBaseEventQueueClassInfo.clazz = FindClassOrDie(env, 551 "android/hardware/SystemSensorManager$BaseEventQueue"); 552 553 gBaseEventQueueClassInfo.dispatchSensorEvent = GetMethodIDOrDie(env, 554 gBaseEventQueueClassInfo.clazz, "dispatchSensorEvent", "(I[FIJ)V"); 555 556 gBaseEventQueueClassInfo.dispatchFlushCompleteEvent = GetMethodIDOrDie(env, 557 gBaseEventQueueClassInfo.clazz, "dispatchFlushCompleteEvent", "(I)V"); 558 559 gBaseEventQueueClassInfo.dispatchAdditionalInfoEvent = GetMethodIDOrDie(env, 560 gBaseEventQueueClassInfo.clazz, "dispatchAdditionalInfoEvent", "(III[F[I)V"); 561 562 return 0; 563} 564