forked from organicmaps/organicmaps-tmp
[android] Resources are unzipped from apk on first launch and copied to /sdcard/MapsWithMe/
This commit is contained in:
parent
2499a05544
commit
dc4549d926
9 changed files with 312 additions and 9 deletions
|
@ -24,15 +24,20 @@
|
|||
<supports-screen android:largeScreens="true"/>
|
||||
|
||||
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name">
|
||||
<activity android:name="com.mapswithme.maps.MWMActivity"
|
||||
<activity android:name="com.mapswithme.maps.CopyResourcesActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="behind"
|
||||
android:theme="@style/MWMNoTitle">
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.mapswithme.maps.MWMActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="behind"
|
||||
android:theme="@style/MWMNoTitle">
|
||||
</activity>
|
||||
<activity android:name="com.mapswithme.maps.DownloadUI"
|
||||
android:label="@string/download_maps"
|
||||
android:screenOrientation="behind">
|
||||
|
|
|
@ -34,6 +34,7 @@ LOCAL_SRC_FILES := \
|
|||
com/mapswithme/maps/VideoTimer.cpp \
|
||||
com/mapswithme/maps/MWMActivity.cpp \
|
||||
com/mapswithme/maps/Lifecycle.cpp \
|
||||
com/mapswithme/maps/CopyResourcesActivity.cpp \
|
||||
com/mapswithme/platform/Platform.cpp \
|
||||
com/mapswithme/platform/HttpThread.cpp \
|
||||
com/mapswithme/platform/Language.cpp \
|
||||
|
|
118
android/jni/com/mapswithme/maps/CopyResourcesActivity.cpp
Normal file
118
android/jni/com/mapswithme/maps/CopyResourcesActivity.cpp
Normal file
|
@ -0,0 +1,118 @@
|
|||
#include <jni.h>
|
||||
// To get free disk space
|
||||
#include <sys/vfs.h>
|
||||
|
||||
#include "../../../../../base/logging.hpp"
|
||||
|
||||
#include "../../../../../coding/zip_reader.hpp"
|
||||
|
||||
#include "../../../../../platform/platform.hpp"
|
||||
|
||||
#include "../../../../../std/vector.hpp"
|
||||
#include "../../../../../std/string.hpp"
|
||||
|
||||
// Special error codes to notify GUI about free space
|
||||
//@{
|
||||
#define ERR_COPIED_SUCCESSFULLY 0
|
||||
#define ERR_NOT_ENOUGH_MEMORY -1
|
||||
#define ERR_NOT_ENOUGH_FREE_SPACE -2
|
||||
#define ERR_STORAGE_DISCONNECTED -3
|
||||
//@}
|
||||
|
||||
struct FileToCopy
|
||||
{
|
||||
string m_pathInZip;
|
||||
string m_pathInSdcard;
|
||||
uint64_t m_uncompressedSize;
|
||||
};
|
||||
|
||||
static string g_apkPath;
|
||||
static string g_sdcardPath;
|
||||
static vector<FileToCopy> g_filesToCopy;
|
||||
static jint g_copiedBytesProgress = 0;
|
||||
|
||||
extern "C"
|
||||
{
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_mapswithme_maps_CopyResourcesActivity_nativeGetBytesToCopy(JNIEnv * env, jobject thiz,
|
||||
jstring apkPath, jstring sdcardPath)
|
||||
{
|
||||
{
|
||||
char const * strApkPath = env->GetStringUTFChars(apkPath, 0);
|
||||
if (!strApkPath)
|
||||
return ERR_NOT_ENOUGH_MEMORY;
|
||||
g_apkPath = strApkPath;
|
||||
env->ReleaseStringUTFChars(apkPath, strApkPath);
|
||||
|
||||
char const * strSdcardPath = env->GetStringUTFChars(sdcardPath, 0);
|
||||
if (!strSdcardPath)
|
||||
return ERR_NOT_ENOUGH_MEMORY;
|
||||
g_sdcardPath = strSdcardPath;
|
||||
env->ReleaseStringUTFChars(sdcardPath, strSdcardPath);
|
||||
}
|
||||
|
||||
jint totalSizeToCopy = 0;
|
||||
// Get files to copy from apk assets folder
|
||||
string const ASSETS_PREFIX = "assets/";
|
||||
Platform::FilesList zipFiles = ZipFileReader::FilesList(g_apkPath);
|
||||
// Leave only assets/* files and check that all files from apk are copied on the sdcard
|
||||
for (Platform::FilesList::iterator it = zipFiles.begin(); it != zipFiles.end();)
|
||||
{
|
||||
if (it->find(ASSETS_PREFIX) != 0)
|
||||
it = zipFiles.erase(it);
|
||||
else
|
||||
{
|
||||
FileToCopy f;
|
||||
f.m_pathInZip = *it;
|
||||
f.m_pathInSdcard = g_sdcardPath + it->substr(ASSETS_PREFIX.size());
|
||||
f.m_uncompressedSize = ZipFileReader(g_apkPath, *it).UncompressedSize();
|
||||
|
||||
uint64_t realSizeInSdcard = 0;
|
||||
Platform::GetFileSizeByFullPath(f.m_pathInSdcard, realSizeInSdcard);
|
||||
if (f.m_uncompressedSize != realSizeInSdcard)
|
||||
{
|
||||
g_filesToCopy.push_back(f);
|
||||
totalSizeToCopy += f.m_uncompressedSize;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return totalSizeToCopy;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_mapswithme_maps_CopyResourcesActivity_nativeCopyNextFile(JNIEnv * env, jobject thiz)
|
||||
{
|
||||
if (g_filesToCopy.empty())
|
||||
return ERR_COPIED_SUCCESSFULLY;
|
||||
|
||||
vector<FileToCopy>::iterator it = g_filesToCopy.begin() + g_filesToCopy.size() - 1;
|
||||
// Check for free space available on the device
|
||||
struct statfs st;
|
||||
if (statfs(g_sdcardPath.c_str(), &st) != 0)
|
||||
{
|
||||
LOG(LWARNING, ("External filesystem is not available"));
|
||||
return ERR_STORAGE_DISCONNECTED;
|
||||
}
|
||||
if (st.f_bsize * st.f_bavail <= it->m_uncompressedSize)
|
||||
{
|
||||
LOG(LERROR, ("Not enough free space to extract file", it->m_pathInSdcard));
|
||||
return ERR_NOT_ENOUGH_FREE_SPACE;
|
||||
}
|
||||
|
||||
// Perform copying
|
||||
try
|
||||
{
|
||||
ZipFileReader::UnzipFile(g_apkPath, it->m_pathInZip, it->m_pathInSdcard);
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LERROR, ("Error while extracting", it->m_pathInZip, "from apk to", it->m_pathInSdcard));
|
||||
return ERR_NOT_ENOUGH_FREE_SPACE;
|
||||
}
|
||||
g_copiedBytesProgress += it->m_uncompressedSize;
|
||||
g_filesToCopy.erase(it);
|
||||
return g_copiedBytesProgress;
|
||||
}
|
||||
}
|
|
@ -22,4 +22,5 @@
|
|||
<string name="close">Cerrar</string>
|
||||
<string name="empty_model">Para poder ver detalles\n de esta zona debes descargar\nel mapa de este país. Pulse el botón\nbajo la pantalla para descargar.</string>
|
||||
<string name="unsupported_phone">Un hardware aceleró OpenGL se requiere. Desgraciadamente, el dispositivo móvil no es compatible.</string>
|
||||
<string name="loading">Carga de %s...</string>
|
||||
</resources>
|
||||
|
|
|
@ -22,4 +22,5 @@
|
|||
<string name="close">Fermer</string>
|
||||
<string name="empty_model">Pour scharger cette carte,\ntoucher le bouton au bas de l`écran</string>
|
||||
<string name="unsupported_phone">Le programme a besoin d`OpenGL pour travailler. Votre appareil n`est pas appuyé maleuresement.</string>
|
||||
<string name="loading">Chargement %s...</string>
|
||||
</resources>
|
||||
|
|
|
@ -26,4 +26,7 @@
|
|||
<string name="disconnect_usb_cable">Отключите USB кабель или вставьте SD-карту</string>
|
||||
<string name="empty_model">Чтобы загрузить карту\nэтого места, нажмите на\nкнопку загрузки внизу экрана</string>
|
||||
<string name="unsupported_phone">Для работы приложения необходим аппаратно ускоренный OpenGL. К сожалению, ваше устройство не поддерживается.</string>
|
||||
<string name="not_enough_free_space_on_sdcard">Недостаточно свободного места на SD карте/в памяти устройства для использования программы</string>
|
||||
<string name="loading">Запуск %s…</string>
|
||||
<string name="not_enough_memory">Недостаточно памяти для запуска программы</string>
|
||||
</resources>
|
||||
|
|
|
@ -22,8 +22,11 @@
|
|||
<string name="miles">Miles</string>
|
||||
<string name="kilometres">Kilometres</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="external_storage_is_not_available">External storage memory is not available</string>
|
||||
<string name="external_storage_is_not_available">SD card/USB storage with downloaded maps is not available</string>
|
||||
<string name="disconnect_usb_cable">Please disconnect USB cable or insert memory card to use MapsWithMe</string>
|
||||
<string name="empty_model">Nothing found. Have you tried\ndownloading maps of the countries?\nJust click the download button\nat the bottom of the screen.</string>
|
||||
<string name="unsupported_phone">A hardware accelerated OpenGL is required. Unfortunately, your device is not supported.</string>
|
||||
<string name="not_enough_free_space_on_sdcard">Please free some space on SD card/USB storage first in order to use the app</string>
|
||||
<string name="loading">Loading %s…</string>
|
||||
<string name="not_enough_memory">Not enough memory to launch app</string>
|
||||
</resources>
|
||||
|
|
165
android/src/com/mapswithme/maps/CopyResourcesActivity.java
Normal file
165
android/src/com/mapswithme/maps/CopyResourcesActivity.java
Normal file
|
@ -0,0 +1,165 @@
|
|||
package com.mapswithme.maps;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
// MWM resources are zipped in the apk inside /assets folder.
|
||||
// MWM code currently can't randomly read zip files, so they're copied
|
||||
// on the first start to the sdcard and used from there
|
||||
public class CopyResourcesActivity extends Activity
|
||||
{
|
||||
private ProgressDialog m_dialog = null;
|
||||
|
||||
// Error codes, should match the same codes in JNI
|
||||
private static final int ERR_COPIED_SUCCESSFULLY = 0;
|
||||
private static final int ERR_NOT_ENOUGH_MEMORY = -1;
|
||||
private static final int ERR_NOT_ENOUGH_FREE_SPACE = -2;
|
||||
private static final int ERR_STORAGE_DISCONNECTED = -3;
|
||||
|
||||
// Copies assets files to external folder on sdcard in the background
|
||||
public class CopyResourcesTask extends AsyncTask<Void, Integer, Integer>
|
||||
{
|
||||
private final String m_apkPath;
|
||||
private final String m_sdcardPath;
|
||||
|
||||
CopyResourcesTask(String apkPath, String sdcardPath)
|
||||
{
|
||||
m_apkPath = apkPath;
|
||||
m_sdcardPath = sdcardPath;
|
||||
// Create sdcard folder if it doesn't exist
|
||||
new File(sdcardPath).mkdirs();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute()
|
||||
{
|
||||
// Check if we need to perform any copying
|
||||
final int bytes = nativeGetBytesToCopy(m_apkPath, m_sdcardPath);
|
||||
if (bytes > 0)
|
||||
{
|
||||
// Display copy progress dialog
|
||||
CopyResourcesActivity.this.showDialog(bytes);
|
||||
}
|
||||
else if (bytes == 0)
|
||||
{
|
||||
// All files are in place, continue with UI initialization
|
||||
cancel(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display error dialog in UI, very rare case
|
||||
CopyResourcesActivity.this.onCopyTaskFinished(ERR_NOT_ENOUGH_MEMORY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer result)
|
||||
{
|
||||
CopyResourcesActivity.this.onCopyTaskFinished(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled()
|
||||
{
|
||||
// In our logic cancelled means that files shouldn't be copied,
|
||||
// So we just proceed to main UI initialization
|
||||
CopyResourcesActivity.this.onCopyTaskFinished(ERR_COPIED_SUCCESSFULLY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... copiedBytes)
|
||||
{
|
||||
CopyResourcesActivity.this.onCopyResourcesProgress(copiedBytes[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... p)
|
||||
{
|
||||
// If negative, stores error code
|
||||
int bytesCopied;
|
||||
do
|
||||
{
|
||||
bytesCopied = nativeCopyNextFile();
|
||||
if (bytesCopied > 0)
|
||||
publishProgress(new Integer(bytesCopied));
|
||||
else if (bytesCopied < 0)
|
||||
return new Integer(bytesCopied);
|
||||
} while (bytesCopied != 0);
|
||||
|
||||
return new Integer(ERR_COPIED_SUCCESSFULLY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
// Create invisible view, only progress dialog will be visible if needed
|
||||
View v = new View(this);
|
||||
v.setVisibility(View.INVISIBLE);
|
||||
setContentView(v);
|
||||
// All copy checks are made in the background task
|
||||
new CopyResourcesTask(MWMActivity.getApkPath(this), MWMActivity.getDataStoragePath()).execute();
|
||||
}
|
||||
|
||||
public void onCopyTaskFinished(int result)
|
||||
{
|
||||
if (result == ERR_COPIED_SUCCESSFULLY)
|
||||
{
|
||||
// Continue with Main UI initialization (MWMActivity)
|
||||
startActivity(new Intent(this, MWMActivity.class));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hide loading progress
|
||||
if (m_dialog != null)
|
||||
m_dialog.dismiss();
|
||||
|
||||
int errMsgId;
|
||||
switch (result)
|
||||
{
|
||||
case ERR_NOT_ENOUGH_FREE_SPACE: errMsgId = R.string.not_enough_free_space_on_sdcard; break;
|
||||
case ERR_STORAGE_DISCONNECTED: errMsgId = R.string.disconnect_usb_cable; break;
|
||||
default: errMsgId = R.string.not_enough_memory;
|
||||
}
|
||||
// Display Error dialog with close button
|
||||
new AlertDialog.Builder(this).setMessage(errMsgId)
|
||||
.setPositiveButton(R.string.close, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
CopyResourcesActivity.this.finish();
|
||||
CopyResourcesActivity.this.moveTaskToBack(true);
|
||||
}})
|
||||
.create().show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int totalBytesToCopy)
|
||||
{
|
||||
m_dialog = new ProgressDialog(this);
|
||||
m_dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
m_dialog.setMessage(String.format(getString(R.string.loading), getString(R.string.app_name)));
|
||||
m_dialog.setCancelable(false);
|
||||
m_dialog.setIndeterminate(false);
|
||||
m_dialog.setMax(totalBytesToCopy);
|
||||
return m_dialog;
|
||||
}
|
||||
|
||||
public void onCopyResourcesProgress(int copiedBytes)
|
||||
{
|
||||
if (m_dialog != null)
|
||||
m_dialog.setProgress(copiedBytes);
|
||||
}
|
||||
|
||||
private native int nativeGetBytesToCopy(String m_apkPath, String m_sdcardPath);
|
||||
private native int nativeCopyNextFile();
|
||||
}
|
|
@ -42,11 +42,11 @@ public class MWMActivity extends NvEventQueueActivity implements
|
|||
private static Context m_context = null;
|
||||
public static Context getCurrentContext() { return m_context; }
|
||||
|
||||
private String getAppBundlePath()
|
||||
public static String getApkPath(Activity activity)
|
||||
{
|
||||
try
|
||||
{
|
||||
return getApplication().getPackageManager().getApplicationInfo(PACKAGE_NAME, 0).sourceDir;
|
||||
return activity.getApplication().getPackageManager().getApplicationInfo(PACKAGE_NAME, 0).sourceDir;
|
||||
} catch (NameNotFoundException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
|
@ -54,11 +54,17 @@ public class MWMActivity extends NvEventQueueActivity implements
|
|||
return "";
|
||||
}
|
||||
|
||||
private String getDataStoragePath(String folder)
|
||||
public static String getDataStoragePath()
|
||||
{
|
||||
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/MapsWithMe/";
|
||||
}
|
||||
|
||||
public static String getExtAppDirectoryPath(String folder)
|
||||
{
|
||||
final String storagePath = Environment.getExternalStorageDirectory().getAbsolutePath();
|
||||
return storagePath.concat(String.format("/Android/data/%s/%s/", PACKAGE_NAME, folder));
|
||||
}
|
||||
|
||||
// Note: local storage memory is limited on some devices!
|
||||
private String getTmpPath()
|
||||
{
|
||||
|
@ -208,8 +214,8 @@ public class MWMActivity extends NvEventQueueActivity implements
|
|||
|
||||
m_context = this;
|
||||
|
||||
final String extStoragePath = getDataStoragePath("files");
|
||||
final String extTmpPath = getDataStoragePath("caches");
|
||||
final String extStoragePath = getDataStoragePath();
|
||||
final String extTmpPath = getExtAppDirectoryPath("caches");
|
||||
// Create folders if they don't exist
|
||||
new File(extStoragePath).mkdirs();
|
||||
new File(extTmpPath).mkdirs();
|
||||
|
@ -221,7 +227,7 @@ public class MWMActivity extends NvEventQueueActivity implements
|
|||
nativeInit(metrics.densityDpi,
|
||||
metrics.widthPixels,
|
||||
metrics.heightPixels,
|
||||
getAppBundlePath(),
|
||||
getApkPath(this),
|
||||
extStoragePath,
|
||||
getTmpPath(),
|
||||
extTmpPath,
|
||||
|
|
Loading…
Add table
Reference in a new issue