[android] Feature: Change path of the resources cache db

This commit is contained in:
Arne Kaiser
2019-01-10 13:02:12 +01:00
committed by Łukasz Paczos
parent 42144ce1dc
commit 5e58a0d81d
8 changed files with 377 additions and 48 deletions

View File

@@ -8,12 +8,11 @@ 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.MapStrictMode;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.log.Logger;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import com.mapbox.mapboxsdk.net.ConnectivityReceiver;
import com.mapbox.mapboxsdk.storage.FileSource;
@@ -135,24 +134,17 @@ 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) {
// Delete the file in a separate thread to avoid affecting the UI
new Thread(new Runnable() {
@Override
public void run() {
try {
String path = FileSource.getInternalCachePath(context) + File.separator + "mbgl-cache.db";
File file = new File(path);
if (file.exists()) {
file.delete();
Logger.d(TAG, String.format("Old ambient cache database deleted to save space: %s", path));
}
} catch (Exception exception) {
Logger.e(TAG, "Failed to delete old ambient cache database: ", exception);
MapStrictMode.strictModeViolation(exception);
}
}
}).start();
final String path = FileSource.getInternalCachePath(context) + File.separator + "mbgl-cache.db";
FileUtils.deleteFile(path);
}
/**

View File

@@ -1,26 +1,30 @@
package com.mapbox.mapboxsdk.storage;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
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.ThreadUtils;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.mapbox.mapboxsdk.log.Logger;
/**
* Holds a central reference to the core's DefaultFileSource for as long as
* there are active mapviews / offline managers
@@ -28,6 +32,8 @@ import com.mapbox.mapboxsdk.log.Logger;
public class FileSource {
private static final String TAG = "Mbgl-FileSource";
private static final String MAPBOX_SHARED_PREFERENCES = "MapboxSharedPreferences";
private static final String MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH = "fileSourceResourcesCachePath";
private static final Lock resourcesCachePathLoaderLock = new ReentrantLock();
private static final Lock internalCachePathLoaderLock = new ReentrantLock();
@Nullable
@@ -53,6 +59,29 @@ public class FileSource {
}
/**
* This callback receives an asynchronous response containing the new path of the
* resources cache database.
*/
@Keep
public interface SetResourcesCachePathCallback {
/**
* Receives the new database path
*
* @param path the path of the current resources cache database
*/
void onSuccess(String path);
/**
* Receives an error message if setting the path was not successful
*
* @param message the error message
*/
void onError(String message);
}
// File source instance is kept alive after initialization
private static FileSource INSTANCE;
@@ -79,17 +108,51 @@ public class FileSource {
*/
@NonNull
private static String getCachePath(@NonNull Context context) {
SharedPreferences preferences = context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE);
String cachePath = preferences.getString(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH, null);
if (!isPathWritable(cachePath)) {
// Use default path
cachePath = getDefaultCachePath(context);
// Reset stored cache path
SharedPreferences.Editor editor =
context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit();
editor.remove(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH).apply();
}
return cachePath;
}
/**
* Get the default resources cache path depending on the external storage configuration
*
* @param context the context to derive the files directory path from
* @return the default directory path
*/
@NonNull
private static String getDefaultCachePath(@NonNull Context context) {
if (isExternalStorageConfiguration(context) && isExternalStorageReadable()) {
File externalFilesDir = context.getExternalFilesDir(null);
if (externalFilesDir != null) {
return externalFilesDir.getAbsolutePath();
}
}
return context.getFilesDir().getAbsolutePath();
}
private static boolean isExternalStorageConfiguration(@NonNull Context context) {
// Default value
boolean isExternalStorageConfiguration = MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL;
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) {
@@ -99,24 +162,7 @@ public class FileSource {
Logger.e(TAG, "Failed to read the storage key: ", exception);
MapStrictMode.strictModeViolation(exception);
}
String cachePath = null;
if (isExternalStorageConfiguration && isExternalStorageReadable()) {
try {
// Try getting the external storage path
cachePath = context.getExternalFilesDir(null).getAbsolutePath();
} catch (NullPointerException exception) {
Logger.e(TAG, "Failed to obtain the external storage path: ", exception);
MapStrictMode.strictModeViolation(exception);
}
}
if (cachePath == null) {
// Default to internal storage
cachePath = context.getFilesDir().getAbsolutePath();
}
return cachePath;
return isExternalStorageConfiguration;
}
/**
@@ -136,8 +182,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;
}
@@ -166,9 +212,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()
};
}
@@ -217,6 +263,92 @@ public class FileSource {
}
}
/**
* Changes the path of the resources cache database.
* Note that the external storage setting needs to be activated in the manifest.
*
* @param context the context of the path
* @param path the new database path
* @param callback the callback to obtain the result
*/
public static void setResourcesCachePath(@NonNull Context context,
@NonNull final String path,
@NonNull final SetResourcesCachePathCallback callback) {
if (getInstance(context).isActivated()) {
final String activatedMessage = "Cannot set path, file source is activated!";
Logger.w(TAG, activatedMessage);
callback.onError(activatedMessage);
} 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 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);
}
});
}
}
}).start();
}
}
}
private static void setResourcesCachePath(@NonNull Context context, @NonNull String path) {
resourcesCachePathLoaderLock.lock();
resourcesCachePath = path;
reinitializeOfflineManager(context);
resourcesCachePathLoaderLock.unlock();
}
private static boolean isPathWritable(String path) {
if (path == null || path.isEmpty()) {
return false;
}
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();

View File

@@ -3,11 +3,15 @@ package com.mapbox.mapboxsdk.utils;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.log.Logger;
import java.io.File;
import java.lang.ref.WeakReference;
public class FileUtils {
private static final String TAG = "Mbgl-FileUtils";
/**
* Task checking whether app's process can read a file.
*/
@@ -121,4 +125,30 @@ public class FileUtils {
*/
void onError();
}
/**
* Deletes a file asynchronously in a separate thread.
*
* @param path the path of the file that should be deleted
*/
public static void deleteFile(@NonNull final String path) {
// Delete the file in a separate thread to avoid affecting the UI
new Thread(new Runnable() {
@Override
public void run() {
try {
File file = new File(path);
if (file.exists()) {
if (file.delete()) {
Logger.d(TAG, "File deleted to save space: " + path);
} else {
Logger.e(TAG, "Failed to delete file: " + path);
}
}
} catch (Exception exception) {
Logger.e(TAG, "Failed to delete file: ", exception);
}
}
}).start();
}
}