diff options
author | Arne Kaiser <onkelarne@gmail.com> | 2019-01-10 13:02:12 +0100 |
---|---|---|
committer | Łukasz Paczos <lukasz.paczos@mapbox.com> | 2019-03-13 18:28:04 +0100 |
commit | 5e58a0d81d702a543e898b489ead044a630229a0 (patch) | |
tree | 749088390349d67b74c4ed17ec3db7b11ca95e1e | |
parent | 42144ce1dc4cadef66f43a896572aee69739cd4f (diff) | |
download | qtlocation-mapboxgl-5e58a0d81d702a543e898b489ead044a630229a0.tar.gz |
[android] Feature: Change path of the resources cache db
8 files changed, 377 insertions, 48 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java index 6731efd4b8..0d85be18a5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java @@ -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); } /** diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java index d3dba6f90c..8df527657c 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java @@ -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(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/FileUtils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/FileUtils.java index 52009d20ef..500e784602 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/FileUtils.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/FileUtils.java @@ -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(); + } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index c8986d6775..28e284abb3 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -317,6 +317,17 @@ android:value=".activity.FeatureOverviewActivity" /> </activity> <activity + android:name=".activity.offline.ChangeResourcesCachePathActivity" + android:description="@string/description_change_resources_cache_path" + android:label="@string/activity_change_resources_cache_path"> + <meta-data + android:name="@string/category" + android:value="@string/category_offline" /> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".activity.FeatureOverviewActivity" /> + </activity> + <activity android:name=".activity.imagegenerator.SnapshotActivity" android:description="@string/description_snapshot" android:label="@string/activity_snapshot"> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/ChangeResourcesCachePathActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/ChangeResourcesCachePathActivity.kt new file mode 100644 index 0000000000..2c429e829d --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/ChangeResourcesCachePathActivity.kt @@ -0,0 +1,141 @@ +package com.mapbox.mapboxsdk.testapp.activity.offline + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.BaseAdapter +import android.widget.TextView +import android.widget.Toast +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 { + + lateinit var adapter: PathAdapter + + 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() + } + + override fun onStart() { + super.onStart() + val path: String? = FileSource.getResourcesCachePath(this) + Toast.makeText(this, "Current path: $path", Toast.LENGTH_LONG).show() + } + + override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + listView.onItemClickListener = null + val path: String = adapter.getItem(position) as String + FileSource.setResourcesCachePath(this, path, this) + } + + override fun onError(message: String?) { + listView.onItemClickListener = this + Toast.makeText(this, "Error: $message", Toast.LENGTH_LONG).show() + } + + override fun onSuccess(path: String?) { + listView.onItemClickListener = this + Toast.makeText(this, "New path: $path", Toast.LENGTH_LONG).show() + } + + private fun obtainFilesPaths(context: Context): List<String> { + val paths = ArrayList<String>() + paths.add(context.filesDir.absolutePath) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + paths.addAll(obtainExternalFilesPathsKitKat(context)) + } else { + paths.addAll(obtainExternalFilesPathsLegacy(context)) + } + paths.add("${File.separator}invalid${File.separator}cache${File.separator}path") + return paths + } + + private fun obtainExternalFilesPathsLegacy(context: Context): List<String> { + val postFix = + "${File.separator}Android${File.separator}data${File.separator}${context.packageName}${File.separator}files" + val paths = ArrayList<String>() + val externalStorage = System.getenv("EXTERNAL_STORAGE") + val secondaryStorage = System.getenv("SECONDARY_STORAGE") + if (externalStorage != null) { + paths.add(externalStorage + postFix) + } + if (secondaryStorage != null) { + val secPaths = secondaryStorage.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + for (path in secPaths) { + paths.add(path + postFix) + } + } + return paths + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private fun obtainExternalFilesPathsKitKat(context: Context): List<String> { + val paths = ArrayList<String>() + val extDirs = context.getExternalFilesDirs(null) + for (dir in extDirs) { + if (dir != null) { + paths.add(dir.absolutePath) + } + } + return paths + } + + class PathAdapter(private val context: Context, private val paths: List<String>) : BaseAdapter() { + + override fun getItem(position: Int): Any { + return paths[position] + } + + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + + override fun getCount(): Int { + return paths.size + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + val viewHolder: ViewHolder + val view: View + + if (convertView == null) { + viewHolder = ViewHolder() + view = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, parent, false) + viewHolder.textView = view.findViewById(android.R.id.text1) + view?.tag = viewHolder + } else { + view = convertView + viewHolder = view.tag as ViewHolder + } + + viewHolder.textView?.text = paths[position] + + return view + } + + class ViewHolder { + var textView: TextView? = null + } + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_change_resources_cache_path.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_change_resources_cache_path.xml new file mode 100644 index 0000000000..1eb999caf5 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_change_resources_cache_path.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <ListView + android:id="@+id/listView" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + <TextView + android:id="@android:id/empty" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:text="@string/no_results" + android:textSize="24sp"/> + +</LinearLayout> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index 40698eae78..21ebeaabd5 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -20,6 +20,7 @@ <string name="description_offline">Offline Map example</string> <string name="description_update_metadata">Update metadata example</string> <string name="description_offline_region_delete">Delete region example</string> + <string name="description_change_resources_cache_path">Change resources cache path example</string> <string name="description_animated_symbollayer">Animate the position change of a symbol layer</string> <string name="description_polyline">Add a polyline to a map</string> <string name="description_polygon">Add a polygon to a map</string> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index 290a157dd1..26f56f29b1 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -27,6 +27,7 @@ <string name="activity_offline">Offline Map</string> <string name="activity_update_metadata">Update metadata Map</string> <string name="activity_offline_region_delete">Delete region</string> + <string name="activity_change_resources_cache_path">Change resources cache path</string> <string name="activity_minmax_zoom">Min/Max Zoom</string> <string name="activity_viewpager">ViewPager</string> <string name="activity_runtime_style">Runtime Style</string> |