package com.androidol.util.tiles;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import com.androidol.R;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.androidol.constants.UtilConstants;
import com.androidol.events.Event;
import com.androidol.events.TileEvents;
import com.androidol.exceptions.TileFileCorruptedException;
//import com.androidol.events.Event;
//import com.androidol.events.LayerEvents;
import com.androidol.layer.Layer;
import com.androidol.util.Util;
import com.androidol.test.TestActivity;
import com.androidol.tile.Tile;
public class TileProvider implements UtilConstants {
// ===========================================================
// fields
// ===========================================================
protected Bitmap loadingTile; // static bitmap tile for loading
protected Bitmap transparentTile; // static transparent bitmap tile
public Bitmap missingTile; // static error bitmap tile
protected Context context;
protected Layer layer;
protected TileCache tileCache;
//protected TileHttpLoader httpLoader;
protected TileHttpFutureLoader httpLoader;
//protected TileFileSystemLoader fileSystemLoader;
//protected TileFileSystemFutureLoader fileSystemLoader;
protected TileSDCardFutureLoader fileSystemLoader;
//protected TileZippedSDCardFutureLoader fileSystemLoader;
public TileEvents events = new TileEvents();
protected ConcurrentHashMap<String, ArrayList<Future<?>>> fsLoaderThreadQueue = new ConcurrentHashMap<String, ArrayList<Future<?>>>();
protected ConcurrentHashMap<String, ArrayList<Future<?>>> httpLoaderThreadQueue = new ConcurrentHashMap<String, ArrayList<Future<?>>>();
protected boolean isOfflineMode = false;
/**
* Constructor TileProvider
*
* @param context
* @param mapViewUpdateListener
*/
public TileProvider(final Context context) {
this.loadingTile = BitmapFactory.decodeResource(context.getResources(), R.drawable.loading);
this.transparentTile = BitmapFactory.decodeResource(context.getResources(), R.drawable.transparent);
this.missingTile = BitmapFactory.decodeResource(context.getResources(), R.drawable.missing);
this.context = context;
this.tileCache = new TileCache();
//this.fileSystemLoader = new TileFileSystemLoader(context, DISK_CACHE_SIZE, this.tileCache, this.events);
//this.fileSystemLoader = new TileFileSystemFutureLoader(context, DISK_CACHE_SIZE, this.tileCache, this.events);
this.fileSystemLoader = new TileSDCardFutureLoader(context, DISK_CACHE_SIZE, this.tileCache, this.events);
//this.fileSystemLoader = new TileZippedSDCardFutureLoader(context, DISK_CACHE_SIZE, this.tileCache, this.events);
// TODO: if it is TileZippedSDCardFutureLoader, always set isOfflineMode to true
//this.httpLoader = new TileHttpLoader(context, this.fileSystemLoader, this.events);
this.httpLoader = new TileHttpFutureLoader(context, this.fileSystemLoader, this.events);
//this.pendingQueue = new LinkedList<String>();
//this.pendingMatrix = new LinkedHashMap<Layer, LinkedHashMap<String, String>>();
}
/**
* getTile() for getting tiles in synchronize mode
*
* @param url
* @param loadingTile
* @param tile
* @return
*/
/*
public Bitmap getTile(final String url, Bitmap loadingTile, Tile tile) {
// return static tile image
if(url.equalsIgnoreCase("R.drawable.transparent") == true) {
return this.transparentTile;
} else if(url.equalsIgnoreCase("R.drawable.missing") == true) {
return this.missingTile;
}
Bitmap bitmap = null;
try {
bitmap = this.fileSystemLoader.loadTileToMemorySync(url);
} catch(Exception e) {
//Util.printDebugMessage("...tile " + url + " not found on disk...try loading from http....");
//Util.printErrorMessage("...error(" + e.getClass().getSimpleName() + ") loading tile from disk...", e);
this.events.triggerEvent(TileEvents.FS_LOAD_FAILURE, new Event(TileEvents.FS_LOAD_FAILURE, null));
bitmap = loadingTile;
this.httpLoader.getTileAsync(url, tile);
}
return bitmap;
}
*/
/**
* API Method: getTile
*
* @param url
* @return
*/
public Bitmap getTile(final String url, Tile tile) {
return getTile(url, this.loadingTile, tile);
}
/**
* getTile
*
* @param url
* @return
*/
public Bitmap getTile(final String url, final String signature, Tile tile) {
return getTile(url, signature, this.loadingTile, tile);
}
/**
* getTile() for getting tiles in asynchronize mode
*
* @param url
* @param loadingTile
* @return bitmap
*/
public Bitmap getTile(final String url, Bitmap loadingTile, Tile tile) {
return getTile(url, null, this.loadingTile, tile);
}
/**
*
* @param url
* @param signature
* @param loadingTile
* @param tile
* @return
*/
public Bitmap getTile(final String url, final String signature, Bitmap loadingTile, Tile tile) {
// return static tile image
if(url.equalsIgnoreCase("R.drawable.transparent") == true) {
return this.transparentTile;
} else if(url.equalsIgnoreCase("R.drawable.missing") == true) {
return this.missingTile;
}
Bitmap bitmap = this.tileCache.getTile(url);
if(bitmap != null && bitmap.isRecycled()==false){
//Util.printDebugMessage("...tile found in cache: " + url + "...");
//this.events.triggerEvent(TileEvents.MEM_LOAD_SUCCESS, new Event(TileEvents.MEM_LOAD_SUCCESS, url));
} else{
//Util.printDebugMessage("...tile " + url + " not found in cache...try loading from disk....");
//this.events.triggerEvent(TileEvents.MEM_LOAD_FAILURE, new Event(TileEvents.MEM_LOAD_FAILURE, url));
try {
// TileFileSystemFutureLoader
this.fileSystemLoader.loadTileToMemoryAsync(url, signature, tile);
// TileFileSystemLoader
//this.fileSystemLoader.loadTileToMemoryAsync(url, tile);
// apply thread canceling for FS loading
// there seems no need to to thread canceling for FS loading cuz it's too fast
// uncomment out this to enable FS loading thread canceling
/*
Future<?> future = this.fileSystemLoader.loadTileToMemoryAsync(url, signature, tile);
if(future != null) {
if(signature!=null && "".equalsIgnoreCase(signature)==false) {
if(this.fsLoaderThreadQueue.get(signature) == null) {
ArrayList<Future<?>> futures = new ArrayList<Future<?>>();
this.fsLoaderThreadQueue.put(signature, futures);
}
this.fsLoaderThreadQueue.get(signature).add(future);
}
}
*/
bitmap = loadingTile;
} catch(FileNotFoundException e) {
if(this.isOfflineMode == false) {
//Util.printDebugMessage("...tile " + url + " not found on disk...try loading from http....");
//this.events.triggerEvent(TileEvents.FS_LOAD_FAILURE, new Event(TileEvents.FS_LOAD_FAILURE, null));
bitmap = loadingTile;
Future<?> future = this.httpLoader.getTileAsync(url, signature, tile);
if(future != null) {
if(signature!=null && "".equalsIgnoreCase(signature)==false) {
if(this.httpLoaderThreadQueue.get(signature) == null) {
ArrayList<Future<?>> futures = new ArrayList<Future<?>>();
this.httpLoaderThreadQueue.put(signature, futures);
}
this.httpLoaderThreadQueue.get(signature).add(future);
}
}
} else {
// offline mode, no need to load tile from internet just return missing tile
if((this.layer.isBaseLayer()==true) && (this.layer.isVisible()==true)) {
bitmap = missingTile;
} else {
bitmap = transparentTile;
}
}
} catch(TileFileCorruptedException e) {
if(this.isOfflineMode == false) {
Util.printDebugMessage("...tile " + url + " corrupted on disk...try loading from http....");
//this.events.triggerEvent(TileEvents.FS_LOAD_FAILURE, new Event(TileEvents.FS_LOAD_FAILURE, null));
bitmap = loadingTile;
Future<?> future = this.httpLoader.getTileAsync(url, signature, tile);
if(future != null) {
if(signature!=null && "".equalsIgnoreCase(signature)==false) {
if(this.httpLoaderThreadQueue.get(signature) == null) {
ArrayList<Future<?>> futures = new ArrayList<Future<?>>();
this.httpLoaderThreadQueue.put(signature, futures);
}
this.httpLoaderThreadQueue.get(signature).add(future);
}
}
} else {
// offline mode, no need to load tile from internet just return missing tile
// offline mode, no need to load tile from internet just return missing tile
if((this.layer.isBaseLayer()==true) && (this.layer.isVisible()==true)) {
bitmap = missingTile;
} else {
bitmap = transparentTile;
}
}
} catch(Exception e) {
Util.printDebugMessage("...tile can not be loaded for other reason...use transparent tile...");
if((this.layer.isBaseLayer()==true) && (this.layer.isVisible()==true)) {
bitmap = missingTile;
} else {
bitmap = transparentTile;
}
Util.printErrorMessage(e.getMessage());
}
}
return bitmap;
}
/**
* API Method: getLoadingTile
*/
public Bitmap getLoadingTile() {
return this.loadingTile;
}
/**
* API Method: getTransparentTile
*/
public Bitmap getTransparentTile() {
return this.transparentTile;
}
/**
* API Method: setLoadingTile
*/
public void setLoadingTile(Bitmap newLoadingTile) {
if(newLoadingTile != null) {
this.loadingTile = newLoadingTile;
}
}
/**
* API Method: setMissingTile
*/
public void setMissingTile(Bitmap newMissingTile) {
if(newMissingTile != null) {
this.missingTile = newMissingTile;
}
}
/**
* API Method: setTransparentTile
*/
public void setTransparentTile(Bitmap newTransparentTile) {
if(newTransparentTile != null) {
this.transparentTile = newTransparentTile;
}
}
public void cleanupFSLoaderThreadQueue() {
cleanupThreadQueue(null, this.fsLoaderThreadQueue);
}
/**
*
*/
public void cleanupFSLoaderThreadQueue(String signature) {
// uncomment out this to enable FS loading thread canceling
//cleanupThreadQueue(signature, this.fsLoaderThreadQueue);
}
/**
*
*/
public void cleanupHTTPLoaderThreadQueue(String signature) {
cleanupThreadQueue(signature, this.httpLoaderThreadQueue);
}
/**
* cleanupThreadQueue(String signature, ConcurrentHashMap<String, ArrayList<Future<?>>> queue)
*/
private void cleanupThreadQueue(String signature, ConcurrentHashMap<String, ArrayList<Future<?>>> queue) {
Iterator<String> iterator = queue.keySet().iterator();
while(iterator.hasNext()) {
String key = (String)iterator.next();
if(key!=null && key.equalsIgnoreCase(signature)==false) {
ArrayList<Future<?>> threads = queue.get(key);
if(threads != null) {
for(int i=0; i<threads.size(); i++) {
if(threads.get(i) != null) {
if(threads.get(i).isDone() == false) {
//Util.printDebugMessage("...cancel thread with signature: " + key);
if(threads.get(i).cancel(true)) {
// TODO: when canceled, must remove the url from pendingQueue
//Util.printDebugMessage("...successfully canceled thread with signature: " + key);
} else {
//Util.printDebugMessage("...fail to cancel thread with signature: " + key);
}
} else {
//Util.printDebugMessage("...thread is finished with signature: " + key);
}
}
}
}
iterator.remove();
}
}
}
/**
* API Method: addToPendingQueue
* @param url
*/
/*
public synchronized void addToPendingQueue(String url) {
//Util.printDebugMessage(" ...try to add tile " + url + "...");
if(this.pendingQueue.contains(url) == false) {
//Util.printDebugMessage(" ...add tile " + url + "...");
this.pendingQueue.add(url);
}
}
*/
/**
* API Method: addToPendingMatrix
* @param url
*/
/*
public synchronized void addToPendingMatrix(Tile tile) {
if(this.pendingMatrix.containsKey(tile.getLayer()) == false) {
this.pendingMatrix.put(tile.getLayer(), new LinkedHashMap<String, String>());
// TODO: this does not indicate if loading of a layer is just started, find something else
}
if(this.pendingMatrix.get(tile.getLayer()).containsKey(tile.getUrl()) == false) {
this.pendingMatrix.get(tile.getLayer()).put(tile.getUrl(), tile.getUrl());
}
}
*/
/**
* API Method: removeFromPendingQueue
* @param url
*/
/*
public synchronized void removeFromPendingQueue(String url) {
//Util.printDebugMessage(" ...try to remove tile " + url + "...");
if(this.pendingQueue.contains(url) == true) {
this.pendingQueue.remove(url);
//Util.printDebugMessage(" ...tile " + url + " removed...");
}
}
*/
/**
* API Method: removeFromPendingMatrix
* @param tile
*/
/*
public synchronized void removeFromPendingMatrix(Tile tile) {
if(this.pendingMatrix.containsKey(tile.getLayer()) == true) {
if(this.pendingMatrix.get(tile.getLayer()).containsKey(tile.getUrl()) == true) {
this.pendingMatrix.get(tile.getLayer()).remove(tile.getUrl());
//tile.getLayer().events.triggerEvent(LayerEvents.TILE_LOADED, new Event());
}
if(this.pendingMatrix.get(tile.getLayer()).size() == 0) {
this.pendingMatrix.remove(tile.getLayer());
// TODO: this does not indicate if loading of a layer is finished, find something else
}
}
}
*/
/**
* API Method: isStillLoading
*
* @return
* check whether tile provider is still loading some time from disk or Internet
*/
public boolean isStillLoading() {
if(this.httpLoader.pendingQueue.size() == 0 && this.fileSystemLoader.pendingQueue.size() == 0 ) {
return false;
}
return true;
}
public boolean isFSStillLoading() {
if(this.fileSystemLoader.pendingQueue.size() == 0 ) {
return false;
}
return true;
}
public boolean isHTTPStillLoading() {
if(this.httpLoader.pendingQueue.size() == 0 ) {
return false;
}
return true;
}
// ==================================================================================================
// getters & setters
// ==================================================================================================
public boolean isOfflineMode() {
return isOfflineMode;
}
public void setOfflineMode(boolean isOfflineMode) {
this.isOfflineMode = isOfflineMode;
}
public Layer getLayer() {
return layer;
}
public void setLayer(Layer layer) {
this.layer = layer;
// pass layer name or id to TileProvider as root output folder on SD card
if(this.layer.getName()!=null && "".equalsIgnoreCase(this.layer.getName())==false) {
this.fileSystemLoader.setRootOutputPath(this.layer.getName().replace(' ', '_'));
}
}
// ==================================================================================================
// private class
// ==================================================================================================
/*
private class TileLoaderCallable implements Callable<Bitmap> {
protected String url = "";
protected String signature = "";
protected Tile tile = null;
public TileLoaderCallable(String url, String signature, Tile tile) {
super();
this.url = url;
this.signature = signature;
this.tile = tile;
}
@Override
public Bitmap call() throws TileFileCorruptedException {
//Util.printDebugMessage("@...started a TileLoaderCallable thread...url: " + this.url);
Bitmap bitmap = null;
try {
//Thread.sleep(1000);
Future<Bitmap> future = TileProvider.this.fileSystemLoader.loadTileToMemoryAsync2(url, tile);
if(TileProvider.this.fsLoaderThreadQueue.get(this.signature) == null) {
ArrayList<Future<Bitmap>> futures = new ArrayList<Future<Bitmap>>();
TileProvider.this.fsLoaderThreadQueue.put(this.signature, futures);
}
TileProvider.this.fsLoaderThreadQueue.get(this.signature).add(future);
bitmap = future.get();
} catch(TileFileCorruptedException e) {
throw e;
} catch(ExecutionException e) {
throw new TileFileCorruptedException("");
} catch(InterruptedException e) {
throw new TileFileCorruptedException("");
}
if(bitmap!=null && bitmap.isRecycled()==false) {
return bitmap;
} else {
throw new TileFileCorruptedException("");
}
//Util.printDebugMessage("@...finished a TileLoaderCallable thread...url: " + this.url);
}
}
*/
}