Home | History | Annotate | Download | only in jni
      1 /*
      2  * Copyright (C) 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 
     17 //#define LOG_NDEBUG 0
     18 #define LOG_TAG "JET_JNI"
     19 
     20 
     21 #include <stdio.h>
     22 #include <unistd.h>
     23 #include <fcntl.h>
     24 
     25 #include "jni.h"
     26 #include "JNIHelp.h"
     27 #include "android_runtime/AndroidRuntime.h"
     28 
     29 #include "utils/Log.h"
     30 #include "media/JetPlayer.h"
     31 
     32 
     33 using namespace android;
     34 
     35 // ----------------------------------------------------------------------------
     36 static const char* const kClassPathName = "android/media/JetPlayer";
     37 
     38 // ----------------------------------------------------------------------------
     39 struct fields_t {
     40     // these fields provide access from C++ to the...
     41     jclass    jetClass;              // JetPlayer java class global ref
     42     jmethodID postNativeEventInJava; // java method to post events to the Java thread from native
     43     jfieldID  nativePlayerInJavaObj; // stores in Java the native JetPlayer object
     44 };
     45 
     46 static fields_t javaJetPlayerFields;
     47 
     48 
     49 // ----------------------------------------------------------------------------
     50 // ----------------------------------------------------------------------------
     51 
     52 /*
     53  * This function is called from JetPlayer instance's render thread
     54  */
     55 static void
     56 jetPlayerEventCallback(int what, int arg1=0, int arg2=0, void* javaTarget = NULL)
     57 {
     58     JNIEnv *env = AndroidRuntime::getJNIEnv();
     59     if(env) {
     60         env->CallStaticVoidMethod(
     61             javaJetPlayerFields.jetClass, javaJetPlayerFields.postNativeEventInJava,
     62             javaTarget,
     63             what, arg1, arg2);
     64         if (env->ExceptionCheck()) {
     65             env->ExceptionDescribe();
     66             env->ExceptionClear();
     67         }
     68     } else {
     69         LOGE("JET jetPlayerEventCallback(): No JNI env for JET event callback, can't post event.");
     70         return;
     71     }
     72 }
     73 
     74 
     75 // ----------------------------------------------------------------------------
     76 // ----------------------------------------------------------------------------
     77 
     78 static jboolean
     79 android_media_JetPlayer_setup(JNIEnv *env, jobject thiz, jobject weak_this,
     80     jint maxTracks, jint trackBufferSize)
     81 {
     82     //LOGV("android_media_JetPlayer_setup(): entering.");
     83     JetPlayer* lpJet = new JetPlayer(env->NewGlobalRef(weak_this), maxTracks, trackBufferSize);
     84 
     85     EAS_RESULT result = lpJet->init();
     86 
     87     if(result==EAS_SUCCESS) {
     88         // save our newly created C++ JetPlayer in the "nativePlayerInJavaObj" field
     89         // of the Java object (in mNativePlayerInJavaObj)
     90         env->SetIntField(thiz, javaJetPlayerFields.nativePlayerInJavaObj, (int)lpJet);
     91         return JNI_TRUE;
     92     } else {
     93         LOGE("android_media_JetPlayer_setup(): initialization failed with EAS error code %d", (int)result);
     94         delete lpJet;
     95         env->SetIntField(weak_this, javaJetPlayerFields.nativePlayerInJavaObj, 0);
     96         return JNI_FALSE;
     97     }
     98 }
     99 
    100 
    101 // ----------------------------------------------------------------------------
    102 static void
    103 android_media_JetPlayer_finalize(JNIEnv *env, jobject thiz)
    104 {
    105     LOGV("android_media_JetPlayer_finalize(): entering.");
    106     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    107         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    108     if(lpJet != NULL) {
    109         lpJet->release();
    110         delete lpJet;
    111     }
    112 
    113     LOGV("android_media_JetPlayer_finalize(): exiting.");
    114 }
    115 
    116 
    117 // ----------------------------------------------------------------------------
    118 static void
    119 android_media_JetPlayer_release(JNIEnv *env, jobject thiz)
    120 {
    121     android_media_JetPlayer_finalize(env, thiz);
    122     env->SetIntField(thiz, javaJetPlayerFields.nativePlayerInJavaObj, 0);
    123     LOGV("android_media_JetPlayer_release() done");
    124 }
    125 
    126 
    127 // ----------------------------------------------------------------------------
    128 static jboolean
    129 android_media_JetPlayer_loadFromFile(JNIEnv *env, jobject thiz, jstring path)
    130 {
    131     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    132         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    133     if (lpJet == NULL ) {
    134         jniThrowException(env, "java/lang/IllegalStateException",
    135             "Unable to retrieve JetPlayer pointer for openFile()");
    136     }
    137 
    138     // set up event callback function
    139     lpJet->setEventCallback(jetPlayerEventCallback);
    140 
    141     const char *pathStr = env->GetStringUTFChars(path, NULL);
    142     if (pathStr == NULL) {  // Out of memory
    143         LOGE("android_media_JetPlayer_openFile(): aborting, out of memory");
    144         return JNI_FALSE;
    145     }
    146 
    147     LOGV("android_media_JetPlayer_openFile(): trying to open %s", pathStr );
    148     EAS_RESULT result = lpJet->loadFromFile(pathStr);
    149     env->ReleaseStringUTFChars(path, pathStr);
    150 
    151     if(result==EAS_SUCCESS) {
    152         //LOGV("android_media_JetPlayer_openFile(): file successfully opened");
    153         return JNI_TRUE;
    154     } else {
    155         LOGE("android_media_JetPlayer_openFile(): failed to open file with EAS error %d",
    156             (int)result);
    157         return JNI_FALSE;
    158     }
    159 }
    160 
    161 
    162 // ----------------------------------------------------------------------------
    163 static jboolean
    164 android_media_JetPlayer_loadFromFileD(JNIEnv *env, jobject thiz,
    165     jobject fileDescriptor, jlong offset, jlong length)
    166 {
    167     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    168         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    169     if (lpJet == NULL ) {
    170         jniThrowException(env, "java/lang/IllegalStateException",
    171             "Unable to retrieve JetPlayer pointer for openFile()");
    172     }
    173 
    174     // set up event callback function
    175     lpJet->setEventCallback(jetPlayerEventCallback);
    176 
    177     LOGV("android_media_JetPlayer_openFileDescr(): trying to load JET file through its fd" );
    178     EAS_RESULT result = lpJet->loadFromFD(jniGetFDFromFileDescriptor(env, fileDescriptor),
    179         (long long)offset, (long long)length); // cast params to types used by EAS_FILE
    180 
    181     if(result==EAS_SUCCESS) {
    182         LOGV("android_media_JetPlayer_openFileDescr(): file successfully opened");
    183         return JNI_TRUE;
    184     } else {
    185         LOGE("android_media_JetPlayer_openFileDescr(): failed to open file with EAS error %d",
    186             (int)result);
    187         return JNI_FALSE;
    188     }
    189 }
    190 
    191 
    192 // ----------------------------------------------------------------------------
    193 static jboolean
    194 android_media_JetPlayer_closeFile(JNIEnv *env, jobject thiz)
    195 {
    196     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    197         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    198     if (lpJet == NULL ) {
    199         jniThrowException(env, "java/lang/IllegalStateException",
    200             "Unable to retrieve JetPlayer pointer for closeFile()");
    201     }
    202 
    203     if( lpJet->closeFile()==EAS_SUCCESS) {
    204         //LOGV("android_media_JetPlayer_closeFile(): file successfully closed");
    205         return JNI_TRUE;
    206     } else {
    207         LOGE("android_media_JetPlayer_closeFile(): failed to close file");
    208         return JNI_FALSE;
    209     }
    210 }
    211 
    212 
    213 // ----------------------------------------------------------------------------
    214 static jboolean
    215 android_media_JetPlayer_play(JNIEnv *env, jobject thiz)
    216 {
    217     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    218         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    219     if (lpJet == NULL ) {
    220         jniThrowException(env, "java/lang/IllegalStateException",
    221             "Unable to retrieve JetPlayer pointer for play()");
    222     }
    223 
    224     EAS_RESULT result = lpJet->play();
    225     if( result==EAS_SUCCESS) {
    226         //LOGV("android_media_JetPlayer_play(): play successful");
    227         return JNI_TRUE;
    228     } else {
    229         LOGE("android_media_JetPlayer_play(): failed to play with EAS error code %ld",
    230             result);
    231         return JNI_FALSE;
    232     }
    233 }
    234 
    235 
    236 // ----------------------------------------------------------------------------
    237 static jboolean
    238 android_media_JetPlayer_pause(JNIEnv *env, jobject thiz)
    239 {
    240     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    241         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    242     if (lpJet == NULL ) {
    243         jniThrowException(env, "java/lang/IllegalStateException",
    244             "Unable to retrieve JetPlayer pointer for pause()");
    245     }
    246 
    247     EAS_RESULT result = lpJet->pause();
    248     if( result==EAS_SUCCESS) {
    249         //LOGV("android_media_JetPlayer_pause(): pause successful");
    250         return JNI_TRUE;
    251     } else {
    252         if(result==EAS_ERROR_QUEUE_IS_EMPTY) {
    253             LOGV("android_media_JetPlayer_pause(): paused with an empty queue");
    254             return JNI_TRUE;
    255         } else
    256             LOGE("android_media_JetPlayer_pause(): failed to pause with EAS error code %ld",
    257                 result);
    258         return JNI_FALSE;
    259     }
    260 }
    261 
    262 
    263 // ----------------------------------------------------------------------------
    264 static jboolean
    265 android_media_JetPlayer_queueSegment(JNIEnv *env, jobject thiz,
    266         jint segmentNum, jint libNum, jint repeatCount, jint transpose, jint muteFlags,
    267         jbyte userID)
    268 {
    269     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    270         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    271     if (lpJet == NULL ) {
    272         jniThrowException(env, "java/lang/IllegalStateException",
    273             "Unable to retrieve JetPlayer pointer for queueSegment()");
    274     }
    275 
    276     EAS_RESULT result
    277         = lpJet->queueSegment(segmentNum, libNum, repeatCount, transpose, muteFlags, userID);
    278     if(result==EAS_SUCCESS) {
    279         //LOGV("android_media_JetPlayer_queueSegment(): segment successfully queued");
    280         return JNI_TRUE;
    281     } else {
    282         LOGE("android_media_JetPlayer_queueSegment(): failed with EAS error code %ld",
    283             result);
    284         return JNI_FALSE;
    285     }
    286 }
    287 
    288 
    289 // ----------------------------------------------------------------------------
    290 static jboolean
    291 android_media_JetPlayer_queueSegmentMuteArray(JNIEnv *env, jobject thiz,
    292         jint segmentNum, jint libNum, jint repeatCount, jint transpose, jbooleanArray muteArray,
    293         jbyte userID)
    294 {
    295     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    296         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    297     if (lpJet == NULL ) {
    298         jniThrowException(env, "java/lang/IllegalStateException",
    299             "Unable to retrieve JetPlayer pointer for queueSegmentMuteArray()");
    300     }
    301 
    302     EAS_RESULT result=EAS_FAILURE;
    303 
    304     jboolean *muteTracks = NULL;
    305     muteTracks = env->GetBooleanArrayElements(muteArray, NULL);
    306     if (muteTracks == NULL) {
    307         LOGE("android_media_JetPlayer_queueSegment(): failed to read track mute mask.");
    308         return JNI_FALSE;
    309     }
    310 
    311     EAS_U32 muteMask=0;
    312     int maxTracks = lpJet->getMaxTracks();
    313     for (jint trackIndex=0; trackIndex<maxTracks; trackIndex++) {
    314         if(muteTracks[maxTracks-1-trackIndex]==JNI_TRUE)
    315             muteMask = (muteMask << 1) | 0x00000001;
    316         else
    317             muteMask = muteMask << 1;
    318     }
    319     //LOGV("android_media_JetPlayer_queueSegmentMuteArray(): FINAL mute mask =0x%08lX", mask);
    320 
    321     result = lpJet->queueSegment(segmentNum, libNum, repeatCount, transpose, muteMask, userID);
    322 
    323     env->ReleaseBooleanArrayElements(muteArray, muteTracks, 0);
    324     if(result==EAS_SUCCESS) {
    325         //LOGV("android_media_JetPlayer_queueSegmentMuteArray(): segment successfully queued");
    326         return JNI_TRUE;
    327     } else {
    328         LOGE("android_media_JetPlayer_queueSegmentMuteArray(): failed with EAS error code %ld",
    329             result);
    330         return JNI_FALSE;
    331     }
    332 }
    333 
    334 
    335 // ----------------------------------------------------------------------------
    336 static jboolean
    337 android_media_JetPlayer_setMuteFlags(JNIEnv *env, jobject thiz,
    338          jint muteFlags /*unsigned?*/, jboolean bSync)
    339 {
    340     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    341         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    342     if (lpJet == NULL ) {
    343         jniThrowException(env, "java/lang/IllegalStateException",
    344             "Unable to retrieve JetPlayer pointer for setMuteFlags()");
    345     }
    346 
    347     EAS_RESULT result;
    348     result = lpJet->setMuteFlags(muteFlags, bSync==JNI_TRUE ? true : false);
    349     if(result==EAS_SUCCESS) {
    350         //LOGV("android_media_JetPlayer_setMuteFlags(): mute flags successfully updated");
    351         return JNI_TRUE;
    352     } else {
    353         LOGE("android_media_JetPlayer_setMuteFlags(): failed with EAS error code %ld", result);
    354         return JNI_FALSE;
    355     }
    356 }
    357 
    358 
    359 // ----------------------------------------------------------------------------
    360 static jboolean
    361 android_media_JetPlayer_setMuteArray(JNIEnv *env, jobject thiz,
    362         jbooleanArray muteArray, jboolean bSync)
    363 {
    364     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    365         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    366     if (lpJet == NULL ) {
    367         jniThrowException(env, "java/lang/IllegalStateException",
    368             "Unable to retrieve JetPlayer pointer for setMuteArray()");
    369     }
    370 
    371     EAS_RESULT result=EAS_FAILURE;
    372 
    373     jboolean *muteTracks = NULL;
    374     muteTracks = env->GetBooleanArrayElements(muteArray, NULL);
    375     if (muteTracks == NULL) {
    376         LOGE("android_media_JetPlayer_setMuteArray(): failed to read track mute mask.");
    377         return JNI_FALSE;
    378     }
    379 
    380     EAS_U32 muteMask=0;
    381     int maxTracks = lpJet->getMaxTracks();
    382     for (jint trackIndex=0; trackIndex<maxTracks; trackIndex++) {
    383         if(muteTracks[maxTracks-1-trackIndex]==JNI_TRUE)
    384             muteMask = (muteMask << 1) | 0x00000001;
    385         else
    386             muteMask = muteMask << 1;
    387     }
    388     //LOGV("android_media_JetPlayer_setMuteArray(): FINAL mute mask =0x%08lX", muteMask);
    389 
    390     result = lpJet->setMuteFlags(muteMask, bSync==JNI_TRUE ? true : false);
    391 
    392     env->ReleaseBooleanArrayElements(muteArray, muteTracks, 0);
    393     if(result==EAS_SUCCESS) {
    394         //LOGV("android_media_JetPlayer_setMuteArray(): mute flags successfully updated");
    395         return JNI_TRUE;
    396     } else {
    397         LOGE("android_media_JetPlayer_setMuteArray(): \
    398             failed to update mute flags with EAS error code %ld", result);
    399         return JNI_FALSE;
    400     }
    401 }
    402 
    403 
    404 // ----------------------------------------------------------------------------
    405 static jboolean
    406 android_media_JetPlayer_setMuteFlag(JNIEnv *env, jobject thiz,
    407          jint trackId, jboolean muteFlag, jboolean bSync)
    408 {
    409     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    410         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    411     if (lpJet == NULL ) {
    412         jniThrowException(env, "java/lang/IllegalStateException",
    413             "Unable to retrieve JetPlayer pointer for setMuteFlag()");
    414     }
    415 
    416     EAS_RESULT result;
    417     result = lpJet->setMuteFlag(trackId,
    418         muteFlag==JNI_TRUE ? true : false, bSync==JNI_TRUE ? true : false);
    419     if(result==EAS_SUCCESS) {
    420         //LOGV("android_media_JetPlayer_setMuteFlag(): mute flag successfully updated for track %d", trackId);
    421         return JNI_TRUE;
    422     } else {
    423         LOGE("android_media_JetPlayer_setMuteFlag(): failed to update mute flag for track %d with EAS error code %ld",
    424                 trackId, result);
    425         return JNI_FALSE;
    426     }
    427 }
    428 
    429 
    430 // ----------------------------------------------------------------------------
    431 static jboolean
    432 android_media_JetPlayer_triggerClip(JNIEnv *env, jobject thiz, jint clipId)
    433 {
    434     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    435         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    436     if (lpJet == NULL ) {
    437         jniThrowException(env, "java/lang/IllegalStateException",
    438             "Unable to retrieve JetPlayer pointer for triggerClip()");
    439     }
    440 
    441     EAS_RESULT result;
    442     result = lpJet->triggerClip(clipId);
    443     if(result==EAS_SUCCESS) {
    444         //LOGV("android_media_JetPlayer_triggerClip(): triggerClip successful for clip %d", clipId);
    445         return JNI_TRUE;
    446     } else {
    447         LOGE("android_media_JetPlayer_triggerClip(): triggerClip for clip %d failed with EAS error code %ld",
    448                 clipId, result);
    449         return JNI_FALSE;
    450     }
    451 }
    452 
    453 
    454 // ----------------------------------------------------------------------------
    455 static jboolean
    456 android_media_JetPlayer_clearQueue(JNIEnv *env, jobject thiz)
    457 {
    458     JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
    459         thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    460     if (lpJet == NULL ) {
    461         jniThrowException(env, "java/lang/IllegalStateException",
    462             "Unable to retrieve JetPlayer pointer for clearQueue()");
    463     }
    464 
    465     EAS_RESULT result = lpJet->clearQueue();
    466     if(result==EAS_SUCCESS) {
    467         //LOGV("android_media_JetPlayer_clearQueue(): clearQueue successful");
    468         return JNI_TRUE;
    469     } else {
    470         LOGE("android_media_JetPlayer_clearQueue(): clearQueue failed with EAS error code %ld",
    471                 result);
    472         return JNI_FALSE;
    473     }
    474 }
    475 
    476 
    477 // ----------------------------------------------------------------------------
    478 // ----------------------------------------------------------------------------
    479 static JNINativeMethod gMethods[] = {
    480     // name,               signature,               funcPtr
    481     {"native_setup",       "(Ljava/lang/Object;II)Z", (void *)android_media_JetPlayer_setup},
    482     {"native_finalize",    "()V",                   (void *)android_media_JetPlayer_finalize},
    483     {"native_release",     "()V",                   (void *)android_media_JetPlayer_release},
    484     {"native_loadJetFromFile",
    485                            "(Ljava/lang/String;)Z", (void *)android_media_JetPlayer_loadFromFile},
    486     {"native_loadJetFromFileD", "(Ljava/io/FileDescriptor;JJ)Z",
    487                                                     (void *)android_media_JetPlayer_loadFromFileD},
    488     {"native_closeJetFile","()Z",                   (void *)android_media_JetPlayer_closeFile},
    489     {"native_playJet",     "()Z",                   (void *)android_media_JetPlayer_play},
    490     {"native_pauseJet",    "()Z",                   (void *)android_media_JetPlayer_pause},
    491     {"native_queueJetSegment",
    492                            "(IIIIIB)Z",             (void *)android_media_JetPlayer_queueSegment},
    493     {"native_queueJetSegmentMuteArray",
    494                            "(IIII[ZB)Z",     (void *)android_media_JetPlayer_queueSegmentMuteArray},
    495     {"native_setMuteFlags","(IZ)Z",                 (void *)android_media_JetPlayer_setMuteFlags},
    496     {"native_setMuteArray","([ZZ)Z",                (void *)android_media_JetPlayer_setMuteArray},
    497     {"native_setMuteFlag", "(IZZ)Z",                (void *)android_media_JetPlayer_setMuteFlag},
    498     {"native_triggerClip", "(I)Z",                  (void *)android_media_JetPlayer_triggerClip},
    499     {"native_clearQueue",  "()Z",                   (void *)android_media_JetPlayer_clearQueue},
    500 };
    501 
    502 #define JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME "mNativePlayerInJavaObj"
    503 #define JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME "postEventFromNative"
    504 
    505 
    506 int register_android_media_JetPlayer(JNIEnv *env)
    507 {
    508     jclass jetPlayerClass = NULL;
    509     javaJetPlayerFields.jetClass = NULL;
    510     javaJetPlayerFields.postNativeEventInJava = NULL;
    511     javaJetPlayerFields.nativePlayerInJavaObj = NULL;
    512 
    513     // Get the JetPlayer java class
    514     jetPlayerClass = env->FindClass(kClassPathName);
    515     if (jetPlayerClass == NULL) {
    516         LOGE("Can't find %s", kClassPathName);
    517         return -1;
    518     }
    519     javaJetPlayerFields.jetClass = (jclass)env->NewGlobalRef(jetPlayerClass);
    520 
    521     // Get the mNativePlayerInJavaObj variable field
    522     javaJetPlayerFields.nativePlayerInJavaObj = env->GetFieldID(
    523             jetPlayerClass,
    524             JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME, "I");
    525     if (javaJetPlayerFields.nativePlayerInJavaObj == NULL) {
    526         LOGE("Can't find AudioTrack.%s", JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME);
    527         return -1;
    528     }
    529 
    530     // Get the callback to post events from this native code to Java
    531     javaJetPlayerFields.postNativeEventInJava = env->GetStaticMethodID(javaJetPlayerFields.jetClass,
    532             JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME, "(Ljava/lang/Object;III)V");
    533     if (javaJetPlayerFields.postNativeEventInJava == NULL) {
    534         LOGE("Can't find Jet.%s", JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME);
    535         return -1;
    536     }
    537 
    538     return AndroidRuntime::registerNativeMethods(env,
    539             kClassPathName, gMethods, NELEM(gMethods));
    540 }
    541