package de.blau.android.services;
import java.io.File;
import org.acra.ACRA;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.database.sqlite.SQLiteException;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import de.blau.android.R;
import de.blau.android.contract.Paths;
import de.blau.android.prefs.Preferences;
import de.blau.android.resources.TileLayerServer;
import de.blau.android.services.util.MapTile;
import de.blau.android.services.util.MapTileFilesystemProvider;
import de.blau.android.services.util.MapTileProviderDataBase;
import de.blau.android.util.Snack;
/**
* The OpenStreetMapTileProviderService can download map tiles from a server and
* stores them in a file system cache. <br/>
* This class was taken from OpenStreetMapViewer (original package
* org.andnav.osm) in 2010-06 by Marcus Wolschon to be integrated into the
* de.blau.androin OSMEditor.
*
* @author Marcus Wolschon <Marcus@Wolschon.biz>
* @author Manuel Stahl
*/
public class MapTileProviderService extends Service {
private static final String DEBUG_TAG = MapTileProviderService.class.getSimpleName();
private MapTileFilesystemProvider mFileSystemProvider;
private boolean mountPointWriteable = false;
@Override
public void onCreate() {
super.onCreate();
init();
}
@SuppressLint("NewApi")
/**
* Tries to put the tile cache on a removable sd card if present and we
* haven't already created the cache
*/
private void init() {
Preferences prefs = new Preferences(this);
int tileCacheSize = 100; // just in case we can't read the prefs
if (prefs != null) {
tileCacheSize = prefs.getTileCacheSize();
}
File mountPoint = null;
// check for classic location first
File classicMountPoint = Environment.getExternalStorageDirectory();
File classicTileDir = new File(classicMountPoint, Paths.DIRECTORY_PATH_TILE_CACHE_CLASSIC);
if (classicTileDir.exists()) {
// remove old database
MapTileProviderDataBase.delete(getBaseContext());
}
File[] storageDirectories = ContextCompat.getExternalFilesDirs(getBaseContext(), null);
for (File dir : storageDirectories) { // iterate over the directories preferring a removable one if possible
if (dir==null) {
Log.d(DEBUG_TAG,"storage dir null");
continue;
}
Log.d(DEBUG_TAG, "candidate storage directory " + dir.getPath());
if (MapTileProviderDataBase.exists(dir)) { // existing tile cache, use
mountPointWriteable = dir.canWrite();
mountPoint = dir;
if (mountPoint != null && mountPointWriteable) {
break;
}
} else if (dir.canWrite()) {
mountPointWriteable = true;
mountPoint = dir;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
if (Environment.isExternalStorageRemovable(dir)) {
// prefer removeable storage
Log.d(DEBUG_TAG, "isExternalStorageRemovable claims dir is removeable");
break;
}
} catch (IllegalArgumentException iae) {
// we've seen this on some devices even if it doesn0t make sense
Log.d(DEBUG_TAG, "isExternalStorageRemovable didn't like that");
}
}
} else {
Log.d(DEBUG_TAG, dir.getPath() + " not writeable");
}
}
if (mountPoint != null && mountPointWriteable) {
Log.d(DEBUG_TAG,
"Setting cache size to " + tileCacheSize + " on " + mountPoint.getPath());
try {
mFileSystemProvider = new MapTileFilesystemProvider(getBaseContext(), mountPoint,
tileCacheSize * 1024 * 1024); // FSCache
return;
} catch (SQLiteException slex) {
Log.d(DEBUG_TAG, "Opening DB hit " + slex);
ACRA.getErrorReporter().putCustomData("STATUS", "NOCRASH");
ACRA.getErrorReporter().handleException(slex);
}
} else {
Snack.toastTopError(this, R.string.toast_no_suitable_storage);
return;
}
Snack.toastTopError(this, getString(R.string.toast_storage_error, mountPoint));
// FIXME potentially we should set both background and overlay
// preferences to NONE here or simply zap what we are currently are
// using.
// don't terminate, simply ignore requests
}
@Override
public void onDestroy() {
if (mFileSystemProvider != null) {
mFileSystemProvider.destroy();
}
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* The IRemoteInterface is defined through IDL
*/
private final IMapTileProviderService.Stub mBinder = new IMapTileProviderService.Stub() {
// @Override
public String[] getTileProviders() throws RemoteException {
return TileLayerServer.getIds(null,false);
}
// @Override
public void getMapTile(String rendererID, int zoomLevel, int tileX, int tileY,
IMapTileProviderCallback callback) throws RemoteException {
if (!mountPointWriteable) { // fail silently
return;
}
MapTile tile = new MapTile(rendererID, zoomLevel, tileX, tileY);
mFileSystemProvider.loadMapTileAsync(tile, callback);
}
public void flushCache(String rendererId) {
mFileSystemProvider.flushCache(rendererId);
}
};
}