package com.mapbox.mapboxsdk.storage; import android.content.Context; 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.support.annotation.Keep; import android.support.annotation.NonNull; 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.utils.ThreadUtils; 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 */ public class FileSource { private static final String TAG = "Mbgl-FileSource"; private static final Lock resourcesCachePathLoaderLock = new ReentrantLock(); private static final Lock internalCachePathLoaderLock = new ReentrantLock(); private static String resourcesCachePath; private static String internalCachePath; /** * This callback allows implementors to transform URLs before they are requested * from the internet. This can be used add or remove custom parameters, or reroute * certain requests to other servers or endpoints. */ @Keep public interface ResourceTransformCallback { /** * Called whenever a URL needs to be transformed. * * @param kind the kind of URL to be transformed. * @param url the URL to be transformed * @return a URL that will now be downloaded. */ String onURL(@Resource.Kind int kind, String url); } // File source instance is kept alive after initialization private static FileSource INSTANCE; /** * Get the single instance of FileSource. * * @param context the context to derive the cache path from * @return the single instance of FileSource */ @UiThread public static synchronized FileSource getInstance(Context context) { if (INSTANCE == null) { INSTANCE = new FileSource(getResourcesCachePath(context), context.getResources().getAssets()); } return INSTANCE; } /** * Get files directory path for a context. * * @param context the context to derive the files directory path from * @return the files directory path * @deprecated Use {@link #getResourcesCachePath(Context)} instead. */ @Deprecated public static String getCachePath(Context context) { // Default value boolean setStorageExternal = 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); setStorageExternal = appInfo.metaData.getBoolean( MapboxConstants.KEY_META_DATA_SET_STORAGE_EXTERNAL, MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL); } catch (PackageManager.NameNotFoundException exception) { Logger.e(TAG, "Failed to read the package metadata: ", exception); MapStrictMode.strictModeViolation(exception); } catch (Exception exception) { Logger.e(TAG, "Failed to read the storage key: ", exception); MapStrictMode.strictModeViolation(exception); } String cachePath = null; if (setStorageExternal && 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; } /** * Checks if external storage is available to at least read. In order for this to work, make * sure you include <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> * (or WRITE_EXTERNAL_STORAGE) for API level < 18 in your app Manifest. *

* Code from https://developer.android.com/guide/topics/data/data-storage.html#filesExternal *

* * @return true if external storage is readable */ public static boolean isExternalStorageReadable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { return true; } 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)."); return false; } /** * Initializes file directories paths. * * @param context the context to derive paths from */ @UiThread public static void initializeFileDirsPaths(Context context) { ThreadUtils.checkThread("FileSource"); lockPathLoaders(); if (resourcesCachePath == null || internalCachePath == null) { new FileDirsPathsTask().execute(context); } } private static class FileDirsPathsTask extends AsyncTask { @Override protected void onCancelled() { unlockPathLoaders(); } @Override protected String[] doInBackground(Context... contexts) { return new String[] { getCachePath(contexts[0]), contexts[0].getCacheDir().getAbsolutePath() }; } @Override protected void onPostExecute(String[] paths) { resourcesCachePath = paths[0]; internalCachePath = paths[1]; unlockPathLoaders(); } } /** * Get files directory path for a context. * * @param context the context to derive the files directory path from * @return the files directory path */ public static String getResourcesCachePath(Context context) { resourcesCachePathLoaderLock.lock(); try { if (resourcesCachePath == null) { resourcesCachePath = getCachePath(context); } return resourcesCachePath; } finally { resourcesCachePathLoaderLock.unlock(); } } /** * Get internal cache path for a context. * * @param context the context to derive the internal cache path from * @return the internal cache path */ public static String getInternalCachePath(Context context) { internalCachePathLoaderLock.lock(); try { if (internalCachePath == null) { internalCachePath = context.getCacheDir().getAbsolutePath(); } return internalCachePath; } finally { internalCachePathLoaderLock.unlock(); } } private static void lockPathLoaders() { internalCachePathLoaderLock.lock(); resourcesCachePathLoaderLock.lock(); } private static void unlockPathLoaders() { resourcesCachePathLoaderLock.unlock(); internalCachePathLoaderLock.unlock(); } @Keep private long nativePtr; private FileSource(String cachePath, AssetManager assetManager) { initialize(Mapbox.getAccessToken(), cachePath, assetManager); } @Keep public native boolean isActivated(); @Keep public native void activate(); @Keep public native void deactivate(); @Keep public native void setAccessToken(@NonNull String accessToken); @Keep public native String getAccessToken(); @Keep public native void setApiBaseUrl(String baseUrl); /** * Sets a callback for transforming URLs requested from the internet *

* The callback will be executed on the main thread once for every requested URL. *

* * @param callback the callback to be invoked or null to reset */ @Keep public native void setResourceTransform(final ResourceTransformCallback callback); @Keep private native void initialize(String accessToken, String cachePath, AssetManager assetManager); @Override @Keep protected native void finalize() throws Throwable; }