Fixed bug 4297 - Android StrictMode policy. Remove APK expansion support

"In the second half of 2021, new apps will be required to publish with the Android App Bundle on Google Play"
(see https://developer.android.com/guide/app-bundle)
And "Android App Bundles don't support APK expansion (*.obb) files".
This commit is contained in:
Sylvain Becker 2020-08-17 19:50:20 +02:00
parent 92cb9192e3
commit 965b466ee8
3 changed files with 33 additions and 437 deletions

View File

@ -1221,76 +1221,6 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
}
}
// APK expansion files support
/** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
private static Object expansionFile;
/** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
private static Method expansionFileMethod;
/**
* This method is called by SDL using JNI.
* @return an InputStream on success or null if no expansion file was used.
* @throws IOException on errors. Message is set for the SDL error message.
*/
public static InputStream openAPKExpansionInputStream(String fileName) throws IOException {
// Get a ZipResourceFile representing a merger of both the main and patch files
if (expansionFile == null) {
String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
if (mainHint == null) {
return null; // no expansion use if no main version was set
}
String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
if (patchHint == null) {
return null; // no expansion use if no patch version was set
}
Integer mainVersion;
Integer patchVersion;
try {
mainVersion = Integer.valueOf(mainHint);
patchVersion = Integer.valueOf(patchHint);
} catch (NumberFormatException ex) {
ex.printStackTrace();
throw new IOException("No valid file versions set for APK expansion files", ex);
}
try {
// To avoid direct dependency on Google APK expansion library that is
// not a part of Android SDK we access it using reflection
expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
.getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
.invoke(null, SDL.getContext(), mainVersion, patchVersion);
expansionFileMethod = expansionFile.getClass()
.getMethod("getInputStream", String.class);
} catch (Exception ex) {
ex.printStackTrace();
expansionFile = null;
expansionFileMethod = null;
throw new IOException("Could not access APK expansion support library", ex);
}
}
// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream;
try {
fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
} catch (Exception ex) {
// calling "getInputStream" failed
ex.printStackTrace();
throw new IOException("Could not open stream from APK expansion file", ex);
}
if (fileStream == null) {
// calling "getInputStream" was successful but null was returned
throw new IOException("Could not find path in APK expansion file");
}
return fileStream;
}
// Messagebox
/** Result of current messagebox. Also used for blocking the calling thread. */

View File

@ -96,15 +96,7 @@ typedef struct SDL_RWops
#if defined(__ANDROID__)
struct
{
void *fileNameRef;
void *inputStreamRef;
void *readableByteChannelRef;
void *readMethod;
void *assetFileDescriptorRef;
long position;
long size;
long offset;
int fd;
void *asset;
} androidio;
#elif defined(__WIN32__)
struct

View File

