[android] when changing the resource path, recreate database instead of the whole FileSource

This commit is contained in:
Łukasz Paczos
2019-02-19 13:23:49 +01:00
committed by Łukasz Paczos
parent 3f7b70fdb0
commit b7702995fc
5 changed files with 120 additions and 106 deletions

View File

@@ -8,7 +8,6 @@ import android.os.Looper;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import com.mapbox.mapboxsdk.LibraryLoader;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.R;
@@ -134,14 +133,6 @@ public class OfflineManager {
deleteAmbientDatabase(this.context);
}
/**
* Clears the current instance of the offline manager.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static void clear() {
instance = null;
}
private void deleteAmbientDatabase(final Context context) {
final String path = FileSource.getInternalCachePath(context) + File.separator + "mbgl-cache.db";
FileUtils.deleteFile(path);
@@ -461,17 +452,17 @@ public class OfflineManager {
* resource were requested in the process of map rendering.
* Use this method to pre-warm the cache with resources you know
* will be requested.
*
* <p>
* This call is asynchronous: the data may not be immediately available
* for in-progress requests, although subsequent requests should have
* access to the cached data.
*
* @param url The URL of the resource to insert
* @param data Response data to store for this resource. Data is expected to be uncompressed;
* internally, the cache will compress data as necessary.
* @param modified Optional "modified" response header, in seconds since 1970, or 0 if not set
* @param expires Optional "expires" response header, in seconds since 1970, or 0 if not set
* @param etag Optional "entity tag" response header
* @param url The URL of the resource to insert
* @param data Response data to store for this resource. Data is expected to be uncompressed;
* internally, the cache will compress data as necessary.
* @param modified Optional "modified" response header, in seconds since 1970, or 0 if not set
* @param expires Optional "expires" response header, in seconds since 1970, or 0 if not set
* @param etag Optional "entity tag" response header
* @param mustRevalidate Indicates whether response can be used after it's stale
*/
@Keep

View File

