diff --git a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/IconsCache.java b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/IconsCache.java index 70c72db234..a9dac3f6ea 100644 --- a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/IconsCache.java +++ b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/IconsCache.java @@ -2,16 +2,15 @@ package net.osmand.core.samples.android.sample1; import android.graphics.drawable.Drawable; -import net.osmand.core.jni.OsmAndCore; -import net.osmand.core.jni.SWIGTYPE_p_QByteArray; -import net.osmand.core.jni.SwigUtilities; +import net.osmand.core.samples.android.sample1.core.CoreResourcesFromAndroidAssetsCustom; public class IconsCache { + private CoreResourcesFromAndroidAssetsCustom assets; private float displayDensityFactor; - public IconsCache(float displayDensityFactor) { - this.displayDensityFactor = displayDensityFactor; + public IconsCache(CoreResourcesFromAndroidAssetsCustom assets) { + this.assets = assets; } public float getDisplayDensityFactor() { @@ -23,12 +22,6 @@ public class IconsCache { } public Drawable getIcon(String name) { - /* - SWIGTYPE_p_QByteArray byteArray = - OsmAndCore.getCoreResourcesProvider().getResource(name, displayDensityFactor); - String s = SwigUtilities.getDataFromQByteArray(byteArray); - */ - - return null; + return assets.getIcon("map/icons/" + name + ".png", displayDensityFactor); } } diff --git a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/MainActivity.java b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/MainActivity.java index 7e0bad0fc9..01ab827121 100644 --- a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/MainActivity.java +++ b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/MainActivity.java @@ -6,7 +6,6 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.graphics.PointF; import android.os.Bundle; -import android.os.Environment; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorListener; import android.text.Editable; @@ -24,7 +23,6 @@ import android.widget.ListView; import android.widget.TextView; import net.osmand.core.android.AtlasMapRendererView; -import net.osmand.core.android.CoreResourcesFromAndroidAssets; import net.osmand.core.android.NativeCore; import net.osmand.core.jni.AreaI; import net.osmand.core.jni.IMapLayerProvider; @@ -47,6 +45,7 @@ import net.osmand.core.jni.Utilities; import net.osmand.core.samples.android.sample1.MultiTouchSupport.MultiTouchZoomListener; import net.osmand.core.samples.android.sample1.adapters.SearchListAdapter; import net.osmand.core.samples.android.sample1.adapters.SearchListItem; +import net.osmand.core.samples.android.sample1.core.CoreResourcesFromAndroidAssetsCustom; import net.osmand.core.samples.android.sample1.search.SearchAPI; import net.osmand.core.samples.android.sample1.search.SearchAPI.SearchAPICallback; import net.osmand.core.samples.android.sample1.search.SearchItem; @@ -111,18 +110,12 @@ public class MainActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + SampleApplication app = getSampleApplication(); + gestureDetector = new GestureDetector(this, new MapViewOnGestureListener()); multiTouchSupport = new MultiTouchSupport(this, new MapViewMultiTouchZoomListener()); - long startTime = System.currentTimeMillis(); - - // Initialize native core prior (if needed) - if (NativeCore.isAvailable() && !NativeCore.isLoaded()) - NativeCore.load(CoreResourcesFromAndroidAssets.loadFromCurrentApplication(this)); - - Logger.get().setSeverityLevelThreshold(LogSeverityLevel.Debug); - - // Inflate views + // Inflate views setContentView(R.layout.activity_main); // Get map view @@ -153,8 +146,7 @@ public class MainActivity extends Activity { }); // Additional log sink - fileLogSink = QIODeviceLogSink.createFileLogSink( - Environment.getExternalStorageDirectory() + "/osmand/osmandcore.log"); + fileLogSink = QIODeviceLogSink.createFileLogSink(app.getAbsoluteAppPath() + "/osmandcore.log"); Logger.get().addLogSink(fileLogSink); // Get device display density factor @@ -178,8 +170,8 @@ public class MainActivity extends Activity { Log.i(TAG, "Going to prepare OBFs collection"); obfsCollection = new ObfsCollection(); - Log.i(TAG, "Will load OBFs from " + Environment.getExternalStorageDirectory() + "/osmand"); - obfsCollection.addDirectory(Environment.getExternalStorageDirectory() + "/osmand", false); + Log.i(TAG, "Will load OBFs from " + app.getAbsoluteAppPath()); + obfsCollection.addDirectory(app.getAbsoluteAppPath(), false); Log.i(TAG, "Going to prepare all resources for renderer"); mapPresentationEnvironment = new MapPresentationEnvironment( @@ -209,9 +201,7 @@ public class MainActivity extends Activity { mapLayerProvider0 = new MapRasterLayerProvider_Software(mapPrimitivesProvider); mapView.setMapLayerProvider(0, mapLayerProvider0); - System.out.println("NATIVE_INITIALIZED = " + (System.currentTimeMillis() - startTime) / 1000f); - - getSampleApplication().getIconsCache().setDisplayDensityFactor(displayDensityFactor); + app.getIconsCache().setDisplayDensityFactor(displayDensityFactor); //Setup search diff --git a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/SampleApplication.java b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/SampleApplication.java index cb029c96e4..8f85cc87fc 100644 --- a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/SampleApplication.java +++ b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/SampleApplication.java @@ -3,13 +3,19 @@ package net.osmand.core.samples.android.sample1; import android.app.Application; import android.os.Environment; +import net.osmand.core.android.NativeCore; +import net.osmand.core.jni.LogSeverityLevel; +import net.osmand.core.jni.Logger; +import net.osmand.core.samples.android.sample1.core.CoreResourcesFromAndroidAssetsCustom; import net.osmand.osm.AbstractPoiType; import net.osmand.osm.MapPoiTypes; +import java.io.File; import java.lang.reflect.Field; public class SampleApplication extends Application { + private CoreResourcesFromAndroidAssetsCustom assetsCustom; private MapPoiTypes poiTypes; private IconsCache iconsCache; @@ -19,7 +25,19 @@ public class SampleApplication extends Application super.onCreate(); initPoiTypes(); - iconsCache = new IconsCache(2f); + + // Initialize native core + if (NativeCore.isAvailable() && !NativeCore.isLoaded()) { + assetsCustom = CoreResourcesFromAndroidAssetsCustom.loadFromCurrentApplication(this); + NativeCore.load(assetsCustom); + } + Logger.get().setSeverityLevelThreshold(LogSeverityLevel.Debug); + + iconsCache = new IconsCache(assetsCustom); + } + + public CoreResourcesFromAndroidAssetsCustom getAssetsCustom() { + return assetsCustom; } public MapPoiTypes getPoiTypes() { @@ -54,6 +72,17 @@ public class SampleApplication extends Application }); } + public String getAbsoluteAppPath() { + return Environment.getExternalStorageDirectory() + "/osmand"; + } + + public File getAppPath(String path) { + if (path == null) { + path = ""; + } + return new File(getAbsoluteAppPath(), path); + } + public String getLangTranslation(String l) { try { java.lang.reflect.Field f = R.string.class.getField("lang_"+l); diff --git a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/adapters/AmenitySearchListItem.java b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/adapters/AmenitySearchListItem.java index 33f4731284..de35d52935 100644 --- a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/adapters/AmenitySearchListItem.java +++ b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/adapters/AmenitySearchListItem.java @@ -2,7 +2,6 @@ package net.osmand.core.samples.android.sample1.adapters; import android.graphics.drawable.Drawable; -import net.osmand.core.jni.OsmAndCore; import net.osmand.core.samples.android.sample1.MapUtils; import net.osmand.core.samples.android.sample1.SampleApplication; import net.osmand.core.samples.android.sample1.search.AmenitySearchItem; @@ -78,10 +77,7 @@ public class AmenitySearchListItem extends SearchListItem { Drawable drawable = null; PoiType st = amenity.getType().getPoiTypeByKeyName(amenity.getSubType()); if (st != null) { - drawable = app.getIconsCache().getIcon("mx_" + st.getIconKeyName()); - if (drawable == null) { - drawable = app.getIconsCache().getIcon("mx_" + st.getOsmTag() + "_" + st.getOsmValue()); - } + drawable = app.getIconsCache().getIcon(st.getOsmTag() + "_" + st.getOsmValue()); } return drawable; } diff --git a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/core/CoreResourcesFromAndroidAssetsCustom.java b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/core/CoreResourcesFromAndroidAssetsCustom.java new file mode 100644 index 0000000000..984b52d255 --- /dev/null +++ b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/core/CoreResourcesFromAndroidAssetsCustom.java @@ -0,0 +1,338 @@ +package net.osmand.core.samples.android.sample1.core; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.Log; + +import net.osmand.core.jni.BoolPtr; +import net.osmand.core.jni.SWIGTYPE_p_QByteArray; +import net.osmand.core.jni.SWIGTYPE_p_bool; +import net.osmand.core.jni.SwigUtilities; +import net.osmand.core.jni.interface_ICoreResourcesProvider; +import net.osmand.core.samples.android.sample1.SampleApplication; +import net.osmand.util.Algorithms; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +// This class provides reverse mapping from 'embed-resources.list' to files&folders scheme used by OsmAndCore_android.aar package +@TargetApi(Build.VERSION_CODES.GINGERBREAD) +public class CoreResourcesFromAndroidAssetsCustom extends interface_ICoreResourcesProvider { + private static final String TAG = "CoreResFromAndAssets"; + private static final String NATIVE_TAG = "CoreResFromAndAssets"; + + private final Context _context; + private String _bundleFilename; + private final HashMap _resources = new HashMap(); + + private final class ResourceData { + public File path; + public long offset; + public long size; + } + + private final class ResourceEntry { + public ResourceData defaultVariant; + public TreeMap variantsByDisplayDensityFactor; + } + + private CoreResourcesFromAndroidAssetsCustom(final Context context) { + _context = context; + } + + private boolean load() { + final AssetManager assetManager = _context.getResources().getAssets(); + + PackageInfo packageInfo = null; + try { + packageInfo = _context.getPackageManager().getPackageInfo(_context.getPackageName(), 0); + } catch (NameNotFoundException e) { + Log.e(TAG, "Failed to get own package info", e); + return false; + } + _bundleFilename = packageInfo.applicationInfo.sourceDir; + Log.i(TAG, "Located own package at '" + _bundleFilename + "'"); + + // Load the index + final List resourcesInBundle = new LinkedList(); + try { + final InputStream resourcesIndexStream = assetManager.open("OsmAndCore_ResourcesBundle.index", + AssetManager.ACCESS_BUFFER); + + final BufferedReader resourcesIndexBufferedReader = new BufferedReader(new InputStreamReader( + resourcesIndexStream)); + String resourceInBundle; + while ((resourceInBundle = resourcesIndexBufferedReader.readLine()) != null) + resourcesInBundle.add(resourceInBundle); + } catch (IOException e) { + Log.e(TAG, "Failed to read bundle index", e); + return false; + } + Log.i(TAG, "Application contains " + resourcesInBundle.size() + " resources"); + + // Parse resources index + final Pattern resourceNameWithQualifiersRegExp = Pattern.compile("(?:\\[(.*)\\]/)(.*)"); + for (String resourceInBundle : resourcesInBundle) { + // Process resource name + String pureResourceName = resourceInBundle; + String[] qualifiers = null; + final Matcher resourceNameComponentsMatcher = resourceNameWithQualifiersRegExp.matcher(resourceInBundle); + if (resourceNameComponentsMatcher.matches()) { + qualifiers = resourceNameComponentsMatcher.group(1).split(";"); + pureResourceName = resourceNameComponentsMatcher.group(2); + } + + // Get location of this resource + final String path = "OsmAndCore_ResourcesBundle/" + resourceInBundle + (resourceInBundle.endsWith(".png") ? "" : ".qz"); + final File extractedPath = ((SampleApplication) _context.getApplicationContext()).getAppPath(path); + final ResourceData resourceData = new ResourceData(); + if (!extractedPath.exists()) { + try { + final AssetFileDescriptor resourceFd = assetManager.openFd(path); + long declaredSize = resourceFd.getDeclaredLength(); + resourceData.size = resourceFd.getLength(); + resourceData.offset = resourceFd.getStartOffset(); + resourceData.path = new File(_bundleFilename); + resourceFd.close(); + if (declaredSize != resourceData.size) { + Log.e(NATIVE_TAG, "Declared size does not match size for '" + resourceInBundle + "'"); + continue; + } + } catch (FileNotFoundException e) { + try { + final File containgDir = extractedPath.getParentFile(); + if (containgDir != null && !containgDir.exists()) + containgDir.mkdirs(); + extractedPath.createNewFile(); + + final InputStream resourceStream = assetManager.open(path, AssetManager.ACCESS_STREAMING); + final FileOutputStream fileStream = new FileOutputStream(extractedPath); + Algorithms.streamCopy(resourceStream, fileStream); + Algorithms.closeStream(fileStream); + Algorithms.closeStream(resourceStream); + } catch (IOException e2) { + if (extractedPath.exists()) + extractedPath.delete(); + + Log.e(NATIVE_TAG, "Failed to extract '" + resourceInBundle + "'", e2); + continue; + } + resourceData.offset = 0; + resourceData.path = extractedPath; + resourceData.size = resourceData.path.length(); + } catch (IOException e) { + Log.e(NATIVE_TAG, "Failed to locate '" + resourceInBundle + "'", e); + continue; + } + } else { + resourceData.offset = 0; + resourceData.path = extractedPath; + resourceData.size = resourceData.path.length(); + } + + // Get resource entry for this resource + ResourceEntry resourceEntry = _resources.get(pureResourceName); + if (resourceEntry == null) { + resourceEntry = new ResourceEntry(); + _resources.put(pureResourceName, resourceEntry); + } + if (qualifiers == null) { + resourceEntry.defaultVariant = resourceData; + } else { + for (String qualifier : qualifiers) { + final String[] qualifierComponents = qualifier.trim().split("="); + + if (qualifierComponents.length == 2 && qualifierComponents[0].equals("ddf")) { + float ddfValue; + try { + ddfValue = Float.parseFloat(qualifierComponents[1]); + } catch (NumberFormatException e) { + Log.e(TAG, "Unsupported value '" + qualifierComponents[1] + "' for DDF qualifier", e); + continue; + } + + if (resourceEntry.variantsByDisplayDensityFactor == null) + resourceEntry.variantsByDisplayDensityFactor = new TreeMap(); + resourceEntry.variantsByDisplayDensityFactor.put(ddfValue, resourceData); + } else { + Log.w(TAG, "Unsupported qualifier '" + qualifier.trim() + "'"); + } + } + } + } + + return true; + } + + @Override + public SWIGTYPE_p_QByteArray getResource(String name, float displayDensityFactor, SWIGTYPE_p_bool ok_) { + final BoolPtr ok = BoolPtr.frompointer(ok_); + + ResourceData resourceData = getResourceData(name, displayDensityFactor); + if (resourceData == null) { + Log.w(TAG, "Requested resource [ddf=" + displayDensityFactor + "]'" + name + "' was not found"); + if (ok != null) + ok.assign(false); + return SwigUtilities.emptyQByteArray(); + } + final String dataPath = resourceData.path.getAbsolutePath(); + + final SWIGTYPE_p_QByteArray data; + if (resourceData.offset == 0 && resourceData.size == resourceData.path.length()) { + if (!name.endsWith(".png")) { + data = SwigUtilities.qDecompress(SwigUtilities.readEntireFile(dataPath)); + } else { + data = SwigUtilities.readEntireFile(dataPath); + } + } else { + if (!name.endsWith(".png")) { + data = SwigUtilities.qDecompress(SwigUtilities.readPartOfFile(dataPath, + resourceData.offset, resourceData.size)); + } else { + data = SwigUtilities.readPartOfFile(dataPath, + resourceData.offset, resourceData.size); + } + } + + if (ok != null) + ok.assign(true); + return data; + } + + @Override + public SWIGTYPE_p_QByteArray getResource(String name, SWIGTYPE_p_bool ok_) { + final BoolPtr ok = BoolPtr.frompointer(ok_); + + final ResourceEntry resourceEntry = _resources.get(name); + if (resourceEntry == null) { + Log.w(TAG, "Requested resource '" + name + "' was not found"); + if (ok != null) + ok.assign(false); + return SwigUtilities.emptyQByteArray(); + } + + if (resourceEntry.defaultVariant == null) { + Log.w(TAG, "Requested resource '" + name + "' was not found"); + if (ok != null) + ok.assign(false); + return SwigUtilities.emptyQByteArray(); + } + System.out.println(resourceEntry.defaultVariant.path.getAbsolutePath()); + final SWIGTYPE_p_QByteArray data; + final String dataPath = resourceEntry.defaultVariant.path.getAbsolutePath(); + if (resourceEntry.defaultVariant.offset == 0 + && resourceEntry.defaultVariant.size == resourceEntry.defaultVariant.path.length()) { + if (!name.endsWith(".png")) { + data = SwigUtilities.qDecompress(SwigUtilities.readEntireFile(dataPath)); + } else { + data = SwigUtilities.readEntireFile(dataPath); + } + } else { + if (!name.endsWith(".png")) { + data = SwigUtilities.qDecompress(SwigUtilities.readPartOfFile(dataPath, + resourceEntry.defaultVariant.offset, resourceEntry.defaultVariant.size)); + } else { + data = SwigUtilities.readPartOfFile(dataPath, + resourceEntry.defaultVariant.offset, resourceEntry.defaultVariant.size); + } + } + + if (ok != null) + ok.assign(true); + return data; + } + + @Override + public boolean containsResource(String name, float displayDensityFactor) { + final ResourceEntry resourceEntry = _resources.get(name); + if (resourceEntry == null || resourceEntry.variantsByDisplayDensityFactor == null) + return false; + + // If there's variant for any DDF, it will be used + return true; + } + + @Override + public boolean containsResource(String name) { + final ResourceEntry resourceEntry = _resources.get(name); + if (resourceEntry == null) + return false; + + if (resourceEntry.defaultVariant == null) + return false; + + return true; + } + + public ResourceData getResourceData(String name, float displayDensityFactor) { + final ResourceEntry resourceEntry = _resources.get(name); + if (resourceEntry == null || resourceEntry.variantsByDisplayDensityFactor == null) { + return null; + } + + Map.Entry resourceDataEntry = resourceEntry.variantsByDisplayDensityFactor + .ceilingEntry(displayDensityFactor); + if (resourceDataEntry == null) + resourceDataEntry = resourceEntry.variantsByDisplayDensityFactor.lastEntry(); + ResourceData resourceData = resourceDataEntry.getValue(); + Log.d(TAG, "Using ddf=" + resourceDataEntry.getKey() + " while looking for " + displayDensityFactor + " of '" + + name + "'"); + System.out.println(resourceData.path.getAbsolutePath()); + return resourceData; + } + + public Drawable getIcon(String name, float displayDensityFactor) { + ResourceData resourceData = getResourceData(name, displayDensityFactor); + if (resourceData != null) { + final String dataPath = resourceData.path.getAbsolutePath(); + if (resourceData.offset == 0 && resourceData.size == resourceData.path.length()) { + return BitmapDrawable.createFromPath(dataPath); + } else { + try { + byte[] array = new byte[(int)resourceData.size]; + FileInputStream fis = new FileInputStream(dataPath); + fis.skip((int)resourceData.offset); + fis.read(array, 0, (int)resourceData.size); + fis.close(); + Bitmap bitmap = BitmapFactory.decodeByteArray(array, 0, (int)resourceData.size); + return new BitmapDrawable(_context.getResources(), bitmap); + + } catch (IOException e) { + Log.d(TAG, "Cannot read file: " + dataPath); + } + } + } + return null; + } + + public static CoreResourcesFromAndroidAssetsCustom loadFromCurrentApplication(final Context context) { + final CoreResourcesFromAndroidAssetsCustom bundle = new CoreResourcesFromAndroidAssetsCustom(context); + + if (!bundle.load()) + return null; + + return bundle; + } +} diff --git a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/search/AddressSearchItem.java b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/search/AddressSearchItem.java index dd8078b630..14ec48b31d 100644 --- a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/search/AddressSearchItem.java +++ b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/search/AddressSearchItem.java @@ -23,7 +23,7 @@ public class AddressSearchItem extends SearchItem { addLocalizedNames(street.getLocalizedNames()); if (street.getStreetGroup() != null) { nameSuffix = "st."; - typeStr = getTypeStr(street.getStreetGroup()) + " — " + street.getStreetGroup().getNativeName(); + typeStr = street.getStreetGroup().getNativeName() + " — " + getTypeStr(street.getStreetGroup()); } else { typeStr = "Street"; } diff --git a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/search/AmenitySearchItem.java b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/search/AmenitySearchItem.java index a6f87645f5..49a10721fa 100644 --- a/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/search/AmenitySearchItem.java +++ b/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/search/AmenitySearchItem.java @@ -3,8 +3,6 @@ package net.osmand.core.samples.android.sample1.search; import net.osmand.core.jni.Amenity; import net.osmand.core.jni.DecodedCategoryList; import net.osmand.core.jni.DecodedValueList; -import net.osmand.core.jni.QStringList; -import net.osmand.core.jni.QStringStringHash; import net.osmand.util.Algorithms; import java.util.HashMap; @@ -34,7 +32,7 @@ public class AmenitySearchItem extends SearchItem { for (int i = 0; i < decodedValueList.size(); i++) { Amenity.DecodedValue decodedValue = decodedValueList.get(i); String tag = decodedValue.getDeclaration().getTagName(); - String value = decodedValue.getValue().toString(); + String value = decodedValue.getValue(); values.put(tag, value); } }