@ -311,7 +311,6 @@ static jmethodID midIsScreenKeyboardShown;
static jmethodID midIsTablet;
static jmethodID midManualBackButton;
static jmethodID midMinimizeWindow;
static jmethodID midOpenAPKExpansionInputStream;
static jmethodID midRequestPermission;
static jmethodID midSendMessage;
static jmethodID midSetActivityTitle;
@ -590,7 +589,6 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
midIsTablet = (*env)->GetStaticMethodID(env, mActivityClass, "isTablet", "()Z");
midManualBackButton = (*env)->GetStaticMethodID(env, mActivityClass, "manualBackButton", "()V");
midMinimizeWindow = (*env)->GetStaticMethodID(env, mActivityClass, "minimizeWindow","()V");
midOpenAPKExpansionInputStream = (*env)->GetStaticMethodID(env, mActivityClass, "openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
midRequestPermission = (*env)->GetStaticMethodID(env, mActivityClass, "requestPermission", "(Ljava/lang/String;I)V");
midSendMessage = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
midSetActivityTitle = (*env)->GetStaticMethodID(env, mActivityClass, "setActivityTitle","(Ljava/lang/String;)Z");
@ -620,7 +618,6 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
!midIsTablet ||
!midManualBackButton ||
!midMinimizeWindow ||
!midOpenAPKExpansionInputStream ||
!midRequestPermission ||
!midSendMessage ||
!midSetActivityTitle ||
@ -1867,245 +1864,43 @@ static void Internal_Android_Destroy_AssetManager() {
}
}
static int Internal_Android_JNI_FileOpen(SDL_RWops *ctx)
{
struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
int result = 0;
jmethodID mid;
jobject context;
jobject assetManager;
jobject inputStream;
jclass channels;
jobject readableByteChannel;
jstring fileNameJString;
jobject fd;
jclass fdCls;
jfieldID descriptor;
JNIEnv *env = Android_JNI_GetEnv();
if (!LocalReferenceHolder_Init(&refs, env)) {
goto failure;
}
fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
ctx->hidden.androidio.position = 0;
/* context = SDLActivity.getContext(); */
context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
/* assetManager = context.getAssets(); */
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
"getAssets", "()Landroid/content/res/AssetManager;");
assetManager = (*env)->CallObjectMethod(env, context, mid);
/* First let's try opening the file to obtain an AssetFileDescriptor.
* This method reads the files directly from the APKs using standard *nix calls
*/
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
inputStream = (*env)->CallObjectMethod(env, assetManager, mid, fileNameJString);
if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
goto fallback;
}
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream), "getStartOffset", "()J");
ctx->hidden.androidio.offset = (long)(*env)->CallLongMethod(env, inputStream, mid);
if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
goto fallback;
}
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream), "getDeclaredLength", "()J");
ctx->hidden.androidio.size = (long)(*env)->CallLongMethod(env, inputStream, mid);
if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
goto fallback;
}
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
fd = (*env)->CallObjectMethod(env, inputStream, mid);
fdCls = (*env)->GetObjectClass(env, fd);
descriptor = (*env)->GetFieldID(env, fdCls, "descriptor", "I");
ctx->hidden.androidio.fd = (*env)->GetIntField(env, fd, descriptor);
ctx->hidden.androidio.assetFileDescriptorRef = (*env)->NewGlobalRef(env, inputStream);
/* Seek to the correct offset in the file. */
lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
if (0) {
fallback:
/* Disabled log message because of spam on the Nexus 7 */
/* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
/* Try the old method using InputStream */
ctx->hidden.androidio.assetFileDescriptorRef = NULL;
/* inputStream = assetManager.open(<filename>); */
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, assetManager),
"open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
inputStream = (*env)->CallObjectMethod(env, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
/* Try fallback to APK expansion files */
inputStream = (*env)->CallStaticObjectMethod(env, mActivityClass, midOpenAPKExpansionInputStream, fileNameJString);
/* Exception is checked first because it always needs to be cleared.
* If no exception occurred then the last SDL error message is kept.
*/
if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) {
goto failure;
}
}
ctx->hidden.androidio.inputStreamRef = (*env)->NewGlobalRef(env, inputStream);
/* Despite all the visible documentation on [Asset]InputStream claiming
* that the .available() method is not guaranteed to return the entire file
* size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
* android/apis/content/ReadAsset.java imply that Android's
* AssetInputStream.available() /will/ always return the total file size
*/
/* size = inputStream.available(); */
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream),
"available", "()I");
ctx->hidden.androidio.size = (long)(*env)->CallIntMethod(env, inputStream, mid);
if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
goto failure;
}
/* readableByteChannel = Channels.newChannel(inputStream); */
channels = (*env)->FindClass(env, "java/nio/channels/Channels");
mid = (*env)->GetStaticMethodID(env, channels,
"newChannel",
"(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
readableByteChannel = (*env)->CallStaticObjectMethod(
env, channels, mid, inputStream);
if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
goto failure;
}
ctx->hidden.androidio.readableByteChannelRef =
(*env)->NewGlobalRef(env, readableByteChannel);
/* Store .read id for reading purposes */
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, readableByteChannel),
"read", "(Ljava/nio/ByteBuffer;)I");
ctx->hidden.androidio.readMethod = mid;
}
if (0) {
failure:
result = -1;
(*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.fileNameRef);
if(ctx->hidden.androidio.inputStreamRef != NULL) {
(*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.inputStreamRef);
}
if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
(*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.readableByteChannelRef);
}
if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
(*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
}
}
LocalReferenceHolder_Cleanup(&refs);
return result;
}
int Android_JNI_FileOpen(SDL_RWops *ctx,
const char *fileName, const char *mode)
{
struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
JNIEnv *env = Android_JNI_GetEnv();
int retval;
jstring fileNameJString;
AAsset *asset = NULL;
ctx->hidden.androidio.asset = NULL;
if (!LocalReferenceHolder_Init(&refs, env)) {
LocalReferenceHolder_Cleanup(&refs);
if (asset_manager == NULL) {
Internal_Android_Create_AssetManager();
}
if (asset_manager == NULL) {
return -1;
}
if (!ctx) {
LocalReferenceHolder_Cleanup(&refs);
asset = AAssetManager_open(asset_manager, fileName, AASSET_MODE_UNKNOWN);
if (asset == NULL) {
return -1;
}
fileNameJString = (*env)->NewStringUTF(env, fileName);
ctx->hidden.androidio.fileNameRef = (*env)->NewGlobalRef(env, fileNameJString);
ctx->hidden.androidio.inputStreamRef = NULL;
ctx->hidden.androidio.readableByteChannelRef = NULL;
ctx->hidden.androidio.readMethod = NULL;
ctx->hidden.androidio.assetFileDescriptorRef = NULL;
retval = Internal_Android_JNI_FileOpen(ctx);
LocalReferenceHolder_Cleanup(&refs);
return retval;
ctx->hidden.androidio.asset = (void*) asset;
return 0;
}
size_t Android_JNI_FileRead(SDL_RWops *ctx, void *buffer,
size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
size_t size, size_t maxnum)
{
struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
size_t result;
AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
result = AAsset_read(asset, buffer, size * maxnum);
if (ctx->hidden.androidio.assetFileDescriptorRef) {
size_t bytesMax = size * maxnum;
size_t result;
if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
}
result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
if (result > 0) {
ctx->hidden.androidio.position += result;
LocalReferenceHolder_Cleanup(&refs);
return result / size;
}
LocalReferenceHolder_Cleanup(&refs);
return 0;
if (result > 0) {
/* Number of chuncks */
return (result / size);
} else {
jlong bytesRemaining = (jlong) (size * maxnum);
jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position);
int bytesRead = 0;
JNIEnv *env;
jobject readableByteChannel;
jmethodID readMethod;
jobject byteBuffer;
/* Don't read more bytes than those that remain in the file, otherwise we get an exception */
if (bytesRemaining > bytesMax) bytesRemaining = bytesMax;
env = Android_JNI_GetEnv();
if (!LocalReferenceHolder_Init(&refs, env)) {
LocalReferenceHolder_Cleanup(&refs);
return 0;
}
readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
byteBuffer = (*env)->NewDirectByteBuffer(env, buffer, bytesRemaining);
while (bytesRemaining > 0) {
/* result = readableByteChannel.read(...); */
int result = (*env)->CallIntMethod(env, readableByteChannel, readMethod, byteBuffer);
if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
LocalReferenceHolder_Cleanup(&refs);
return 0;
}
if (result < 0) {
break;
}
bytesRemaining -= result;
bytesRead += result;
ctx->hidden.androidio.position += result;
}
LocalReferenceHolder_Cleanup(&refs);
return bytesRead / size;
/* Error or EOF */
return result;
}
}
@ -2116,148 +1911,27 @@ size_t Android_JNI_FileWrite(SDL_RWops *ctx, const void *buffer,
return 0;
}
static int Internal_Android_JNI_FileClose(SDL_RWops *ctx, SDL_bool release)
Sint64 Android_JNI_FileSize(SDL_RWops *ctx)
{
struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
int result = 0;
JNIEnv *env = Android_JNI_GetEnv();
if (!LocalReferenceHolder_Init(&refs, env)) {
LocalReferenceHolder_Cleanup(&refs);
return SDL_SetError("Failed to allocate enough JVM local references");
}
if (ctx) {
if (release) {
(*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.fileNameRef);
}
if (ctx->hidden.androidio.assetFileDescriptorRef) {
jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream),
"close", "()V");
(*env)->CallVoidMethod(env, inputStream, mid);
(*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
result = -1;
}
}
else {
jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
/* inputStream.close(); */
jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream),
"close", "()V");
(*env)->CallVoidMethod(env, inputStream, mid);
(*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.inputStreamRef);
(*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.readableByteChannelRef);
if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
result = -1;
}
}
if (release) {
SDL_FreeRW(ctx);
}
}
LocalReferenceHolder_Cleanup(&refs);
off64_t result;
AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
result = AAsset_getLength64(asset);
return result;
}
Sint64 Android_JNI_FileSize(SDL_RWops *ctx)
Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
{
return ctx->hidden.androidio.size;
}
Sint64 Android_JNI_FileSeek(SDL_RWops *ctx, Sint64 offset, int whence)
{
if (ctx->hidden.androidio.assetFileDescriptorRef) {
off_t ret;
switch (whence) {
case RW_SEEK_SET:
if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
offset += ctx->hidden.androidio.offset;
break;
case RW_SEEK_CUR:
offset += ctx->hidden.androidio.position;
if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
offset += ctx->hidden.androidio.offset;
break;
case RW_SEEK_END:
offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
break;
default:
return SDL_SetError("Unknown value for 'whence'");
}
ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
if (ret == -1) return -1;
ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
} else {
Sint64 newPosition;
Sint64 movement;
switch (whence) {
case RW_SEEK_SET:
newPosition = offset;
break;
case RW_SEEK_CUR:
newPosition = ctx->hidden.androidio.position + offset;
break;
case RW_SEEK_END:
newPosition = ctx->hidden.androidio.size + offset;
break;
default:
return SDL_SetError("Unknown value for 'whence'");
}
/* Validate the new position */
if (newPosition < 0) {
return SDL_Error(SDL_EFSEEK);
}
if (newPosition > ctx->hidden.androidio.size) {
newPosition = ctx->hidden.androidio.size;
}
movement = newPosition - ctx->hidden.androidio.position;
if (movement > 0) {
unsigned char buffer[4096];
/* The easy case where we're seeking forwards */
while (movement > 0) {
Sint64 amount = sizeof (buffer);
size_t result;
if (amount > movement) {
amount = movement;
}
result = Android_JNI_FileRead(ctx, buffer, 1, (size_t)amount);
if (result <= 0) {
/* Failed to read/skip the required amount, so fail */
return -1;
}
movement -= result;
}
} else if (movement < 0) {
/* We can't seek backwards so we have to reopen the file and seek */
/* forwards which obviously isn't very efficient */
Internal_Android_JNI_FileClose(ctx, SDL_FALSE);
Internal_Android_JNI_FileOpen(ctx);
Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
}
}
return ctx->hidden.androidio.position;
off64_t result;
AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
result = AAsset_seek64(asset, offset, whence);
return result;
}
int Android_JNI_FileClose(SDL_RWops *ctx)
{
return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
AAsset_close(asset);
return 0;
}
int Android_JNI_SetClipboardText(const char *text)