@@ -7,17 +7,16 @@ import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import com.mapbox.mapboxsdk.MapStrictMode;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.log.Logger;
import com.mapbox.mapboxsdk.offline.OfflineManager;
import com.mapbox.mapboxsdk.utils.FileUtils;
import com.mapbox.mapboxsdk.utils.ThreadUtils;
import java.io.File;
@@ -64,7 +63,7 @@ public class FileSource {
* resources cache database.
*/
@Keep
public interface SetResourcesCachePathCallback {
public interface ResourcesCachePathChangeCallback {
/**
* Receives the new database path
@@ -117,7 +116,7 @@ public class FileSource {
// Reset stored cache path
SharedPreferences.Editor editor =
context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit();
context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit();
editor.remove(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH).apply();
}
@@ -148,11 +147,11 @@ public class FileSource {
try {
// Try getting a custom value from the app Manifest
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(),
PackageManager.GET_META_DATA);
PackageManager.GET_META_DATA);
if (appInfo.metaData != null) {
isExternalStorageConfiguration = appInfo.metaData.getBoolean(
MapboxConstants.KEY_META_DATA_SET_STORAGE_EXTERNAL,
MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL
MapboxConstants.KEY_META_DATA_SET_STORAGE_EXTERNAL,
MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL
);
}
} catch (PackageManager.NameNotFoundException exception) {
@@ -182,8 +181,8 @@ public class FileSource {
}
Logger.w(TAG, "External storage was requested but it isn't readable. For API level < 18"
+ " make sure you've requested READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE"
+ " permissions in your app Manifest (defaulting to internal storage).");
+ " make sure you've requested READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE"
+ " permissions in your app Manifest (defaulting to internal storage).");
return false;
}
@@ -212,9 +211,9 @@ public class FileSource {
@NonNull
@Override
protected String[] doInBackground(Context... contexts) {
return new String[]{
getCachePath(contexts[0]),
contexts[0].getCacheDir().getAbsolutePath()
return new String[] {
getCachePath(contexts[0]),
contexts[0].getCacheDir().getAbsolutePath()
};
}
@@ -273,67 +272,66 @@ public class FileSource {
*/
public static void setResourcesCachePath(@NonNull Context context,
@NonNull final String path,
@NonNull final SetResourcesCachePathCallback callback) {
@NonNull ResourcesCachePathChangeCallback callback) {
final String fileSourceActivatedMessage = "Cannot set path, file source is activated."
+ " Make sure that the map or a resources download is not running.";
if (getInstance(context).isActivated()) {
final String activatedMessage = "Cannot set path, file source is activated!";
Logger.w(TAG, activatedMessage);
callback.onError(activatedMessage);
Logger.w(TAG, fileSourceActivatedMessage);
callback.onError(fileSourceActivatedMessage);
} else if (path.equals(resourcesCachePath)) {
// no need to change the path
callback.onSuccess(path);
} else {
if (path.equals(resourcesCachePath)) {
// no need to change the path
callback.onSuccess(path);
} else {
final WeakReference<Context> contextWeakReference = new WeakReference<>(context);
new Thread(new Runnable() {
@Override
public void run() {
final Context context = contextWeakReference.get();
final String message;
if (context != null) {
if (!isPathWritable(path)) {
message = "Path is not writable: " + path;
} else {
message = null;
final WeakReference<Context> contextWeakReference = new WeakReference<>(context);
final WeakReference<ResourcesCachePathChangeCallback> callbackWeakReference = new WeakReference<>(callback);
new FileUtils.CheckFileWritePermissionTask(new FileUtils.OnCheckFileWritePermissionListener() {
@Override
public void onWritePermissionGranted() {
final Context context = contextWeakReference.get();
final ResourcesCachePathChangeCallback callback = callbackWeakReference.get();
final SharedPreferences.Editor editor =
context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit();
if (!editor.putString(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH, path).commit()) {
Logger.w(TAG, "Cannot store cache path in shared preferences.");
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
setResourcesCachePath(context, path);
callback.onSuccess(path);
}
});
}
} else {
message = "Context is null";
}
if (message != null) {
Logger.w(TAG, message);
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
callback.onError(message);
}
});
}
if (callback == null) {
Logger.w(TAG, "Lost callback reference.");
return;
} else if (context == null) {
String lostContextMessage = "Lost context reference.";
Logger.w(TAG, lostContextMessage);
callback.onError(lostContextMessage);
return;
}
}).start();
}
// verify fileSource's activation again after the async task returns
if (getInstance(context).isActivated()) {
Logger.w(TAG, fileSourceActivatedMessage);
callback.onError(fileSourceActivatedMessage);
} else {
final SharedPreferences.Editor editor =
context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit();
editor.putString(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH, path);
editor.apply();
setResourcesCachePath(context, path);
callback.onSuccess(path);
}
}
@Override
public void onError() {
final ResourcesCachePathChangeCallback callback = callbackWeakReference.get();
if (callback != null) {
String message = "Path is not writable: " + path;
Logger.e(TAG, message);
callback.onError(message);
}
}
}).execute(new File(path));
}
}
private static void setResourcesCachePath(@NonNull Context context, @NonNull String path) {
resourcesCachePathLoaderLock.lock();
resourcesCachePath = path;
reinitializeOfflineManager(context);
resourcesCachePathLoaderLock.unlock();
getInstance(context).setResourceCachePath(path);
}
private static boolean isPathWritable(String path) {
@@ -343,12 +341,6 @@ public class FileSource {
return new File(path).canWrite();
}
private static void reinitializeOfflineManager(@NonNull Context context) {
final FileSource fileSource = FileSource.getInstance(context);
fileSource.initialize(Mapbox.getAccessToken(), resourcesCachePath, context.getResources().getAssets());
OfflineManager.clear();
}
private static void lockPathLoaders() {
internalCachePathLoaderLock.lock();
resourcesCachePathLoaderLock.lock();
@@ -396,6 +388,9 @@ public class FileSource {
@Keep
public native void setResourceTransform(final ResourceTransformCallback callback);
@Keep
private native void setResourceCachePath(String path);
@Keep
private native void initialize(String accessToken, String cachePath, AssetManager assetManager);

View File

@@ -12,27 +12,36 @@ import android.widget.AdapterView
import android.widget.BaseAdapter
import android.widget.TextView
import android.widget.Toast
import com.mapbox.mapboxsdk.log.Logger
import com.mapbox.mapboxsdk.offline.OfflineManager
import com.mapbox.mapboxsdk.offline.OfflineRegion
import com.mapbox.mapboxsdk.storage.FileSource
import com.mapbox.mapboxsdk.testapp.R
import kotlinx.android.synthetic.main.activity_change_resources_cache_path.*
import java.io.File
class ChangeResourcesCachePathActivity : AppCompatActivity(),
AdapterView.OnItemClickListener,
FileSource.SetResourcesCachePathCallback {
AdapterView.OnItemClickListener,
FileSource.ResourcesCachePathChangeCallback {
lateinit var adapter: PathAdapter
companion object {
private const val TAG = "Mbgl-ChangeResourcesCachePathActivity"
}
private lateinit var adapter: PathAdapter
private lateinit var offlineManager: OfflineManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_change_resources_cache_path)
Thread(Runnable {
adapter = PathAdapter(this, obtainFilesPaths(this))
listView.adapter = adapter
listView.emptyView = empty
listView.onItemClickListener = this
}).start()
adapter = PathAdapter(this, obtainExternalFilesPaths())
listView.adapter = adapter
listView.emptyView = empty
listView.onItemClickListener = this
offlineManager = OfflineManager.getInstance(this)
}
override fun onStart() {
@@ -55,23 +64,33 @@ class ChangeResourcesCachePathActivity : AppCompatActivity(),
override fun onSuccess(path: String?) {
listView.onItemClickListener = this
Toast.makeText(this, "New path: $path", Toast.LENGTH_LONG).show()
offlineManager.listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback {
override fun onList(offlineRegions: Array<out OfflineRegion>?) {
Logger.i(TAG, "Number of saved offline regions in the new path: ${offlineRegions?.size.toString()}")
}
override fun onError(error: String?) {
Logger.e(TAG, error)
}
})
}
private fun obtainFilesPaths(context: Context): List<String> {
private fun Context.obtainExternalFilesPaths(): List<String> {
val paths = ArrayList<String>()
paths.add(context.filesDir.absolutePath)
paths.add(this.filesDir.absolutePath)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
paths.addAll(obtainExternalFilesPathsKitKat(context))
paths.addAll(obtainExternalFilesPathsKitKat())
} else {
paths.addAll(obtainExternalFilesPathsLegacy(context))
paths.addAll(obtainExternalFilesPathsLegacy())
}
paths.add("${File.separator}invalid${File.separator}cache${File.separator}path")
return paths
}
private fun obtainExternalFilesPathsLegacy(context: Context): List<String> {
private fun Context.obtainExternalFilesPathsLegacy(): List<String> {
val postFix =
"${File.separator}Android${File.separator}data${File.separator}${context.packageName}${File.separator}files"
"${File.separator}Android${File.separator}data${File.separator}${this.packageName}${File.separator}files"
val paths = ArrayList<String>()
val externalStorage = System.getenv("EXTERNAL_STORAGE")
val secondaryStorage = System.getenv("SECONDARY_STORAGE")
@@ -88,9 +107,9 @@ class ChangeResourcesCachePathActivity : AppCompatActivity(),
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private fun obtainExternalFilesPathsKitKat(context: Context): List<String> {
private fun Context.obtainExternalFilesPathsKitKat(): List<String> {
val paths = ArrayList<String>()
val extDirs = context.getExternalFilesDirs(null)
val extDirs = this.getExternalFilesDirs(null)
for (dir in extDirs) {
if (dir != null) {
paths.add(dir.absolutePath)
@@ -105,12 +124,10 @@ class ChangeResourcesCachePathActivity : AppCompatActivity(),
return paths[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getCount(): Int {
return paths.size
}

View File

@@ -19,11 +19,12 @@ FileSource::FileSource(jni::JNIEnv& _env,
const jni::String& accessToken,
const jni::String& _cachePath,
const jni::Object<AssetManager>& assetManager) {
mapbox::sqlite::setTempPath(jni::Make<std::string>(_env, _cachePath));
std::string path = jni::Make<std::string>(_env, _cachePath);
mapbox::sqlite::setTempPath(path);
// Create a core default file source
fileSource = std::make_unique<mbgl::DefaultFileSource>(
jni::Make<std::string>(_env, _cachePath) + "/mbgl-offline.db",
path + DATABASE_FILE,
std::make_unique<AssetManagerFileSource>(_env, assetManager));
// Set access token
@@ -69,6 +70,12 @@ void FileSource::setResourceTransform(jni::JNIEnv& env, const jni::Object<FileSo
}
}
void FileSource::setResourceCachePath(jni::JNIEnv& env, const jni::String& path) {
std::string newPath = jni::Make<std::string>(env, path);
mapbox::sqlite::setTempPath(newPath);
fileSource->setResourceCachePath(newPath + DATABASE_FILE);
}
void FileSource::resume(jni::JNIEnv&) {
if (!activationCounter) {
activationCounter = optional<int>(1) ;
@@ -129,6 +136,7 @@ void FileSource::registerNative(jni::JNIEnv& env) {
METHOD(&FileSource::setAccessToken, "setAccessToken"),
METHOD(&FileSource::setAPIBaseUrl, "setApiBaseUrl"),
METHOD(&FileSource::setResourceTransform, "setResourceTransform"),
METHOD(&FileSource::setResourceCachePath, "setResourceCachePath"),
METHOD(&FileSource::resume, "activate"),
METHOD(&FileSource::pause, "deactivate"),
METHOD(&FileSource::isResumed, "isActivated")

View File

@@ -39,6 +39,8 @@ public:
void setResourceTransform(jni::JNIEnv&, const jni::Object<FileSource::ResourceTransformCallback>&);
void setResourceCachePath(jni::JNIEnv&, const jni::String&);
void resume(jni::JNIEnv&);
void pause(jni::JNIEnv&);
@@ -52,6 +54,7 @@ public:
static void registerNative(jni::JNIEnv&);
private:
const std::string DATABASE_FILE = "/mbgl-offline.db";
optional<int> activationCounter;
std::unique_ptr<Actor<ResourceTransform>> resourceTransform;
std::unique_ptr<mbgl::DefaultFileSource> fileSource;