diff options
author | Christian Lohmaier <lohmaier+LibreOffice@googlemail.com> | 2015-10-11 01:52:39 +0200 |
---|---|---|
committer | Christian Lohmaier <lohmaier+LibreOffice@googlemail.com> | 2015-10-11 02:03:43 +0200 |
commit | 143fb0a4b5d4ab69d4928299d8112ab95d99870a (patch) | |
tree | 094673c521e14d4ba52fd706acba07be96bead49 | |
parent | ee8257a1c70eadb7330b0ee99ec3b86fe4084bdf (diff) |
move extracting assets to Java & use AssetManager to access assets
using AssetsManager in both java as well as native parts allows to
handle files both with and without compression transparently
Change-Id: If02f1159c498be7ea965fd9c217410722f2dca1f
-rw-r--r-- | android/Bootstrap/src/org/libreoffice/kit/LibreOfficeKit.java | 8 | ||||
-rw-r--r-- | android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java | 139 | ||||
-rw-r--r-- | include/osl/detail/android-bootstrap.h | 5 | ||||
-rw-r--r-- | sal/android/libreofficekit-jni.c | 19 | ||||
-rw-r--r-- | sal/android/lo-bootstrap.c | 182 | ||||
-rw-r--r-- | sal/osl/unx/file.cxx | 29 |
6 files changed, 149 insertions, 233 deletions
diff --git a/android/Bootstrap/src/org/libreoffice/kit/LibreOfficeKit.java b/android/Bootstrap/src/org/libreoffice/kit/LibreOfficeKit.java index 431c384726d0..b2fb5e12053a 100644 --- a/android/Bootstrap/src/org/libreoffice/kit/LibreOfficeKit.java +++ b/android/Bootstrap/src/org/libreoffice/kit/LibreOfficeKit.java @@ -11,6 +11,7 @@ package org.libreoffice.kit; import android.app.Activity; import android.content.pm.ApplicationInfo; +import android.content.res.AssetManager; import android.util.Log; import java.io.File; @@ -24,6 +25,7 @@ import java.nio.ByteBuffer; public final class LibreOfficeKit { private static String LOGTAG = LibreOfficeKit.class.getSimpleName(); + private static AssetManager mgr; // private constructor because instantiating would be meaningless private LibreOfficeKit() { @@ -34,7 +36,7 @@ public final class LibreOfficeKit } // Trigger initialization on the JNI - LOKit side. - private static native boolean initializeNative(String dataDir, String cacheDir, String apkFile); + private static native boolean initializeNative(String dataDir, String cacheDir, String apkFile, AssetManager mgr); public static native ByteBuffer getLibreOfficeKitHandle(); @@ -55,6 +57,8 @@ public final class LibreOfficeKit return; } + mgr = activity.getResources().getAssets(); + ApplicationInfo applicationInfo = activity.getApplicationInfo(); String dataDir = applicationInfo.dataDir; Log.i(LOGTAG, String.format("Initializing LibreOfficeKit, dataDir=%s\n", dataDir)); @@ -83,7 +87,7 @@ public final class LibreOfficeKit // TMPDIR is used by osl_getTempDirURL() putenv("TMPDIR=" + cacheDir); - if (!initializeNative(dataDir, cacheDir, apkFile)) { + if (!initializeNative(dataDir, cacheDir, apkFile, mgr)) { Log.e(LOGTAG, "Initialize native failed!"); return; } diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java index 324423c30aa3..eb566073f2a7 100644 --- a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java +++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java @@ -6,6 +6,9 @@ import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -33,9 +36,11 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.List; @@ -47,6 +52,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity { private static final String LOGTAG = "LibreOfficeMainActivity"; private static final String DEFAULT_DOC_PATH = "/assets/example.odt"; private static final String ENABLE_EXPERIMENTAL_PREFS_KEY = "ENABLE_EXPERIMENTAL"; + private static final String ASSETS_EXTRACTED_PREFS_KEY = "ASSETS_EXTRACTED"; public static LibreOfficeMainActivity mAppContext; @@ -147,9 +153,15 @@ public class LibreOfficeMainActivity extends AppCompatActivity { mAppContext = this; super.onCreate(savedInstanceState); - mEnableEditing = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) - .getBoolean(ENABLE_EXPERIMENTAL_PREFS_KEY, false); + SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + mEnableEditing = sPrefs.getBoolean(ENABLE_EXPERIMENTAL_PREFS_KEY, false); + + if (sPrefs.getInt(ASSETS_EXTRACTED_PREFS_KEY, 0) != BuildConfig.VERSION_CODE) { + if(copyFromAssets(getAssets(), "unpack", getApplicationInfo().dataDir)) { + sPrefs.edit().putInt(ASSETS_EXTRACTED_PREFS_KEY, BuildConfig.VERSION_CODE).apply(); + } + } mMainHandler = new Handler(); setContentView(R.layout.activity_main); @@ -165,6 +177,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity { Log.d(LOGTAG, "SCHEME_CONTENT: getPath(): " + getIntent().getData().getPath()); } else { // TODO: can't open the file + Log.e(LOGTAG, "couldn't create temporary file from "+getIntent().getData()); } } else if (getIntent().getData().getScheme().equals(ContentResolver.SCHEME_FILE)) { mInputFile = new File(getIntent().getData().getPath()); @@ -217,38 +230,42 @@ public class LibreOfficeMainActivity extends AppCompatActivity { private boolean copyFileToTemp() { ContentResolver contentResolver = getContentResolver(); - InputStream inputStream = null; + FileChannel inputChannel = null; + FileChannel outputChannel = null; + // CSV files need a .csv suffix to be opened in Calc. + String suffix = null; + String intentType = getIntent().getType(); + // K-9 mail uses the first, GMail uses the second variant. + if ("text/comma-separated-values".equals(intentType) || "text/csv".equals(intentType)) + suffix = ".csv"; + try { - inputStream = contentResolver.openInputStream(getIntent().getData()); - - // CSV files need a .csv suffix to be opened in Calc. - String suffix = null; - String intentType = getIntent().getType(); - // K-9 mail uses the first, GMail uses the second variant. - if ("text/comma-separated-values".equals(intentType) || "text/csv".equals(intentType)) - suffix = ".csv"; - mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir()); - - OutputStream outputStream = new FileOutputStream(mTempFile); - byte[] buffer = new byte[4096]; - int len = 0; - while ((len = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, len); + try { + AssetFileDescriptor assetFD = contentResolver.openAssetFileDescriptor(getIntent().getData(), "r"); + if (assetFD == null) { + Log.e(LOGTAG, "couldn't create assetfiledescriptor from "+getIntent().getDataString()); + return false; + } + inputChannel = assetFD.createInputStream().getChannel(); + mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir()); + + outputChannel = new FileOutputStream(mTempFile).getChannel(); + long bytesTransferred = 0; + // might not copy all at once, so make sure everything gets copied.... + while (bytesTransferred < inputChannel.size()) { + bytesTransferred += outputChannel.transferFrom(inputChannel, bytesTransferred, inputChannel.size()); + } + Log.e(LOGTAG, "Success copying "+bytesTransferred+ " bytes"); + return true; + } finally { + if (inputChannel != null) inputChannel.close(); + if (outputChannel != null) outputChannel.close(); } - inputStream.close(); - outputStream.close(); - return true; } catch (FileNotFoundException e) { + return false; } catch (IOException e) { - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - } - } + return false; } - return false; } /** @@ -362,6 +379,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity { if (isFinishing()) { // Not an orientation change if (mTempFile != null) { + //noinspection ResultOfMethodCallIgnored mTempFile.delete(); } } @@ -423,8 +441,6 @@ public class LibreOfficeMainActivity extends AppCompatActivity { * Hides software keyboard. */ private void hideSoftKeyboardDirect() { - LayerView layerView = (LayerView) findViewById(R.id.layer_view); - if (getCurrentFocus() != null) { InputMethodManager inputMethodManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); @@ -471,6 +487,63 @@ public class LibreOfficeMainActivity extends AppCompatActivity { mDrawerLayout.closeDrawer(mDrawerList); } } + + private static boolean copyFromAssets(AssetManager assetManager, + String fromAssetPath, String targetDir) { + try { + String[] files = assetManager.list(fromAssetPath); + + boolean res = true; + for (String file : files) { + String[] dirOrFile = assetManager.list(fromAssetPath+"/"+file); + if ( dirOrFile.length == 0) { + //noinspection ResultOfMethodCallIgnored + new File(targetDir).mkdirs(); + res &= copyAsset(assetManager, + fromAssetPath + "/" + file, + targetDir + "/" + file); + } else + res &= copyFromAssets(assetManager, + fromAssetPath + "/" + file, + targetDir + "/" + file); + } + return res; + } catch (Exception e) { + e.printStackTrace(); + Log.e(LOGTAG, "copyFromAssets failed: " + e.getMessage()); + return false; + } + } + + private static boolean copyAsset(AssetManager assetManager, String fromAssetPath, String toPath) { + ReadableByteChannel source = null; + FileChannel dest = null; + try { + try { + source = Channels.newChannel(assetManager.open(fromAssetPath)); + dest = new FileOutputStream(toPath).getChannel(); + long bytesTransferred = 0; + // might not copy all at once, so make sure everything gets copied.... + ByteBuffer buffer = ByteBuffer.allocate(4096); + while (source.read(buffer)>0) { + buffer.flip(); + bytesTransferred += dest.write(buffer); + buffer.clear(); + } + Log.v(LOGTAG, "Success copying "+fromAssetPath+" to "+toPath + " bytes: "+bytesTransferred); + return true; + } finally { + if (dest != null) dest.close(); + if (source != null) source.close(); + } + } catch (FileNotFoundException e) { + Log.e(LOGTAG, "file " + fromAssetPath + " not found! " + e.getMessage()); + return false; + } catch (IOException e) { + Log.e(LOGTAG, "failed to copy file " + fromAssetPath + " from assets to " + toPath + " - " + e.getMessage()); + return false; + } + } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/osl/detail/android-bootstrap.h b/include/osl/detail/android-bootstrap.h index b18531bb65e2..5a5c7fcbd82d 100644 --- a/include/osl/detail/android-bootstrap.h +++ b/include/osl/detail/android-bootstrap.h @@ -20,6 +20,7 @@ extern "C" { #endif #include <osl/detail/component-mapping.h> +#include <android/asset_manager.h> typedef struct lo_apk_dir lo_apk_dir; @@ -38,12 +39,10 @@ JavaVM *lo_get_javavm(void); const char *lo_get_app_data_dir(void); -#define UNPACK_TREE "/assets/unpack" -#define UNPACK_TREE_GZ "/assets/gz.unpack" +AAssetManager *lo_get_native_assetmgr(void); int setup_cdir(void); int setup_assets_tree(void); -void extract_files(const char *root, const char *prefix, int gzipped); #ifdef __cplusplus } diff --git a/sal/android/libreofficekit-jni.c b/sal/android/libreofficekit-jni.c index 41fa97e8273d..c5f53c92bcd5 100644 --- a/sal/android/libreofficekit-jni.c +++ b/sal/android/libreofficekit-jni.c @@ -21,6 +21,8 @@ #include <jni.h> #include <android/log.h> +#include <android/asset_manager.h> +#include <android/asset_manager_jni.h> #include <osl/detail/android-bootstrap.h> @@ -34,6 +36,7 @@ extern const char* data_dir; extern const char* cache_dir; extern void* apk_file; extern int apk_file_size; +AAssetManager* native_asset_manager; extern void Java_org_libreoffice_android_Bootstrap_putenv(JNIEnv* env, jobject clazz, jstring string); extern void Java_org_libreoffice_android_Bootstrap_redirect_1stdio(JNIEnv* env, jobject clazz, jboolean state); @@ -63,7 +66,7 @@ void Java_org_libreoffice_kit_LibreOfficeKit_redirectStdio __attribute__ ((visibility("default"))) jboolean Java_org_libreoffice_kit_LibreOfficeKit_initializeNative (JNIEnv* env, jobject clazz, - jstring dataDir, jstring cacheDir, jstring apkFile) + jstring dataDir, jstring cacheDir, jstring apkFile, jobject assetManager) { struct stat st; int fd; @@ -76,6 +79,8 @@ jboolean Java_org_libreoffice_kit_LibreOfficeKit_initializeNative (void) clazz; + native_asset_manager = AAssetManager_fromJava(env, assetManager); + dataDirPath = (*env)->GetStringUTFChars(env, dataDir, NULL); data_dir = strdup(dataDirPath); (*env)->ReleaseStringUTFChars(env, dataDir, dataDirPath); @@ -122,10 +127,6 @@ jboolean Java_org_libreoffice_kit_LibreOfficeKit_initializeNative return JNI_FALSE; } - // Extract files from the .apk that can't be used mmapped directly from it - extract_files(UNPACK_TREE, UNPACK_TREE, 0); - extract_files(UNPACK_TREE_GZ, UNPACK_TREE_GZ, 1); - // LibreOfficeKit expects a path to the program/ directory free(full_program_dir); data_dir_len = strlen(data_dir); @@ -160,4 +161,12 @@ jobject Java_org_libreoffice_kit_LibreOfficeKit_getLibreOfficeKitHandle return (*env)->NewDirectByteBuffer(env, (void*) aOffice, sizeof(LibreOfficeKit)); } +__attribute__ ((visibility("default"))) +AAssetManager * +lo_get_native_assetmgr(void) +{ + return native_asset_manager; +} + + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/android/lo-bootstrap.c b/sal/android/lo-bootstrap.c index 5c47b8f6bd2f..7a2da48eb93b 100644 --- a/sal/android/lo-bootstrap.c +++ b/sal/android/lo-bootstrap.c @@ -311,10 +311,6 @@ Java_org_libreoffice_android_Bootstrap_setup__Ljava_lang_String_2Ljava_lang_Stri if (!setup_assets_tree()) return JNI_FALSE; - // Extract files from the .apk that can't be used mmapped directly from it - extract_files(UNPACK_TREE, UNPACK_TREE, 0); - extract_files(UNPACK_TREE_GZ, UNPACK_TREE_GZ, 1); - return JNI_TRUE; } @@ -635,184 +631,6 @@ lo_apk_lstat(const char *path, return -1; } -static int -mkdir_p(const char *dirname) -{ - char *p = malloc(strlen(dirname) + 1); - const char *q = dirname + 1; - const char *slash; - - do { - slash = strchr(q, '/'); - if (slash == NULL) - slash = q + strlen(q); - memcpy(p, dirname, slash-dirname); - p[slash-dirname] = '\0'; - if (mkdir(p, 0700) == -1 && errno != EEXIST) { - LOGE("mkdir_p: Could not create %s: %s", p, strerror(errno)); - free(p); - return 0; - } - if (*slash) - q = slash + 1; - } while (*slash); - - free(p); - return 1; -} - -static int -extract_gzipped(const char *filename, - const char *apkentry, - int size, - FILE *f) -{ - gzFile gzfd; - int gzerrno; - int nbytes; - char buf[5000]; - int total = 0; - char *tmpname; - FILE *tmp; - - tmpname = malloc(strlen(cache_dir) + strlen("/tmp.gz") + 1); - strcpy(tmpname, cache_dir); - strcat(tmpname, "/tmp.gz"); - - tmp = fopen(tmpname, "w+"); - unlink(tmpname); - - if (tmp == NULL) { - LOGE("extract_gzipped: could not create %s: %s", tmpname, strerror(errno)); - free(tmpname); - return 0; - } - - if (fwrite(apkentry, size, 1, tmp) != 1) { - LOGE("extract_gzipped: could not write gzipped entry to %s: %s", tmpname, strerror(errno)); - fclose(tmp); - free(tmpname); - return 0; - } - - free(tmpname); - rewind(tmp); - - gzfd = gzdopen(fileno(tmp), "rb"); - if (gzfd == NULL) { - LOGE("extract_gzipped: gzdopen failed"); - fclose(tmp); - return 0; - } - - while ((nbytes = gzread(gzfd, buf, sizeof(buf))) > 0) { - fwrite(buf, nbytes, 1, f); - total += nbytes; - } - if (nbytes == -1) { - LOGE("extract_gzipped: Could not gzread from %s: %s", filename, gzerror(gzfd, &gzerrno)); - return total; - } - if (gzclose(gzfd) == -1) { - LOGE("extract_gzipped: gzclose failed"); - return total; - } - - return total; -} - -void -extract_files(const char *root, - const char *prefix, - int gzipped) -{ - lo_apk_dir *tree = lo_apk_opendir(prefix); - struct dirent *dent; - - if (tree == NULL) - return; - - while ((dent = lo_apk_readdir(tree)) != NULL) { - if (strcmp(dent->d_name, ".") == 0 || - strcmp(dent->d_name, "..") == 0) - continue; - - if (dent->d_type == DT_DIR) { - char *subdir = malloc(strlen(prefix) + 1 + strlen(dent->d_name) + 1); - strcpy(subdir, prefix); - strcat(subdir, "/"); - strcat(subdir, dent->d_name); - extract_files(root, subdir, gzipped); - free(subdir); - } else { - char *filename; - char *newfilename; - const char *apkentry; - size_t size; - struct stat st; - FILE *f; - - filename = malloc(strlen(prefix) + 1 + strlen(dent->d_name) + 1); - strcpy(filename, prefix); - strcat(filename, "/"); - strcat(filename, dent->d_name); - - apkentry = lo_apkentry(filename, &size); - if (apkentry == NULL) { - LOGE("extract_files: Could not find %s in .apk", filename); - free(filename); - continue; - } - - newfilename = malloc(strlen(data_dir) + 1 + strlen(prefix) - strlen(root) + strlen(dent->d_name) + 1); - strcpy(newfilename, data_dir); - strcat(newfilename, "/"); - strcat(newfilename, prefix + strlen(root) + 1); - - if (!mkdir_p(newfilename)) { - free(filename); - free(newfilename); - continue; - } - - strcat(newfilename, "/"); - strcat(newfilename, dent->d_name); - - if (stat(newfilename, &st) == 0 && - (gzipped || st.st_size == (long long) size)) { - free(filename); - free(newfilename); - continue; - } - - f = fopen(newfilename, "w"); - if (f == NULL) { - LOGE("extract_files: Could not open %s for writing: %s", newfilename, strerror(errno)); - free(filename); - free(newfilename); - continue; - } - - if (!gzipped) { - if (fwrite(apkentry, size, 1, f) != 1) { - LOGE("extract_files: Could not write %lld bytes to %s: %s", (long long) size, newfilename, strerror(errno)); - } else { - LOGI("extract_files: Copied %s to %s: %lld bytes", filename, newfilename, (long long) size); - } - } else { - size = extract_gzipped(filename, apkentry, size, f); - LOGI("extract_files: Decompressed %s to %s: %lld bytes", filename, newfilename, (long long) size); - } - - fclose(f); - - free(filename); - free(newfilename); - } - } - lo_apk_closedir(tree); -} - /* Android's JNI works only to libraries loaded through Java's * System.loadLibrary(), it seems. But now with just one big app-specific .so * on Android, that would not be a problem, but for historical reasons, we diff --git a/sal/osl/unx/file.cxx b/sal/osl/unx/file.cxx index 62cb12978c41..908373cacf09 100644 --- a/sal/osl/unx/file.cxx +++ b/sal/osl/unx/file.cxx @@ -57,6 +57,8 @@ #ifdef ANDROID #include <osl/detail/android-bootstrap.h> +#include <android/log.h> +#include <android/asset_manager.h> #endif /******************************************************************* @@ -830,6 +832,21 @@ openFilePath( const char *cpFilePath, oslFileHandle* pHandle, sal_uInt32 uFlags, */ if (strncmp (cpFilePath, "/assets/", sizeof ("/assets/") - 1) == 0) { + void* address; + size_t size; + AAssetManager* mgr = lo_get_native_assetmgr(); + AAsset* asset = AAssetManager_open(mgr, cpFilePath + sizeof("/assets/")-1, AASSET_MODE_BUFFER); + if (NULL == asset) { + address = NULL; + errno = ENOENT; + __android_log_print(ANDROID_LOG_ERROR,"libo:sal/osl/unx/file", "failed to open %s", cpFilePath); + return osl_File_E_NOENT; + } else { + size = AAsset_getLength(asset); + address = malloc (sizeof(char)*size); + AAsset_read (asset,address,size); + AAsset_close(asset); + } if (uFlags & osl_File_OpenFlag_Write) { // It seems to work better to silently "open" it read-only @@ -837,15 +854,7 @@ openFilePath( const char *cpFilePath, oslFileHandle* pHandle, sal_uInt32 uFlags, // loading a document from /assets fails with that idiotic // "General Error" dialog... } - void *address; - size_t size; - address = lo_apkentry(cpFilePath, &size); SAL_INFO("sal.file", "osl_openFile(" << cpFilePath << ") => " << address); - if (address == NULL) - { - errno = ENOENT; - return osl_File_E_NOENT; - } return openMemoryAsFile(address, size, pHandle, cpFilePath); } #endif @@ -1043,6 +1052,10 @@ SAL_CALL osl_closeFile( oslFileHandle Handle ) if (pImpl->m_kind == FileHandle_Impl::KIND_MEM) { +#ifdef ANDROID + free(pImpl->m_buffer); + pImpl->m_buffer = NULL; +#endif delete pImpl; return osl_File_E_None; } |