src/core/android/SDL_android.cpp
changeset 6806 9e57ff36fd7a
parent 6802 8a6b8ce97656
child 6816 b3d3ef1e15b5
     1.1 --- a/src/core/android/SDL_android.cpp	Tue Jan 08 08:28:39 2013 -0300
     1.2 +++ b/src/core/android/SDL_android.cpp	Tue Jan 08 09:30:53 2013 -0300
     1.3 @@ -37,6 +37,8 @@
     1.4  
     1.5  #include <android/log.h>
     1.6  #include <pthread.h>
     1.7 +#include <sys/types.h>
     1.8 +#include <unistd.h>
     1.9  #define LOG_TAG "SDL_android"
    1.10  //#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    1.11  //#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    1.12 @@ -559,6 +561,9 @@
    1.13      jclass channels;
    1.14      jobject readableByteChannel;
    1.15      jstring fileNameJString;
    1.16 +    jobject fd;
    1.17 +    jclass fdCls;
    1.18 +    jfieldID descriptor;
    1.19  
    1.20      JNIEnv *mEnv = Android_JNI_GetEnv();
    1.21      if (!refs.init(mEnv)) {
    1.22 @@ -566,61 +571,97 @@
    1.23      }
    1.24  
    1.25      fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
    1.26 +    ctx->hidden.androidio.position = 0;
    1.27  
    1.28      // context = SDLActivity.getContext();
    1.29      mid = mEnv->GetStaticMethodID(mActivityClass,
    1.30              "getContext","()Landroid/content/Context;");
    1.31      context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
    1.32 +    
    1.33  
    1.34      // assetManager = context.getAssets();
    1.35      mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
    1.36              "getAssets", "()Landroid/content/res/AssetManager;");
    1.37      assetManager = mEnv->CallObjectMethod(context, mid);
    1.38  
    1.39 -    // inputStream = assetManager.open(<filename>);
    1.40 -    mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
    1.41 -            "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
    1.42 +    /* First let's try opening the file to obtain an AssetFileDescriptor.
    1.43 +    * This method reads the files directly from the APKs using standard *nix calls
    1.44 +    */
    1.45 +    mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
    1.46      inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
    1.47      if (Android_JNI_ExceptionOccurred()) {
    1.48 -        goto failure;
    1.49 +        goto fallback;
    1.50      }
    1.51  
    1.52 -    ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
    1.53 -
    1.54 -    // Despite all the visible documentation on [Asset]InputStream claiming
    1.55 -    // that the .available() method is not guaranteed to return the entire file
    1.56 -    // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
    1.57 -    // android/apis/content/ReadAsset.java imply that Android's
    1.58 -    // AssetInputStream.available() /will/ always return the total file size
    1.59 -
    1.60 -    // size = inputStream.available();
    1.61 -    mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
    1.62 -            "available", "()I");
    1.63 -    ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
    1.64 +    ctx->hidden.androidio.assetFileDescriptorRef = mEnv->NewGlobalRef(inputStream);
    1.65 +    mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getStartOffset", "()J");
    1.66 +    ctx->hidden.androidio.offset = mEnv->CallLongMethod(inputStream, mid);
    1.67      if (Android_JNI_ExceptionOccurred()) {
    1.68 -        goto failure;
    1.69 +        goto fallback;
    1.70      }
    1.71  
    1.72 -    // readableByteChannel = Channels.newChannel(inputStream);
    1.73 -    channels = mEnv->FindClass("java/nio/channels/Channels");
    1.74 -    mid = mEnv->GetStaticMethodID(channels,
    1.75 -            "newChannel",
    1.76 -            "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
    1.77 -    readableByteChannel = mEnv->CallStaticObjectMethod(
    1.78 -            channels, mid, inputStream);
    1.79 +    mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getDeclaredLength", "()J");
    1.80 +    ctx->hidden.androidio.size = mEnv->CallLongMethod(inputStream, mid);
    1.81 +    
    1.82      if (Android_JNI_ExceptionOccurred()) {
    1.83 -        goto failure;
    1.84 +        goto fallback;
    1.85      }
    1.86  
    1.87 -    ctx->hidden.androidio.readableByteChannelRef =
    1.88 -        mEnv->NewGlobalRef(readableByteChannel);
    1.89 +    mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
    1.90 +    fd = mEnv->CallObjectMethod(inputStream, mid);
    1.91 +    fdCls = mEnv->GetObjectClass(fd);
    1.92 +    descriptor = mEnv->GetFieldID(fdCls, "descriptor", "I");
    1.93 +    ctx->hidden.androidio.fd = mEnv->GetIntField(fd, descriptor);
    1.94  
    1.95 -    // Store .read id for reading purposes
    1.96 -    mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
    1.97 -            "read", "(Ljava/nio/ByteBuffer;)I");
    1.98 -    ctx->hidden.androidio.readMethod = mid;
    1.99 +    if (false) {
   1.100 +fallback:
   1.101 +        __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
   1.102 +        /* Try the old method using InputStream */
   1.103 +        ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   1.104  
   1.105 -    ctx->hidden.androidio.position = 0;
   1.106 +        // inputStream = assetManager.open(<filename>);
   1.107 +        mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   1.108 +                "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
   1.109 +        inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
   1.110 +        if (Android_JNI_ExceptionOccurred()) {
   1.111 +            goto failure;
   1.112 +        }
   1.113 +
   1.114 +        ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   1.115 +
   1.116 +        // Despite all the visible documentation on [Asset]InputStream claiming
   1.117 +        // that the .available() method is not guaranteed to return the entire file
   1.118 +        // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   1.119 +        // android/apis/content/ReadAsset.java imply that Android's
   1.120 +        // AssetInputStream.available() /will/ always return the total file size
   1.121 +
   1.122 +        // size = inputStream.available();
   1.123 +        mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   1.124 +                "available", "()I");
   1.125 +        ctx->hidden.androidio.size = (long)mEnv->CallIntMethod(inputStream, mid);
   1.126 +        if (Android_JNI_ExceptionOccurred()) {
   1.127 +            goto failure;
   1.128 +        }
   1.129 +
   1.130 +        // readableByteChannel = Channels.newChannel(inputStream);
   1.131 +        channels = mEnv->FindClass("java/nio/channels/Channels");
   1.132 +        mid = mEnv->GetStaticMethodID(channels,
   1.133 +                "newChannel",
   1.134 +                "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   1.135 +        readableByteChannel = mEnv->CallStaticObjectMethod(
   1.136 +                channels, mid, inputStream);
   1.137 +        if (Android_JNI_ExceptionOccurred()) {
   1.138 +            goto failure;
   1.139 +        }
   1.140 +
   1.141 +        ctx->hidden.androidio.readableByteChannelRef =
   1.142 +            mEnv->NewGlobalRef(readableByteChannel);
   1.143 +
   1.144 +        // Store .read id for reading purposes
   1.145 +        mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   1.146 +                "read", "(Ljava/nio/ByteBuffer;)I");
   1.147 +        ctx->hidden.androidio.readMethod = mid;
   1.148 +    }
   1.149  
   1.150      if (false) {
   1.151  failure:
   1.152 @@ -636,6 +677,10 @@
   1.153              mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   1.154          }
   1.155  
   1.156 +        if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
   1.157 +            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   1.158 +        }
   1.159 +
   1.160      }
   1.161  
   1.162      return result;
   1.163 @@ -660,6 +705,7 @@
   1.164      ctx->hidden.androidio.inputStreamRef = NULL;
   1.165      ctx->hidden.androidio.readableByteChannelRef = NULL;
   1.166      ctx->hidden.androidio.readMethod = NULL;
   1.167 +    ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   1.168  
   1.169      return Android_JNI_FileOpen(ctx);
   1.170  }
   1.171 @@ -668,40 +714,53 @@
   1.172          size_t size, size_t maxnum)
   1.173  {
   1.174      LocalReferenceHolder refs(__FUNCTION__);
   1.175 -    jlong bytesRemaining = (jlong) (size * maxnum);
   1.176 -    jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   1.177 -    int bytesRead = 0;
   1.178  
   1.179 -    /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   1.180 -    if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   1.181 +    if (ctx->hidden.androidio.assetFileDescriptorRef) {
   1.182 +        size_t bytesMax = size * maxnum;
   1.183 +        if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
   1.184 +            bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
   1.185 +        }
   1.186 +        size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
   1.187 +        if (result > 0) {
   1.188 +            ctx->hidden.androidio.position += result;
   1.189 +            return result / size;
   1.190 +        }
   1.191 +        return 0;
   1.192 +    } else {
   1.193 +        jlong bytesRemaining = (jlong) (size * maxnum);
   1.194 +        jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   1.195 +        int bytesRead = 0;
   1.196  
   1.197 -    JNIEnv *mEnv = Android_JNI_GetEnv();
   1.198 -    if (!refs.init(mEnv)) {
   1.199 -        return -1;
   1.200 -    }
   1.201 +        /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   1.202 +        if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   1.203  
   1.204 -    jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   1.205 -    jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   1.206 -    jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   1.207 -
   1.208 -    while (bytesRemaining > 0) {
   1.209 -        // result = readableByteChannel.read(...);
   1.210 -        int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   1.211 -
   1.212 -        if (Android_JNI_ExceptionOccurred()) {
   1.213 -            return 0;
   1.214 +        JNIEnv *mEnv = Android_JNI_GetEnv();
   1.215 +        if (!refs.init(mEnv)) {
   1.216 +            return -1;
   1.217          }
   1.218  
   1.219 -        if (result < 0) {
   1.220 -            break;
   1.221 +        jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   1.222 +        jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   1.223 +        jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   1.224 +
   1.225 +        while (bytesRemaining > 0) {
   1.226 +            // result = readableByteChannel.read(...);
   1.227 +            int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   1.228 +
   1.229 +            if (Android_JNI_ExceptionOccurred()) {
   1.230 +                return 0;
   1.231 +            }
   1.232 +
   1.233 +            if (result < 0) {
   1.234 +                break;
   1.235 +            }
   1.236 +
   1.237 +            bytesRemaining -= result;
   1.238 +            bytesRead += result;
   1.239 +            ctx->hidden.androidio.position += result;
   1.240          }
   1.241 -
   1.242 -        bytesRemaining -= result;
   1.243 -        bytesRead += result;
   1.244 -        ctx->hidden.androidio.position += result;
   1.245 -    }
   1.246 -
   1.247 -    return bytesRead / size;
   1.248 +        return bytesRead / size;
   1.249 +    }    
   1.250  }
   1.251  
   1.252  extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   1.253 @@ -727,16 +786,28 @@
   1.254              mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   1.255          }
   1.256  
   1.257 -        jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   1.258 +        if (ctx->hidden.androidio.assetFileDescriptorRef) {
   1.259 +            jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   1.260 +            jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   1.261 +                    "close", "()V");
   1.262 +            mEnv->CallVoidMethod(inputStream, mid);
   1.263 +            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   1.264 +            if (Android_JNI_ExceptionOccurred()) {
   1.265 +                result = -1;
   1.266 +            }
   1.267 +        }
   1.268 +        else {
   1.269 +            jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   1.270  
   1.271 -        // inputStream.close();
   1.272 -        jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   1.273 -                "close", "()V");
   1.274 -        mEnv->CallVoidMethod(inputStream, mid);
   1.275 -        mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   1.276 -        mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   1.277 -        if (Android_JNI_ExceptionOccurred()) {
   1.278 -            result = -1;
   1.279 +            // inputStream.close();
   1.280 +            jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   1.281 +                    "close", "()V");
   1.282 +            mEnv->CallVoidMethod(inputStream, mid);
   1.283 +            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   1.284 +            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   1.285 +            if (Android_JNI_ExceptionOccurred()) {
   1.286 +                result = -1;
   1.287 +            }
   1.288          }
   1.289  
   1.290          if (release) {
   1.291 @@ -755,60 +826,86 @@
   1.292  
   1.293  extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   1.294  {
   1.295 -    Sint64 newPosition;
   1.296 +    if (ctx->hidden.androidio.assetFileDescriptorRef) {
   1.297 +        switch (whence) {
   1.298 +            case RW_SEEK_SET:
   1.299 +                if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   1.300 +                offset += ctx->hidden.androidio.offset;
   1.301 +                break;
   1.302 +            case RW_SEEK_CUR:
   1.303 +                offset += ctx->hidden.androidio.position;
   1.304 +                if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   1.305 +                offset += ctx->hidden.androidio.offset;
   1.306 +                break;
   1.307 +            case RW_SEEK_END:
   1.308 +                offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
   1.309 +                break;
   1.310 +            default:
   1.311 +                SDL_SetError("Unknown value for 'whence'");
   1.312 +                return -1;
   1.313 +        }
   1.314 +        whence = SEEK_SET;
   1.315  
   1.316 -    switch (whence) {
   1.317 -        case RW_SEEK_SET:
   1.318 -            newPosition = offset;
   1.319 -            break;
   1.320 -        case RW_SEEK_CUR:
   1.321 -            newPosition = ctx->hidden.androidio.position + offset;
   1.322 -            break;
   1.323 -        case RW_SEEK_END:
   1.324 -            newPosition = ctx->hidden.androidio.size + offset;
   1.325 -            break;
   1.326 -        default:
   1.327 -            SDL_SetError("Unknown value for 'whence'");
   1.328 +        off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
   1.329 +        if (ret == -1) return -1;
   1.330 +        ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
   1.331 +    } else {
   1.332 +        Sint64 newPosition;
   1.333 +
   1.334 +        switch (whence) {
   1.335 +            case RW_SEEK_SET:
   1.336 +                newPosition = offset;
   1.337 +                break;
   1.338 +            case RW_SEEK_CUR:
   1.339 +                newPosition = ctx->hidden.androidio.position + offset;
   1.340 +                break;
   1.341 +            case RW_SEEK_END:
   1.342 +                newPosition = ctx->hidden.androidio.size + offset;
   1.343 +                break;
   1.344 +            default:
   1.345 +                SDL_SetError("Unknown value for 'whence'");
   1.346 +                return -1;
   1.347 +        }
   1.348 +
   1.349 +        /* Validate the new position */
   1.350 +        if (newPosition < 0) {
   1.351 +            SDL_Error(SDL_EFSEEK);
   1.352              return -1;
   1.353 -    }
   1.354 +        }
   1.355 +        if (newPosition > ctx->hidden.androidio.size) {
   1.356 +            newPosition = ctx->hidden.androidio.size;
   1.357 +        }
   1.358  
   1.359 -    /* Validate the new position */
   1.360 -    if (newPosition < 0) {
   1.361 -        SDL_Error(SDL_EFSEEK);
   1.362 -        return -1;
   1.363 -    }
   1.364 -    if (newPosition > ctx->hidden.androidio.size) {
   1.365 -        newPosition = ctx->hidden.androidio.size;
   1.366 -    }
   1.367 +        Sint64 movement = newPosition - ctx->hidden.androidio.position;
   1.368 +        if (movement > 0) {
   1.369 +            unsigned char buffer[4096];
   1.370  
   1.371 -    Sint64 movement = newPosition - ctx->hidden.androidio.position;
   1.372 -    if (movement > 0) {
   1.373 -        unsigned char buffer[4096];
   1.374 +            // The easy case where we're seeking forwards
   1.375 +            while (movement > 0) {
   1.376 +                Sint64 amount = sizeof (buffer);
   1.377 +                if (amount > movement) {
   1.378 +                    amount = movement;
   1.379 +                }
   1.380 +                size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   1.381 +                if (result <= 0) {
   1.382 +                    // Failed to read/skip the required amount, so fail
   1.383 +                    return -1;
   1.384 +                }
   1.385  
   1.386 -        // The easy case where we're seeking forwards
   1.387 -        while (movement > 0) {
   1.388 -            Sint64 amount = sizeof (buffer);
   1.389 -            if (amount > movement) {
   1.390 -                amount = movement;
   1.391 -            }
   1.392 -            size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   1.393 -            if (result <= 0) {
   1.394 -                // Failed to read/skip the required amount, so fail
   1.395 -                return -1;
   1.396 +                movement -= result;
   1.397              }
   1.398  
   1.399 -            movement -= result;
   1.400 +        } else if (movement < 0) {
   1.401 +            // We can't seek backwards so we have to reopen the file and seek
   1.402 +            // forwards which obviously isn't very efficient
   1.403 +            Android_JNI_FileClose(ctx, false);
   1.404 +            Android_JNI_FileOpen(ctx);
   1.405 +            Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   1.406          }
   1.407 -
   1.408 -    } else if (movement < 0) {
   1.409 -        // We can't seek backwards so we have to reopen the file and seek
   1.410 -        // forwards which obviously isn't very efficient
   1.411 -        Android_JNI_FileClose(ctx, false);
   1.412 -        Android_JNI_FileOpen(ctx);
   1.413 -        Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   1.414      }
   1.415  
   1.416      return ctx->hidden.androidio.position;
   1.417 +    
   1.418  }
   1.419  
   1.420  extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)