package de.blau.android;
import java.util.Map;
import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.ScriptableObject;
import com.faendir.rhino_android.RhinoAndroidHelper;
import android.content.Context;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import de.blau.android.names.Names;
import de.blau.android.names.Names.NameAndTags;
import de.blau.android.net.UserAgentInterceptor;
import de.blau.android.osm.StorageDelegator;
import de.blau.android.prefs.Preferences;
import de.blau.android.presets.Preset;
import de.blau.android.presets.Preset.PresetItem;
import de.blau.android.tasks.TaskStorage;
import de.blau.android.util.NotificationCache;
import de.blau.android.util.collections.MultiHashMap;
import de.blau.android.util.rtree.RTree;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
@ReportsCrashes(
reportType = org.acra.sender.HttpSender.Type.JSON,
httpMethod = org.acra.sender.HttpSender.Method.PUT,
formUri = "http://acralyzer.vespucci.io/acraproxy",
mode = ReportingInteractionMode.DIALOG,
resDialogText = R.string.crash_dialog_text,
resDialogCommentPrompt = R.string.crash_dialog_comment_prompt)
public class App extends android.app.Application {
private static App currentInstance;
private static StorageDelegator delegator = new StorageDelegator();
private static TaskStorage taskStorage = new TaskStorage();
private static OkHttpClient httpClient;
private static final Object httpClientLock = new Object();
public static String userAgent;
/**
* The logic that manipulates the model. (non-UI)<br/>
*/
private static Logic logic;
/**
* The currently selected presets
*/
private static Preset[] currentPresets;
private static final Object currentPresetsLock = new Object();
private static MultiHashMap<String, PresetItem> presetSearchIndex = null;
private static final Object presetSearchIndexLock = new Object();
private static MultiHashMap<String, PresetItem> translatedPresetSearchIndex = null;
private static final Object translatedPresetSearchIndexLock = new Object();
/**
* name index related stuff
*/
private static Names names = null;
private static final Object namesLock = new Object();
private static Map<String,NameAndTags> namesSearchIndex = null;
private static final Object namesSearchIndexLock = new Object();
/**
* Geo index to on device photos
*/
private static RTree photoIndex;
/**
* Cache of recent notifications for tasks
*/
private static NotificationCache taskNotifications;
private static final Object taskNotificationsLock = new Object();
/**
* Cache of recent notifications for OSM data issues
*/
private static NotificationCache osmDataNotifications;
private static final Object osmDataNotificationsLock = new Object();
/**
* Rhino related objects
*/
private static RhinoAndroidHelper rhinoHelper;
private static org.mozilla.javascript.Scriptable rhinoScope;
private static final Object rhinoLock = new Object();
@Override
public void onCreate() {
// The following line triggers the initialization of ACRA
ACRA.init(this);
super.onCreate();
String appName = getString(R.string.app_name);
String appVersion = getString(R.string.app_version);
userAgent = appName + "/" + appVersion;
currentInstance = this;
}
public static StorageDelegator getDelegator() {
return delegator;
}
public static TaskStorage getTaskStorage() {
return taskStorage;
}
public static App getCurrentInstance() {
return currentInstance;
}
public static Resources resources() {
return currentInstance.getResources();
}
@NonNull
public static OkHttpClient getHttpClient() {
synchronized(httpClientLock) {
if (httpClient == null) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addNetworkInterceptor(new UserAgentInterceptor(userAgent));
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
builder.addNetworkInterceptor(httpLoggingInterceptor);
}
httpClient = builder.build();
}
return httpClient;
}
}
public static Preset[] getCurrentPresets(Context ctx) {
synchronized (currentPresetsLock) {
if (currentPresets == null) {
Preferences prefs = new Preferences(ctx);
currentPresets = prefs.getPreset();
}
return currentPresets;
}
}
/**
* Resets the current presets, causing them to be re-parsed
*/
public static void resetPresets() {
synchronized (currentPresetsLock) {
currentPresets = null;
presetSearchIndex = null;
translatedPresetSearchIndex = null;
}
}
public static MultiHashMap<String, PresetItem> getPresetSearchIndex(Context ctx) {
synchronized (presetSearchIndexLock) {
if (presetSearchIndex == null) {
presetSearchIndex = Preset.getSearchIndex(getCurrentPresets(ctx));
}
return presetSearchIndex;
}
}
public static MultiHashMap<String, PresetItem> getTranslatedPresetSearchIndex(Context ctx) {
synchronized (translatedPresetSearchIndexLock) {
if (translatedPresetSearchIndex == null) {
translatedPresetSearchIndex = Preset.getTranslatedSearchIndex(getCurrentPresets(ctx));
}
return translatedPresetSearchIndex;
}
}
public static Map<String,NameAndTags> getNameSearchIndex(Context ctx) {
getNames(ctx);
synchronized (namesSearchIndexLock) {
if (namesSearchIndex == null) {
// names.dump2Log();
namesSearchIndex = names.getSearchIndex();
}
return namesSearchIndex;
}
}
public static Names getNames(Context ctx) {
synchronized (namesLock) {
if (names == null) {
// this should be done async if it takes too long
names = new Names(ctx);
}
return names;
}
}
/**
* Returns the current in-memory index, use resetPhotoIndex to initialize/reset
* @return
*/
public static RTree getPhotoIndex() {
return photoIndex;
}
public static void resetPhotoIndex() {
photoIndex = new RTree(20,50);
}
/**
* @return the logic
*/
public static Logic getLogic() {
return logic;
}
/**
* Allocate new logic, logic contains some state and should only exist once
* @return a new instance of the Logic class
*/
public synchronized static Logic newLogic() {
if (logic==null) {
logic = new Logic();
}
return logic;
}
/**
* Return the cache for task notifications, allocate if necessary
* @return the notification cache
*/
public static NotificationCache getTaskNotifications(Context ctx) {
synchronized (taskNotificationsLock) {
if (taskNotifications == null) {
taskNotifications = new NotificationCache(ctx);
}
return taskNotifications;
}
}
/**
* If the cache is empty replace it
* @param cache
*/
public static void setTaskNotifications(Context ctx, NotificationCache cache) {
synchronized (taskNotificationsLock) {
if (taskNotifications == null || taskNotifications.isEmpty()) {
taskNotifications = cache;
taskNotifications.trim(ctx);
}
}
}
/**
* Return the cache for osm data notifications, allocate if necessary
* @return
*/
public static NotificationCache getOsmDataNotifications(Context ctx) {
synchronized (osmDataNotificationsLock) {
if (osmDataNotifications == null) {
osmDataNotifications = new NotificationCache(ctx);
}
return osmDataNotifications;
}
}
/**
* If the cache is empty replace it
* @param cache
*/
public static void setOsmDataNotifications(Context ctx,NotificationCache cache) {
synchronized (osmDataNotificationsLock) {
if (osmDataNotifications == null || osmDataNotifications.isEmpty()) {
osmDataNotifications = cache;
osmDataNotifications.trim(ctx);
}
}
}
/**
* Return a rhino helper for scripting
* @param ctx android context
* @return a RhinoAndroidHelper
*/
public static RhinoAndroidHelper getRhinoHelper(Context ctx) {
synchronized (rhinoLock) {
if (rhinoHelper == null) {
rhinoHelper = new RhinoAndroidHelper(ctx);
}
return rhinoHelper;
}
}
/**
* Return a sandboxed rhino scope for scripting
*
* Allows access to the java package but not to the app internals
* FIXME not clear if we can use the same scope the whole time
* @param ctx android context
* @return rhino scope
*/
public static org.mozilla.javascript.Scriptable getRestrictedRhinoScope(Context ctx) {
synchronized (rhinoLock) {
if (rhinoScope == null) {
org.mozilla.javascript.Context c = rhinoHelper.enterContext();
try {
// this is a fairly hackish way of sandboxing, but it does work
// rhinoScope = c.initStandardObjects(); // don't seal the individual objects
rhinoScope = new ImporterTopLevel(c);
c.evaluateString(rhinoScope , "java", "lazyLoad", 0, null);
c.evaluateString(rhinoScope , "importClass(Packages.de.blau.android.osm.BoundingBox)", "lazyLoad", 0, null);
c.evaluateString(rhinoScope , "importClass(Packages.de.blau.android.util.GeoMath)", "lazyLoad", 0, null);
((ScriptableObject)rhinoScope).sealObject();
} finally {
org.mozilla.javascript.Context.exit();
}
}
return rhinoScope;
}
}
}