Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[android] when changing the resource path, recreate database instead …
Browse files Browse the repository at this point in the history
…of the whole FileSource
  • Loading branch information
LukasPaczos committed Mar 13, 2019
1 parent 3f7b70f commit b770299
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 107 deletions.
Expand Up @@ -8,7 +8,6 @@
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;
Expand Down Expand Up @@ -134,14 +133,6 @@ private OfflineManager(Context context) {
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);
Expand Down Expand Up @@ -461,17 +452,17 @@ private native void createOfflineRegion(FileSource fileSource, OfflineRegionDefi
* 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
Expand Down
Expand Up @@ -7,17 +7,16 @@
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;
Expand Down Expand Up @@ -64,7 +63,7 @@ public interface ResourceTransformCallback {
* resources cache database.
*/
@Keep
public interface SetResourcesCachePathCallback {
public interface ResourcesCachePathChangeCallback {

/**
* Receives the new database path
Expand Down Expand Up @@ -117,7 +116,7 @@ private static String getCachePath(@NonNull Context context) {

// 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();
}

Expand Down Expand Up @@ -148,11 +147,11 @@ private static boolean isExternalStorageConfiguration(@NonNull Context context)
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) {
Expand Down Expand Up @@ -182,8 +181,8 @@ public static boolean isExternalStorageReadable() {
}

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;
}
Expand Down Expand Up @@ -212,9 +211,9 @@ protected void onCancelled() {
@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()
};
}

Expand Down Expand Up @@ -273,67 +272,66 @@ public static String getInternalCachePath(@NonNull Context context) {
*/
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 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);
}
});
}
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();

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) {
Expand All @@ -343,12 +341,6 @@ private static boolean isPathWritable(String path) {
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();
Expand Down Expand Up @@ -396,6 +388,9 @@ private FileSource(String cachePath, AssetManager assetManager) {
@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);

Expand Down
Expand Up @@ -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() {
Expand All @@ -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")
Expand All @@ -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)
Expand All @@ -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
}
Expand Down

0 comments on commit b770299

Please sign in to comment.