1 /* 2 * Copyright 2013, 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_NDEBUG 0 18 #define LOG_TAG "MediaDrm-JNI" 19 #include <utils/Log.h> 20 21 #include "android_media_MediaDrm.h" 22 23 #include "android_runtime/AndroidRuntime.h" 24 #include "android_runtime/Log.h" 25 #include "android_os_Parcel.h" 26 #include "jni.h" 27 #include "JNIHelp.h" 28 29 #include <binder/IServiceManager.h> 30 #include <binder/Parcel.h> 31 #include <media/IDrm.h> 32 #include <media/IMediaPlayerService.h> 33 #include <media/stagefright/foundation/ADebug.h> 34 #include <media/stagefright/MediaErrors.h> 35 36 namespace android { 37 38 #define FIND_CLASS(var, className) \ 39 var = env->FindClass(className); \ 40 LOG_FATAL_IF(! var, "Unable to find class " className); 41 42 #define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ 43 var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ 44 LOG_FATAL_IF(! var, "Unable to find field " fieldName); 45 46 #define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ 47 var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \ 48 LOG_FATAL_IF(! var, "Unable to find method " fieldName); 49 50 #define GET_STATIC_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ 51 var = env->GetStaticFieldID(clazz, fieldName, fieldDescriptor); \ 52 LOG_FATAL_IF(! var, "Unable to find field " fieldName); 53 54 #define GET_STATIC_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ 55 var = env->GetStaticMethodID(clazz, fieldName, fieldDescriptor); \ 56 LOG_FATAL_IF(! var, "Unable to find static method " fieldName); 57 58 59 struct RequestFields { 60 jfieldID data; 61 jfieldID defaultUrl; 62 }; 63 64 struct ArrayListFields { 65 jmethodID init; 66 jmethodID add; 67 }; 68 69 struct HashmapFields { 70 jmethodID init; 71 jmethodID get; 72 jmethodID put; 73 jmethodID entrySet; 74 }; 75 76 struct SetFields { 77 jmethodID iterator; 78 }; 79 80 struct IteratorFields { 81 jmethodID next; 82 jmethodID hasNext; 83 }; 84 85 struct EntryFields { 86 jmethodID getKey; 87 jmethodID getValue; 88 }; 89 90 struct EventTypes { 91 jint kEventProvisionRequired; 92 jint kEventKeyRequired; 93 jint kEventKeyExpired; 94 jint kEventVendorDefined; 95 } gEventTypes; 96 97 struct KeyTypes { 98 jint kKeyTypeStreaming; 99 jint kKeyTypeOffline; 100 jint kKeyTypeRelease; 101 } gKeyTypes; 102 103 struct fields_t { 104 jfieldID context; 105 jmethodID post_event; 106 RequestFields keyRequest; 107 RequestFields provisionRequest; 108 ArrayListFields arraylist; 109 HashmapFields hashmap; 110 SetFields set; 111 IteratorFields iterator; 112 EntryFields entry; 113 }; 114 115 static fields_t gFields; 116 117 // ---------------------------------------------------------------------------- 118 // ref-counted object for callbacks 119 class JNIDrmListener: public DrmListener 120 { 121 public: 122 JNIDrmListener(JNIEnv* env, jobject thiz, jobject weak_thiz); 123 ~JNIDrmListener(); 124 virtual void notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj = NULL); 125 private: 126 JNIDrmListener(); 127 jclass mClass; // Reference to MediaDrm class 128 jobject mObject; // Weak ref to MediaDrm Java object to call on 129 }; 130 131 JNIDrmListener::JNIDrmListener(JNIEnv* env, jobject thiz, jobject weak_thiz) 132 { 133 // Hold onto the MediaDrm class for use in calling the static method 134 // that posts events to the application thread. 135 jclass clazz = env->GetObjectClass(thiz); 136 if (clazz == NULL) { 137 ALOGE("Can't find android/media/MediaDrm"); 138 jniThrowException(env, "java/lang/Exception", 139 "Can't find android/media/MediaDrm"); 140 return; 141 } 142 mClass = (jclass)env->NewGlobalRef(clazz); 143 144 // We use a weak reference so the MediaDrm object can be garbage collected. 145 // The reference is only used as a proxy for callbacks. 146 mObject = env->NewGlobalRef(weak_thiz); 147 } 148 149 JNIDrmListener::~JNIDrmListener() 150 { 151 // remove global references 152 JNIEnv *env = AndroidRuntime::getJNIEnv(); 153 env->DeleteGlobalRef(mObject); 154 env->DeleteGlobalRef(mClass); 155 } 156 157 void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra, 158 const Parcel *obj) 159 { 160 jint jeventType; 161 162 // translate DrmPlugin event types into their java equivalents 163 switch(eventType) { 164 case DrmPlugin::kDrmPluginEventProvisionRequired: 165 jeventType = gEventTypes.kEventProvisionRequired; 166 break; 167 case DrmPlugin::kDrmPluginEventKeyNeeded: 168 jeventType = gEventTypes.kEventKeyRequired; 169 break; 170 case DrmPlugin::kDrmPluginEventKeyExpired: 171 jeventType = gEventTypes.kEventKeyExpired; 172 break; 173 case DrmPlugin::kDrmPluginEventVendorDefined: 174 jeventType = gEventTypes.kEventVendorDefined; 175 break; 176 default: 177 ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType); 178 return; 179 } 180 181 JNIEnv *env = AndroidRuntime::getJNIEnv(); 182 if (obj && obj->dataSize() > 0) { 183 jobject jParcel = createJavaParcelObject(env); 184 if (jParcel != NULL) { 185 Parcel* nativeParcel = parcelForJavaObject(env, jParcel); 186 nativeParcel->setData(obj->data(), obj->dataSize()); 187 env->CallStaticVoidMethod(mClass, gFields.post_event, mObject, 188 jeventType, extra, jParcel); 189 } 190 } 191 192 if (env->ExceptionCheck()) { 193 ALOGW("An exception occurred while notifying an event."); 194 LOGW_EX(env); 195 env->ExceptionClear(); 196 } 197 } 198 199 200 static bool throwExceptionAsNecessary( 201 JNIEnv *env, status_t err, const char *msg = NULL) { 202 203 const char *drmMessage = NULL; 204 205 switch(err) { 206 case ERROR_DRM_UNKNOWN: 207 drmMessage = "General DRM error"; 208 break; 209 case ERROR_DRM_NO_LICENSE: 210 drmMessage = "No license"; 211 break; 212 case ERROR_DRM_LICENSE_EXPIRED: 213 drmMessage = "License expired"; 214 break; 215 case ERROR_DRM_SESSION_NOT_OPENED: 216 drmMessage = "Session not opened"; 217 break; 218 case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED: 219 drmMessage = "Not initialized"; 220 break; 221 case ERROR_DRM_DECRYPT: 222 drmMessage = "Decrypt error"; 223 break; 224 case ERROR_DRM_CANNOT_HANDLE: 225 drmMessage = "Unsupported scheme or data format"; 226 break; 227 case ERROR_DRM_TAMPER_DETECTED: 228 drmMessage = "Invalid state"; 229 break; 230 default: 231 break; 232 } 233 234 String8 vendorMessage; 235 if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) { 236 vendorMessage.format("DRM vendor-defined error: %d", err); 237 drmMessage = vendorMessage.string(); 238 } 239 240 if (err == BAD_VALUE) { 241 jniThrowException(env, "java/lang/IllegalArgumentException", msg); 242 return true; 243 } else if (err == ERROR_DRM_NOT_PROVISIONED) { 244 jniThrowException(env, "android/media/NotProvisionedException", msg); 245 return true; 246 } else if (err == ERROR_DRM_RESOURCE_BUSY) { 247 jniThrowException(env, "android/media/ResourceBusyException", msg); 248 return true; 249 } else if (err == ERROR_DRM_DEVICE_REVOKED) { 250 jniThrowException(env, "android/media/DeniedByServerException", msg); 251 return true; 252 } else if (err != OK) { 253 String8 errbuf; 254 if (drmMessage != NULL) { 255 if (msg == NULL) { 256 msg = drmMessage; 257 } else { 258 errbuf.format("%s: %s", msg, drmMessage); 259 msg = errbuf.string(); 260 } 261 } 262 ALOGE("Illegal state exception: %s", msg); 263 jniThrowException(env, "java/lang/IllegalStateException", msg); 264 return true; 265 } 266 return false; 267 } 268 269 static sp<IDrm> GetDrm(JNIEnv *env, jobject thiz) { 270 JDrm *jdrm = (JDrm *)env->GetIntField(thiz, gFields.context); 271 return jdrm ? jdrm->getDrm() : NULL; 272 } 273 274 JDrm::JDrm( 275 JNIEnv *env, jobject thiz, const uint8_t uuid[16]) { 276 mObject = env->NewWeakGlobalRef(thiz); 277 mDrm = MakeDrm(uuid); 278 if (mDrm != NULL) { 279 mDrm->setListener(this); 280 } 281 } 282 283 JDrm::~JDrm() { 284 mDrm.clear(); 285 286 JNIEnv *env = AndroidRuntime::getJNIEnv(); 287 288 env->DeleteWeakGlobalRef(mObject); 289 mObject = NULL; 290 } 291 292 // static 293 sp<IDrm> JDrm::MakeDrm() { 294 sp<IServiceManager> sm = defaultServiceManager(); 295 296 sp<IBinder> binder = 297 sm->getService(String16("media.player")); 298 299 sp<IMediaPlayerService> service = 300 interface_cast<IMediaPlayerService>(binder); 301 302 if (service == NULL) { 303 return NULL; 304 } 305 306 sp<IDrm> drm = service->makeDrm(); 307 308 if (drm == NULL || (drm->initCheck() != OK && drm->initCheck() != NO_INIT)) { 309 return NULL; 310 } 311 312 return drm; 313 } 314 315 // static 316 sp<IDrm> JDrm::MakeDrm(const uint8_t uuid[16]) { 317 sp<IDrm> drm = MakeDrm(); 318 319 if (drm == NULL) { 320 return NULL; 321 } 322 323 status_t err = drm->createPlugin(uuid); 324 325 if (err != OK) { 326 return NULL; 327 } 328 329 return drm; 330 } 331 332 status_t JDrm::setListener(const sp<DrmListener>& listener) { 333 Mutex::Autolock lock(mLock); 334 mListener = listener; 335 return OK; 336 } 337 338 void JDrm::notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj) { 339 sp<DrmListener> listener; 340 mLock.lock(); 341 listener = mListener; 342 mLock.unlock(); 343 344 if (listener != NULL) { 345 Mutex::Autolock lock(mNotifyLock); 346 listener->notify(eventType, extra, obj); 347 } 348 } 349 350 351 // static 352 bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) { 353 sp<IDrm> drm = MakeDrm(); 354 355 if (drm == NULL) { 356 return false; 357 } 358 359 return drm->isCryptoSchemeSupported(uuid, mimeType); 360 } 361 362 status_t JDrm::initCheck() const { 363 return mDrm == NULL ? NO_INIT : OK; 364 } 365 366 // JNI conversion utilities 367 static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray) { 368 Vector<uint8_t> vector; 369 size_t length = env->GetArrayLength(byteArray); 370 vector.insertAt((size_t)0, length); 371 env->GetByteArrayRegion(byteArray, 0, length, (jbyte *)vector.editArray()); 372 return vector; 373 } 374 375 static jbyteArray VectorToJByteArray(JNIEnv *env, Vector<uint8_t> const &vector) { 376 size_t length = vector.size(); 377 jbyteArray result = env->NewByteArray(length); 378 if (result != NULL) { 379 env->SetByteArrayRegion(result, 0, length, (jbyte *)vector.array()); 380 } 381 return result; 382 } 383 384 static String8 JStringToString8(JNIEnv *env, jstring const &jstr) { 385 String8 result; 386 387 const char *s = env->GetStringUTFChars(jstr, NULL); 388 if (s) { 389 result = s; 390 env->ReleaseStringUTFChars(jstr, s); 391 } 392 return result; 393 } 394 395 /* 396 import java.util.HashMap; 397 import java.util.Set; 398 import java.Map.Entry; 399 import jav.util.Iterator; 400 401 HashMap<k, v> hm; 402 Set<Entry<k, v> > s = hm.entrySet(); 403 Iterator i = s.iterator(); 404 Entry e = s.next(); 405 */ 406 407 static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env, jobject &hashMap) { 408 jclass clazz; 409 FIND_CLASS(clazz, "java/lang/String"); 410 KeyedVector<String8, String8> keyedVector; 411 412 jobject entrySet = env->CallObjectMethod(hashMap, gFields.hashmap.entrySet); 413 if (entrySet) { 414 jobject iterator = env->CallObjectMethod(entrySet, gFields.set.iterator); 415 if (iterator) { 416 jboolean hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext); 417 while (hasNext) { 418 jobject entry = env->CallObjectMethod(iterator, gFields.iterator.next); 419 if (entry) { 420 jobject obj = env->CallObjectMethod(entry, gFields.entry.getKey); 421 if (!env->IsInstanceOf(obj, clazz)) { 422 jniThrowException(env, "java/lang/IllegalArgumentException", 423 "HashMap key is not a String"); 424 } 425 jstring jkey = static_cast<jstring>(obj); 426 427 obj = env->CallObjectMethod(entry, gFields.entry.getValue); 428 if (!env->IsInstanceOf(obj, clazz)) { 429 jniThrowException(env, "java/lang/IllegalArgumentException", 430 "HashMap value is not a String"); 431 } 432 jstring jvalue = static_cast<jstring>(obj); 433 434 String8 key = JStringToString8(env, jkey); 435 String8 value = JStringToString8(env, jvalue); 436 keyedVector.add(key, value); 437 438 env->DeleteLocalRef(jkey); 439 env->DeleteLocalRef(jvalue); 440 hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext); 441 } 442 env->DeleteLocalRef(entry); 443 } 444 env->DeleteLocalRef(iterator); 445 } 446 env->DeleteLocalRef(entrySet); 447 } 448 return keyedVector; 449 } 450 451 static jobject KeyedVectorToHashMap (JNIEnv *env, KeyedVector<String8, String8> const &map) { 452 jclass clazz; 453 FIND_CLASS(clazz, "java/util/HashMap"); 454 jobject hashMap = env->NewObject(clazz, gFields.hashmap.init); 455 for (size_t i = 0; i < map.size(); ++i) { 456 jstring jkey = env->NewStringUTF(map.keyAt(i).string()); 457 jstring jvalue = env->NewStringUTF(map.valueAt(i).string()); 458 env->CallObjectMethod(hashMap, gFields.hashmap.put, jkey, jvalue); 459 env->DeleteLocalRef(jkey); 460 env->DeleteLocalRef(jvalue); 461 } 462 return hashMap; 463 } 464 465 static jobject ListOfVectorsToArrayListOfByteArray(JNIEnv *env, 466 List<Vector<uint8_t> > list) { 467 jclass clazz; 468 FIND_CLASS(clazz, "java/util/ArrayList"); 469 jobject arrayList = env->NewObject(clazz, gFields.arraylist.init); 470 List<Vector<uint8_t> >::iterator iter = list.begin(); 471 while (iter != list.end()) { 472 jbyteArray byteArray = VectorToJByteArray(env, *iter); 473 env->CallBooleanMethod(arrayList, gFields.arraylist.add, byteArray); 474 env->DeleteLocalRef(byteArray); 475 iter++; 476 } 477 478 return arrayList; 479 } 480 481 } // namespace android 482 483 using namespace android; 484 485 static sp<JDrm> setDrm( 486 JNIEnv *env, jobject thiz, const sp<JDrm> &drm) { 487 sp<JDrm> old = (JDrm *)env->GetIntField(thiz, gFields.context); 488 if (drm != NULL) { 489 drm->incStrong(thiz); 490 } 491 if (old != NULL) { 492 old->decStrong(thiz); 493 } 494 env->SetIntField(thiz, gFields.context, (int)drm.get()); 495 496 return old; 497 } 498 499 static bool CheckSession(JNIEnv *env, const sp<IDrm> &drm, jbyteArray const &jsessionId) 500 { 501 if (drm == NULL) { 502 jniThrowException(env, "java/lang/IllegalStateException", "MediaDrm obj is null"); 503 return false; 504 } 505 506 if (jsessionId == NULL) { 507 jniThrowException(env, "java/lang/IllegalArgumentException", "sessionId is null"); 508 return false; 509 } 510 return true; 511 } 512 513 static void android_media_MediaDrm_release(JNIEnv *env, jobject thiz) { 514 sp<JDrm> drm = setDrm(env, thiz, NULL); 515 if (drm != NULL) { 516 drm->setListener(NULL); 517 } 518 } 519 520 static void android_media_MediaDrm_native_init(JNIEnv *env) { 521 jclass clazz; 522 FIND_CLASS(clazz, "android/media/MediaDrm"); 523 GET_FIELD_ID(gFields.context, clazz, "mNativeContext", "I"); 524 GET_STATIC_METHOD_ID(gFields.post_event, clazz, "postEventFromNative", 525 "(Ljava/lang/Object;IILjava/lang/Object;)V"); 526 527 jfieldID field; 528 GET_STATIC_FIELD_ID(field, clazz, "EVENT_PROVISION_REQUIRED", "I"); 529 gEventTypes.kEventProvisionRequired = env->GetStaticIntField(clazz, field); 530 GET_STATIC_FIELD_ID(field, clazz, "EVENT_KEY_REQUIRED", "I"); 531 gEventTypes.kEventKeyRequired = env->GetStaticIntField(clazz, field); 532 GET_STATIC_FIELD_ID(field, clazz, "EVENT_KEY_EXPIRED", "I"); 533 gEventTypes.kEventKeyExpired = env->GetStaticIntField(clazz, field); 534 GET_STATIC_FIELD_ID(field, clazz, "EVENT_VENDOR_DEFINED", "I"); 535 gEventTypes.kEventVendorDefined = env->GetStaticIntField(clazz, field); 536 537 GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I"); 538 gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field); 539 GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_OFFLINE", "I"); 540 gKeyTypes.kKeyTypeOffline = env->GetStaticIntField(clazz, field); 541 GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_RELEASE", "I"); 542 gKeyTypes.kKeyTypeRelease = env->GetStaticIntField(clazz, field); 543 544 FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest"); 545 GET_FIELD_ID(gFields.keyRequest.data, clazz, "mData", "[B"); 546 GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;"); 547 548 FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest"); 549 GET_FIELD_ID(gFields.provisionRequest.data, clazz, "mData", "[B"); 550 GET_FIELD_ID(gFields.provisionRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;"); 551 552 FIND_CLASS(clazz, "java/util/ArrayList"); 553 GET_METHOD_ID(gFields.arraylist.init, clazz, "<init>", "()V"); 554 GET_METHOD_ID(gFields.arraylist.add, clazz, "add", "(Ljava/lang/Object;)Z"); 555 556 FIND_CLASS(clazz, "java/util/HashMap"); 557 GET_METHOD_ID(gFields.hashmap.init, clazz, "<init>", "()V"); 558 GET_METHOD_ID(gFields.hashmap.get, clazz, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); 559 GET_METHOD_ID(gFields.hashmap.put, clazz, "put", 560 "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); 561 GET_METHOD_ID(gFields.hashmap.entrySet, clazz, "entrySet", "()Ljava/util/Set;"); 562 563 FIND_CLASS(clazz, "java/util/Set"); 564 GET_METHOD_ID(gFields.set.iterator, clazz, "iterator", "()Ljava/util/Iterator;"); 565 566 FIND_CLASS(clazz, "java/util/Iterator"); 567 GET_METHOD_ID(gFields.iterator.next, clazz, "next", "()Ljava/lang/Object;"); 568 GET_METHOD_ID(gFields.iterator.hasNext, clazz, "hasNext", "()Z"); 569 570 FIND_CLASS(clazz, "java/util/Map$Entry"); 571 GET_METHOD_ID(gFields.entry.getKey, clazz, "getKey", "()Ljava/lang/Object;"); 572 GET_METHOD_ID(gFields.entry.getValue, clazz, "getValue", "()Ljava/lang/Object;"); 573 } 574 575 static void android_media_MediaDrm_native_setup( 576 JNIEnv *env, jobject thiz, 577 jobject weak_this, jbyteArray uuidObj) { 578 579 if (uuidObj == NULL) { 580 jniThrowException(env, "java/lang/IllegalArgumentException", "uuid is null"); 581 return; 582 } 583 584 Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj); 585 586 if (uuid.size() != 16) { 587 jniThrowException(env, "java/lang/IllegalArgumentException", 588 "invalid UUID size, expected 16 bytes"); 589 return; 590 } 591 592 sp<JDrm> drm = new JDrm(env, thiz, uuid.array()); 593 594 status_t err = drm->initCheck(); 595 596 if (err != OK) { 597 jniThrowException( 598 env, 599 "android/media/UnsupportedSchemeException", 600 "Failed to instantiate drm object."); 601 return; 602 } 603 604 sp<JNIDrmListener> listener = new JNIDrmListener(env, thiz, weak_this); 605 drm->setListener(listener); 606 setDrm(env, thiz, drm); 607 } 608 609 static void android_media_MediaDrm_native_finalize( 610 JNIEnv *env, jobject thiz) { 611 android_media_MediaDrm_release(env, thiz); 612 } 613 614 static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( 615 JNIEnv *env, jobject thiz, jbyteArray uuidObj, jstring jmimeType) { 616 617 if (uuidObj == NULL) { 618 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 619 return false; 620 } 621 622 Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj); 623 624 if (uuid.size() != 16) { 625 jniThrowException( 626 env, 627 "java/lang/IllegalArgumentException", 628 "invalid UUID size, expected 16 bytes"); 629 return false; 630 } 631 632 String8 mimeType; 633 if (jmimeType != NULL) { 634 mimeType = JStringToString8(env, jmimeType); 635 } 636 637 return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType); 638 } 639 640 static jbyteArray android_media_MediaDrm_openSession( 641 JNIEnv *env, jobject thiz) { 642 sp<IDrm> drm = GetDrm(env, thiz); 643 644 if (drm == NULL) { 645 jniThrowException(env, "java/lang/IllegalStateException", 646 "MediaDrm obj is null"); 647 return NULL; 648 } 649 650 Vector<uint8_t> sessionId; 651 status_t err = drm->openSession(sessionId); 652 653 if (throwExceptionAsNecessary(env, err, "Failed to open session")) { 654 return NULL; 655 } 656 657 return VectorToJByteArray(env, sessionId); 658 } 659 660 static void android_media_MediaDrm_closeSession( 661 JNIEnv *env, jobject thiz, jbyteArray jsessionId) { 662 sp<IDrm> drm = GetDrm(env, thiz); 663 664 if (!CheckSession(env, drm, jsessionId)) { 665 return; 666 } 667 668 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 669 670 status_t err = drm->closeSession(sessionId); 671 672 throwExceptionAsNecessary(env, err, "Failed to close session"); 673 } 674 675 static jobject android_media_MediaDrm_getKeyRequest( 676 JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jinitData, 677 jstring jmimeType, jint jkeyType, jobject joptParams) { 678 sp<IDrm> drm = GetDrm(env, thiz); 679 680 if (!CheckSession(env, drm, jsessionId)) { 681 return NULL; 682 } 683 684 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 685 686 Vector<uint8_t> initData; 687 if (jinitData != NULL) { 688 initData = JByteArrayToVector(env, jinitData); 689 } 690 691 String8 mimeType; 692 if (jmimeType != NULL) { 693 mimeType = JStringToString8(env, jmimeType); 694 } 695 696 DrmPlugin::KeyType keyType; 697 if (jkeyType == gKeyTypes.kKeyTypeStreaming) { 698 keyType = DrmPlugin::kKeyType_Streaming; 699 } else if (jkeyType == gKeyTypes.kKeyTypeOffline) { 700 keyType = DrmPlugin::kKeyType_Offline; 701 } else if (jkeyType == gKeyTypes.kKeyTypeRelease) { 702 keyType = DrmPlugin::kKeyType_Release; 703 } else { 704 jniThrowException(env, "java/lang/IllegalArgumentException", 705 "invalid keyType"); 706 return NULL; 707 } 708 709 KeyedVector<String8, String8> optParams; 710 if (joptParams != NULL) { 711 optParams = HashMapToKeyedVector(env, joptParams); 712 } 713 714 Vector<uint8_t> request; 715 String8 defaultUrl; 716 717 status_t err = drm->getKeyRequest(sessionId, initData, mimeType, 718 keyType, optParams, request, defaultUrl); 719 720 if (throwExceptionAsNecessary(env, err, "Failed to get key request")) { 721 return NULL; 722 } 723 724 // Fill out return obj 725 jclass clazz; 726 FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest"); 727 728 jobject keyObj = NULL; 729 730 if (clazz) { 731 keyObj = env->AllocObject(clazz); 732 jbyteArray jrequest = VectorToJByteArray(env, request); 733 env->SetObjectField(keyObj, gFields.keyRequest.data, jrequest); 734 735 jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string()); 736 env->SetObjectField(keyObj, gFields.keyRequest.defaultUrl, jdefaultUrl); 737 } 738 739 return keyObj; 740 } 741 742 static jbyteArray android_media_MediaDrm_provideKeyResponse( 743 JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jresponse) { 744 sp<IDrm> drm = GetDrm(env, thiz); 745 746 if (!CheckSession(env, drm, jsessionId)) { 747 return NULL; 748 } 749 750 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 751 752 if (jresponse == NULL) { 753 jniThrowException(env, "java/lang/IllegalArgumentException", 754 "key response is null"); 755 return NULL; 756 } 757 Vector<uint8_t> response(JByteArrayToVector(env, jresponse)); 758 Vector<uint8_t> keySetId; 759 760 status_t err = drm->provideKeyResponse(sessionId, response, keySetId); 761 762 if (throwExceptionAsNecessary(env, err, "Failed to handle key response")) { 763 return NULL; 764 } 765 return VectorToJByteArray(env, keySetId); 766 } 767 768 static void android_media_MediaDrm_removeKeys( 769 JNIEnv *env, jobject thiz, jbyteArray jkeysetId) { 770 sp<IDrm> drm = GetDrm(env, thiz); 771 772 if (jkeysetId == NULL) { 773 jniThrowException(env, "java/lang/IllegalArgumentException", 774 "keySetId is null"); 775 return; 776 } 777 778 Vector<uint8_t> keySetId(JByteArrayToVector(env, jkeysetId)); 779 780 status_t err = drm->removeKeys(keySetId); 781 782 throwExceptionAsNecessary(env, err, "Failed to remove keys"); 783 } 784 785 static void android_media_MediaDrm_restoreKeys( 786 JNIEnv *env, jobject thiz, jbyteArray jsessionId, 787 jbyteArray jkeysetId) { 788 789 sp<IDrm> drm = GetDrm(env, thiz); 790 791 if (!CheckSession(env, drm, jsessionId)) { 792 return; 793 } 794 795 if (jkeysetId == NULL) { 796 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 797 return; 798 } 799 800 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 801 Vector<uint8_t> keySetId(JByteArrayToVector(env, jkeysetId)); 802 803 status_t err = drm->restoreKeys(sessionId, keySetId); 804 805 throwExceptionAsNecessary(env, err, "Failed to restore keys"); 806 } 807 808 static jobject android_media_MediaDrm_queryKeyStatus( 809 JNIEnv *env, jobject thiz, jbyteArray jsessionId) { 810 sp<IDrm> drm = GetDrm(env, thiz); 811 812 if (!CheckSession(env, drm, jsessionId)) { 813 return NULL; 814 } 815 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 816 817 KeyedVector<String8, String8> infoMap; 818 819 status_t err = drm->queryKeyStatus(sessionId, infoMap); 820 821 if (throwExceptionAsNecessary(env, err, "Failed to query key status")) { 822 return NULL; 823 } 824 825 return KeyedVectorToHashMap(env, infoMap); 826 } 827 828 static jobject android_media_MediaDrm_getProvisionRequest( 829 JNIEnv *env, jobject thiz) { 830 sp<IDrm> drm = GetDrm(env, thiz); 831 832 if (drm == NULL) { 833 jniThrowException(env, "java/lang/IllegalStateException", 834 "MediaDrm obj is null"); 835 return NULL; 836 } 837 838 Vector<uint8_t> request; 839 String8 defaultUrl; 840 841 status_t err = drm->getProvisionRequest(request, defaultUrl); 842 843 if (throwExceptionAsNecessary(env, err, "Failed to get provision request")) { 844 return NULL; 845 } 846 847 // Fill out return obj 848 jclass clazz; 849 FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest"); 850 851 jobject provisionObj = NULL; 852 853 if (clazz) { 854 provisionObj = env->AllocObject(clazz); 855 jbyteArray jrequest = VectorToJByteArray(env, request); 856 env->SetObjectField(provisionObj, gFields.provisionRequest.data, jrequest); 857 858 jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string()); 859 env->SetObjectField(provisionObj, gFields.provisionRequest.defaultUrl, jdefaultUrl); 860 } 861 862 return provisionObj; 863 } 864 865 static void android_media_MediaDrm_provideProvisionResponse( 866 JNIEnv *env, jobject thiz, jbyteArray jresponse) { 867 sp<IDrm> drm = GetDrm(env, thiz); 868 869 if (drm == NULL) { 870 jniThrowException(env, "java/lang/IllegalStateException", 871 "MediaDrm obj is null"); 872 return; 873 } 874 875 if (jresponse == NULL) { 876 jniThrowException(env, "java/lang/IllegalArgumentException", 877 "provision response is null"); 878 return; 879 } 880 881 Vector<uint8_t> response(JByteArrayToVector(env, jresponse)); 882 883 status_t err = drm->provideProvisionResponse(response); 884 885 throwExceptionAsNecessary(env, err, "Failed to handle provision response"); 886 } 887 888 static jobject android_media_MediaDrm_getSecureStops( 889 JNIEnv *env, jobject thiz) { 890 sp<IDrm> drm = GetDrm(env, thiz); 891 892 if (drm == NULL) { 893 jniThrowException(env, "java/lang/IllegalStateException", 894 "MediaDrm obj is null"); 895 return NULL; 896 } 897 898 List<Vector<uint8_t> > secureStops; 899 900 status_t err = drm->getSecureStops(secureStops); 901 902 if (throwExceptionAsNecessary(env, err, "Failed to get secure stops")) { 903 return NULL; 904 } 905 906 return ListOfVectorsToArrayListOfByteArray(env, secureStops); 907 } 908 909 static void android_media_MediaDrm_releaseSecureStops( 910 JNIEnv *env, jobject thiz, jbyteArray jssRelease) { 911 sp<IDrm> drm = GetDrm(env, thiz); 912 913 if (drm == NULL) { 914 jniThrowException(env, "java/lang/IllegalStateException", 915 "MediaDrm obj is null"); 916 return; 917 } 918 919 Vector<uint8_t> ssRelease(JByteArrayToVector(env, jssRelease)); 920 921 status_t err = drm->releaseSecureStops(ssRelease); 922 923 throwExceptionAsNecessary(env, err, "Failed to release secure stops"); 924 } 925 926 static jstring android_media_MediaDrm_getPropertyString( 927 JNIEnv *env, jobject thiz, jstring jname) { 928 sp<IDrm> drm = GetDrm(env, thiz); 929 930 if (drm == NULL) { 931 jniThrowException(env, "java/lang/IllegalStateException", 932 "MediaDrm obj is null"); 933 return NULL; 934 } 935 936 if (jname == NULL) { 937 jniThrowException(env, "java/lang/IllegalArgumentException", 938 "property name String is null"); 939 return NULL; 940 } 941 942 String8 name = JStringToString8(env, jname); 943 String8 value; 944 945 status_t err = drm->getPropertyString(name, value); 946 947 if (throwExceptionAsNecessary(env, err, "Failed to get property")) { 948 return NULL; 949 } 950 951 return env->NewStringUTF(value.string()); 952 } 953 954 static jbyteArray android_media_MediaDrm_getPropertyByteArray( 955 JNIEnv *env, jobject thiz, jstring jname) { 956 sp<IDrm> drm = GetDrm(env, thiz); 957 958 if (drm == NULL) { 959 jniThrowException(env, "java/lang/IllegalStateException", 960 "MediaDrm obj is null"); 961 return NULL; 962 } 963 964 if (jname == NULL) { 965 jniThrowException(env, "java/lang/IllegalArgumentException", 966 "property name String is null"); 967 return NULL; 968 } 969 970 String8 name = JStringToString8(env, jname); 971 Vector<uint8_t> value; 972 973 status_t err = drm->getPropertyByteArray(name, value); 974 975 if (throwExceptionAsNecessary(env, err, "Failed to get property")) { 976 return NULL; 977 } 978 979 return VectorToJByteArray(env, value); 980 } 981 982 static void android_media_MediaDrm_setPropertyString( 983 JNIEnv *env, jobject thiz, jstring jname, jstring jvalue) { 984 sp<IDrm> drm = GetDrm(env, thiz); 985 986 if (drm == NULL) { 987 jniThrowException(env, "java/lang/IllegalStateException", 988 "MediaDrm obj is null"); 989 return; 990 } 991 992 if (jname == NULL) { 993 jniThrowException(env, "java/lang/IllegalArgumentException", 994 "property name String is null"); 995 return; 996 } 997 998 if (jvalue == NULL) { 999 jniThrowException(env, "java/lang/IllegalArgumentException", 1000 "property value String is null"); 1001 return; 1002 } 1003 1004 String8 name = JStringToString8(env, jname); 1005 String8 value = JStringToString8(env, jvalue); 1006 1007 status_t err = drm->setPropertyString(name, value); 1008 1009 throwExceptionAsNecessary(env, err, "Failed to set property"); 1010 } 1011 1012 static void android_media_MediaDrm_setPropertyByteArray( 1013 JNIEnv *env, jobject thiz, jstring jname, jbyteArray jvalue) { 1014 sp<IDrm> drm = GetDrm(env, thiz); 1015 1016 if (drm == NULL) { 1017 jniThrowException(env, "java/lang/IllegalStateException", 1018 "MediaDrm obj is null"); 1019 return; 1020 } 1021 1022 if (jname == NULL) { 1023 jniThrowException(env, "java/lang/IllegalArgumentException", 1024 "property name String is null"); 1025 return; 1026 } 1027 1028 if (jvalue == NULL) { 1029 jniThrowException(env, "java/lang/IllegalArgumentException", 1030 "property value byte array is null"); 1031 return; 1032 } 1033 1034 String8 name = JStringToString8(env, jname); 1035 Vector<uint8_t> value = JByteArrayToVector(env, jvalue); 1036 1037 status_t err = drm->setPropertyByteArray(name, value); 1038 1039 throwExceptionAsNecessary(env, err, "Failed to set property"); 1040 } 1041 1042 static void android_media_MediaDrm_setCipherAlgorithmNative( 1043 JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, 1044 jstring jalgorithm) { 1045 1046 sp<IDrm> drm = GetDrm(env, jdrm); 1047 1048 if (!CheckSession(env, drm, jsessionId)) { 1049 return; 1050 } 1051 1052 if (jalgorithm == NULL) { 1053 jniThrowException(env, "java/lang/IllegalArgumentException", 1054 "algorithm String is null"); 1055 return; 1056 } 1057 1058 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 1059 String8 algorithm = JStringToString8(env, jalgorithm); 1060 1061 status_t err = drm->setCipherAlgorithm(sessionId, algorithm); 1062 1063 throwExceptionAsNecessary(env, err, "Failed to set cipher algorithm"); 1064 } 1065 1066 static void android_media_MediaDrm_setMacAlgorithmNative( 1067 JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, 1068 jstring jalgorithm) { 1069 1070 sp<IDrm> drm = GetDrm(env, jdrm); 1071 1072 if (!CheckSession(env, drm, jsessionId)) { 1073 return; 1074 } 1075 1076 if (jalgorithm == NULL) { 1077 jniThrowException(env, "java/lang/IllegalArgumentException", 1078 "algorithm String is null"); 1079 return; 1080 } 1081 1082 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 1083 String8 algorithm = JStringToString8(env, jalgorithm); 1084 1085 status_t err = drm->setMacAlgorithm(sessionId, algorithm); 1086 1087 throwExceptionAsNecessary(env, err, "Failed to set mac algorithm"); 1088 } 1089 1090 1091 static jbyteArray android_media_MediaDrm_encryptNative( 1092 JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, 1093 jbyteArray jkeyId, jbyteArray jinput, jbyteArray jiv) { 1094 1095 sp<IDrm> drm = GetDrm(env, jdrm); 1096 1097 if (!CheckSession(env, drm, jsessionId)) { 1098 return NULL; 1099 } 1100 1101 if (jkeyId == NULL || jinput == NULL || jiv == NULL) { 1102 jniThrowException(env, "java/lang/IllegalArgumentException", 1103 "required argument is null"); 1104 return NULL; 1105 } 1106 1107 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 1108 Vector<uint8_t> keyId(JByteArrayToVector(env, jkeyId)); 1109 Vector<uint8_t> input(JByteArrayToVector(env, jinput)); 1110 Vector<uint8_t> iv(JByteArrayToVector(env, jiv)); 1111 Vector<uint8_t> output; 1112 1113 status_t err = drm->encrypt(sessionId, keyId, input, iv, output); 1114 1115 if (throwExceptionAsNecessary(env, err, "Failed to encrypt")) { 1116 return NULL; 1117 } 1118 1119 return VectorToJByteArray(env, output); 1120 } 1121 1122 static jbyteArray android_media_MediaDrm_decryptNative( 1123 JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, 1124 jbyteArray jkeyId, jbyteArray jinput, jbyteArray jiv) { 1125 1126 sp<IDrm> drm = GetDrm(env, jdrm); 1127 1128 if (!CheckSession(env, drm, jsessionId)) { 1129 return NULL; 1130 } 1131 1132 if (jkeyId == NULL || jinput == NULL || jiv == NULL) { 1133 jniThrowException(env, "java/lang/IllegalArgumentException", 1134 "required argument is null"); 1135 return NULL; 1136 } 1137 1138 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 1139 Vector<uint8_t> keyId(JByteArrayToVector(env, jkeyId)); 1140 Vector<uint8_t> input(JByteArrayToVector(env, jinput)); 1141 Vector<uint8_t> iv(JByteArrayToVector(env, jiv)); 1142 Vector<uint8_t> output; 1143 1144 status_t err = drm->decrypt(sessionId, keyId, input, iv, output); 1145 if (throwExceptionAsNecessary(env, err, "Failed to decrypt")) { 1146 return NULL; 1147 } 1148 1149 return VectorToJByteArray(env, output); 1150 } 1151 1152 static jbyteArray android_media_MediaDrm_signNative( 1153 JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, 1154 jbyteArray jkeyId, jbyteArray jmessage) { 1155 1156 sp<IDrm> drm = GetDrm(env, jdrm); 1157 1158 if (!CheckSession(env, drm, jsessionId)) { 1159 return NULL; 1160 } 1161 1162 if (jkeyId == NULL || jmessage == NULL) { 1163 jniThrowException(env, "java/lang/IllegalArgumentException", 1164 "required argument is null"); 1165 return NULL; 1166 } 1167 1168 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 1169 Vector<uint8_t> keyId(JByteArrayToVector(env, jkeyId)); 1170 Vector<uint8_t> message(JByteArrayToVector(env, jmessage)); 1171 Vector<uint8_t> signature; 1172 1173 status_t err = drm->sign(sessionId, keyId, message, signature); 1174 1175 if (throwExceptionAsNecessary(env, err, "Failed to sign")) { 1176 return NULL; 1177 } 1178 1179 return VectorToJByteArray(env, signature); 1180 } 1181 1182 static jboolean android_media_MediaDrm_verifyNative( 1183 JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId, 1184 jbyteArray jkeyId, jbyteArray jmessage, jbyteArray jsignature) { 1185 1186 sp<IDrm> drm = GetDrm(env, jdrm); 1187 1188 if (!CheckSession(env, drm, jsessionId)) { 1189 return false; 1190 } 1191 1192 if (jkeyId == NULL || jmessage == NULL || jsignature == NULL) { 1193 jniThrowException(env, "java/lang/IllegalArgumentException", 1194 "required argument is null"); 1195 return false; 1196 } 1197 1198 Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); 1199 Vector<uint8_t> keyId(JByteArrayToVector(env, jkeyId)); 1200 Vector<uint8_t> message(JByteArrayToVector(env, jmessage)); 1201 Vector<uint8_t> signature(JByteArrayToVector(env, jsignature)); 1202 bool match; 1203 1204 status_t err = drm->verify(sessionId, keyId, message, signature, match); 1205 1206 throwExceptionAsNecessary(env, err, "Failed to verify"); 1207 return match; 1208 } 1209 1210 1211 static JNINativeMethod gMethods[] = { 1212 { "release", "()V", (void *)android_media_MediaDrm_release }, 1213 { "native_init", "()V", (void *)android_media_MediaDrm_native_init }, 1214 1215 { "native_setup", "(Ljava/lang/Object;[B)V", 1216 (void *)android_media_MediaDrm_native_setup }, 1217 1218 { "native_finalize", "()V", 1219 (void *)android_media_MediaDrm_native_finalize }, 1220 1221 { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;)Z", 1222 (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative }, 1223 1224 { "openSession", "()[B", 1225 (void *)android_media_MediaDrm_openSession }, 1226 1227 { "closeSession", "([B)V", 1228 (void *)android_media_MediaDrm_closeSession }, 1229 1230 { "getKeyRequest", "([B[BLjava/lang/String;ILjava/util/HashMap;)" 1231 "Landroid/media/MediaDrm$KeyRequest;", 1232 (void *)android_media_MediaDrm_getKeyRequest }, 1233 1234 { "provideKeyResponse", "([B[B)[B", 1235 (void *)android_media_MediaDrm_provideKeyResponse }, 1236 1237 { "removeKeys", "([B)V", 1238 (void *)android_media_MediaDrm_removeKeys }, 1239 1240 { "restoreKeys", "([B[B)V", 1241 (void *)android_media_MediaDrm_restoreKeys }, 1242 1243 { "queryKeyStatus", "([B)Ljava/util/HashMap;", 1244 (void *)android_media_MediaDrm_queryKeyStatus }, 1245 1246 { "getProvisionRequest", "()Landroid/media/MediaDrm$ProvisionRequest;", 1247 (void *)android_media_MediaDrm_getProvisionRequest }, 1248 1249 { "provideProvisionResponse", "([B)V", 1250 (void *)android_media_MediaDrm_provideProvisionResponse }, 1251 1252 { "getSecureStops", "()Ljava/util/List;", 1253 (void *)android_media_MediaDrm_getSecureStops }, 1254 1255 { "releaseSecureStops", "([B)V", 1256 (void *)android_media_MediaDrm_releaseSecureStops }, 1257 1258 { "getPropertyString", "(Ljava/lang/String;)Ljava/lang/String;", 1259 (void *)android_media_MediaDrm_getPropertyString }, 1260 1261 { "getPropertyByteArray", "(Ljava/lang/String;)[B", 1262 (void *)android_media_MediaDrm_getPropertyByteArray }, 1263 1264 { "setPropertyString", "(Ljava/lang/String;Ljava/lang/String;)V", 1265 (void *)android_media_MediaDrm_setPropertyString }, 1266 1267 { "setPropertyByteArray", "(Ljava/lang/String;[B)V", 1268 (void *)android_media_MediaDrm_setPropertyByteArray }, 1269 1270 { "setCipherAlgorithmNative", 1271 "(Landroid/media/MediaDrm;[BLjava/lang/String;)V", 1272 (void *)android_media_MediaDrm_setCipherAlgorithmNative }, 1273 1274 { "setMacAlgorithmNative", 1275 "(Landroid/media/MediaDrm;[BLjava/lang/String;)V", 1276 (void *)android_media_MediaDrm_setMacAlgorithmNative }, 1277 1278 { "encryptNative", "(Landroid/media/MediaDrm;[B[B[B[B)[B", 1279 (void *)android_media_MediaDrm_encryptNative }, 1280 1281 { "decryptNative", "(Landroid/media/MediaDrm;[B[B[B[B)[B", 1282 (void *)android_media_MediaDrm_decryptNative }, 1283 1284 { "signNative", "(Landroid/media/MediaDrm;[B[B[B)[B", 1285 (void *)android_media_MediaDrm_signNative }, 1286 1287 { "verifyNative", "(Landroid/media/MediaDrm;[B[B[B[B)Z", 1288 (void *)android_media_MediaDrm_verifyNative }, 1289 }; 1290 1291 int register_android_media_Drm(JNIEnv *env) { 1292 return AndroidRuntime::registerNativeMethods(env, 1293 "android/media/MediaDrm", gMethods, NELEM(gMethods)); 1294 } 1295 1296