// Created by plusminus on 21:46:22 - 25.09.2008
package de.blau.android.views.util;
import java.util.HashMap;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import de.blau.android.R;
import de.blau.android.exception.StorageException;
import de.blau.android.resources.TileLayerServer;
import de.blau.android.services.IMapTileProviderCallback;
import de.blau.android.services.IMapTileProviderService;
import de.blau.android.services.util.MapAsyncTileProvider;
import de.blau.android.services.util.MapTile;
/**
*
* <br/>
* This class was taken from OpenStreetMapViewer (original package org.andnav.osm) in 2010
* by Marcus Wolschon to be integrated into the de.blau.androin
* OSMEditor.
* @author Nicolas Gramlich
* @author Marcus Wolschon <Marcus@Wolschon.biz>
*
*/
public class MapTileProvider implements ServiceConnection,
MapViewConstants {
// ===========================================================
// Constants
// ===========================================================
/**
* Tag used in debug log-entries.
*/
private static final String DEBUG_TAG = MapTileProvider.class.getSimpleName();
// ===========================================================
// Fields
// ===========================================================
/**
* place holder if tile not available
*/
Object staticTilesLock = new Object();
private static Bitmap mLoadingMapTile;
private static Bitmap mNoTilesTile;
private Context mCtx;
/**
* cache provider
*/
private MapTileCache mTileCache;
private HashMap<String,Long> pending = new HashMap<String,Long>();
private IMapTileProviderService mTileService;
private Handler mDownloadFinishedHandler;
/**
* Set to true if we have less than 64 MB heap or have other caching issues
*/
private boolean smallHeap = false;
// ===========================================================
// Constructors
// ===========================================================
public MapTileProvider(final Context ctx,
final Handler aDownloadFinishedListener) {
mCtx = ctx;
Resources r = ctx.getResources();
synchronized(staticTilesLock) {
if (mNoTilesTile == null) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
mNoTilesTile = BitmapFactory.decodeResource(r,R.drawable.no_tiles, options);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
Log.d(DEBUG_TAG,"Notiles tile uses " + mNoTilesTile.getByteCount());
}
// mLoadingMapTile = BitmapFactory.decodeResource(r,R.drawable.no_tiles);
}
}
mTileCache = new MapTileCache();
smallHeap = Runtime.getRuntime().maxMemory() <= 32L*1024L*1024L; // less than 32MB
Intent explicitIntent = (new Intent(IMapTileProviderService.class.getName())).setPackage(ctx.getPackageName());
if(explicitIntent == null || !ctx.bindService(explicitIntent, this, Context.BIND_AUTO_CREATE)) {
Log.e(DEBUG_TAG, "Could not bind to " + IMapTileProviderService.class.getName() + " in package " + ctx.getPackageName());
}
mDownloadFinishedHandler = aDownloadFinishedListener;
}
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Methods from SuperClass/Interfaces
// ===========================================================
@Override
public void onServiceConnected(android.content.ComponentName name, android.os.IBinder service) {
mTileService = IMapTileProviderService.Stub.asInterface(service);
mDownloadFinishedHandler.sendEmptyMessage(MapTile.MAPTILE_SUCCESS_ID);
Log.d(DEBUG_TAG, "connected");
}
//@Override
@Override
public void onServiceDisconnected(ComponentName name) {
mTileService = null;
Log.d(DEBUG_TAG, "disconnected");
}
// ===========================================================
// Methods
// ===========================================================
/**
* Clear out memory related to tracking map tiles.
*/
public void clear() {
pending.clear();
mTileCache.clear();
mCtx.unbindService(this);
}
/**
* Try to reduce memory use.
*/
public void onLowMemory() {
mTileCache.onLowMemory();
}
/**
* Determine if the specified tile is available from local storage.
* @param aTile The tile to find.
* @return true if the tile is in local storage.
*/
public boolean isTileAvailable(final MapTile aTile) {
return mTileCache.containsTile(aTile);
}
/**
* Attempt to return a tile from cache otherwise ask for it from remote
*
* @param aTile tile spec
* @param owner
* @return the tile or null if it wasn't in cache
*/
@Nullable
public Bitmap getMapTile(@NonNull final MapTile aTile, long owner) {
Bitmap tile = mTileCache.getMapTile(aTile);
if (tile != null) {
// from cache
//if (DEBUGMODE)
// Log.i(DEBUGTAG, "MapTileCache succeeded for: " + aTile.toString());
return tile;
} else {
// from service
if (DEBUGMODE) {
Log.i(DEBUG_TAG, "Memory MapTileCache failed for: " + aTile.toString());
}
preCacheTile(aTile, owner);
}
return null;
}
/**
* Attempt to return a tile from cache otherwise ask for it from remote
*
* @param aTile tile spec
* @param owner
* @return the tile or null if it wasn't in cache
*/
public Bitmap getMapTileFromCache(final MapTile aTile, long owner) {
return mTileCache.getMapTile(aTile);
}
private void preCacheTile(final MapTile aTile, long owner) {
if (mTileService != null && !pending.containsKey(aTile.toId())) {
try {
pending.put(aTile.toId(), Long.valueOf(owner));
mTileService.getMapTile(aTile.rendererID, aTile.zoomLevel, aTile.x, aTile.y, mServiceCallback);
} catch (RemoteException e) {
Log.e(DEBUG_TAG, "RemoteException in preCacheTile()", e);
} catch (Exception e) {
Log.e(DEBUG_TAG, "Exception in preCacheTile()", e);
}
}
}
public void flushCache(String rendererId) {
try {
mTileService.flushCache(rendererId);
} catch (RemoteException e) {
Log.e(DEBUG_TAG, "RemoteException in flushCache()", e);
} catch (Exception e) {
Log.e(DEBUG_TAG, "Exception in flushCache()", e);
}
mTileCache.clear(); // zap everything in in memory cache
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
/**
* Callback for the {@link IOpenStreetMapTileProviderService} we are using.
*/
private IMapTileProviderCallback mServiceCallback = new IMapTileProviderCallback.Stub() {
//@Override
public void mapTileLoaded(final String rendererID, final int zoomLevel, final int tileX, final int tileY, final byte[] data) throws RemoteException {
BitmapFactory.Options options = new BitmapFactory.Options();
if (smallHeap) {
options.inPreferredConfig = Bitmap.Config.RGB_565;
} else {
options.inPreferredConfig = Bitmap.Config.ARGB_8888; // Bitmap.Config.RGB_565;
}
MapTile t = new MapTile(rendererID, zoomLevel, tileX, tileY);
try {
//long start = System.currentTimeMillis();
Bitmap tileBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
// long duration = System.currentTimeMillis() - start;
if (tileBitmap == null) {
Log.d(DEBUG_TAG, "decoded tile is null");
throw new RemoteException();
}
// Log.d(DEBUGTAG, "raw data size " + data.length + " decoded bitmap size " + aTile.getRowBytes()*aTile.getHeight());
String id = t.toId();
Long l = pending.get(t.toId());
if (l != null) {
mTileCache.putTile(t, tileBitmap,l);
pending.remove(id);
} // else wasn't in pending queue just ignore
mDownloadFinishedHandler.sendEmptyMessage(MapTile.MAPTILE_SUCCESS_ID);
// Log.d(DEBUGTAG, "Sending tile success message");
} catch (StorageException e) {
// unable to cache tile
if (!smallHeap) { // reduce tile size to half
smallHeap = true;
mTileCache.clear();
// should toast this
} else {
// FIXME this should show a toast ... or a special tile
}
} catch (NullPointerException npe) {
Log.d(DEBUG_TAG, "Exception in mapTileLoaded callback " + npe);
npe.printStackTrace();
throw new RemoteException();
}
if (DEBUGMODE)
Log.i(DEBUG_TAG, "MapTile download success."+t.toString());
}
//@Override
public void mapTileFailed(final String rendererID, final int zoomLevel, final int tileX, final int tileY, final int reason) throws RemoteException {
MapTile t = new MapTile(rendererID, zoomLevel, tileX, tileY);
if (reason == MapAsyncTileProvider.DOESNOTEXIST) {// only show error tile if we have no chance of getting the proper one
TileLayerServer osmts = TileLayerServer.get(mCtx, rendererID, false);
if (zoomLevel < Math.max(0,osmts.getMinZoomLevel()-1)) {
try {
mTileCache.putTile(t, mNoTilesTile, false, 0);
} catch (StorageException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
}
}
}
pending.remove(t.toString());
//if (DEBUGMODE) {
// Log.e(DEBUGTAG, "MapTile download error " + t.toString());
//}
// don't send when we fail mDownloadFinishedHandler.sendEmptyMessage(OpenStreetMapTile.MAPTILE_SUCCESS_ID);
}
};
public String getCacheUsageInfo() {
return mTileCache.getCacheUsageInfo();
}
}