From 235f8b1cbff8ed13071d5c19c0be492c0b25cb78 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Sat, 17 Mar 2012 21:15:49 +0100 Subject: Add 'asset' URLConnection; IOUtil uses URLConnection / incr. effeciency; Android ClassLoaderUtil cleanup; - Add 'asset' URLConnection - Please read API doc 'PiggybackURLConnection' and 'AssetURLConnection' - Solves generic resource handling where platform locations may differ, ie ClassLoader lookup on Android in the 'assets/' subfolder. - New Android 'AssetDexClassLoader' uses 'assets/' folder for findResource(..) - aapt.signed (our APK ant task) - uses 'assets/' folder - adds the 'assetsdir' attribute allowing to copy other assets into the APK - IOUtil uses URLConnection / incr. effeciency - using URLConnection on all getResource(..) since URL is connected anyways for validation and URLConnection can be used by caller right away - String getRelativeOf(URL, String) -> URL getRelativeOf(URL, String) - preserves scheme, authority, etc - simple parentOf handling, more efficient - reusing new 'asset' protocol impl. - Android ClassLoaderUtil cleanup; - Use createClassLoader(..) impl for build-in static jogamp and user APKs, which removes code redundancy Tests: New code path, especially 'assets' are covered by new unit tests, no regressions on Linux. --- .../com/jogamp/common/net/AssetURLConnection.java | 98 ++++++++ .../com/jogamp/common/net/AssetURLContext.java | 212 +++++++++++++++++ .../jogamp/common/net/AssetURLStreamHandler.java | 37 +++ .../common/net/GenericURLStreamHandlerFactory.java | 66 ++++++ .../jogamp/common/net/PiggybackURLConnection.java | 84 +++++++ .../com/jogamp/common/net/PiggybackURLContext.java | 18 ++ src/java/com/jogamp/common/net/asset/Handler.java | 38 ++++ src/java/com/jogamp/common/nio/AbstractBuffer.java | 1 + src/java/com/jogamp/common/os/AndroidVersion.java | 12 +- src/java/com/jogamp/common/util/IOUtil.java | 250 +++++++++++++-------- .../jogamp/android/launcher/ActivityLauncher.java | 11 +- .../android/launcher/AssetDexClassLoader.java | 29 +++ .../jogamp/android/launcher/ClassLoaderUtil.java | 140 ++++++------ src/java/jogamp/android/launcher/LauncherMain.java | 2 +- .../android/launcher/TraceDexClassLoader.java | 27 +++ .../common/os/android/GluegenVersionActivity.java | 5 +- .../jogamp/common/os/android/StaticContext.java | 14 +- 17 files changed, 863 insertions(+), 181 deletions(-) create mode 100644 src/java/com/jogamp/common/net/AssetURLConnection.java create mode 100644 src/java/com/jogamp/common/net/AssetURLContext.java create mode 100644 src/java/com/jogamp/common/net/AssetURLStreamHandler.java create mode 100644 src/java/com/jogamp/common/net/GenericURLStreamHandlerFactory.java create mode 100644 src/java/com/jogamp/common/net/PiggybackURLConnection.java create mode 100644 src/java/com/jogamp/common/net/PiggybackURLContext.java create mode 100644 src/java/com/jogamp/common/net/asset/Handler.java create mode 100644 src/java/jogamp/android/launcher/AssetDexClassLoader.java create mode 100644 src/java/jogamp/android/launcher/TraceDexClassLoader.java (limited to 'src/java') diff --git a/src/java/com/jogamp/common/net/AssetURLConnection.java b/src/java/com/jogamp/common/net/AssetURLConnection.java new file mode 100644 index 0000000..f2a5a01 --- /dev/null +++ b/src/java/com/jogamp/common/net/AssetURLConnection.java @@ -0,0 +1,98 @@ +package com.jogamp.common.net; + +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; + +/** + * See base class {@link PiggybackURLConnection} for motivation. + * + *

+ * asset resource location protocol connection. + *

+ * + *

+ * See {@link AssetURLContext#resolve(String)} how resources are being resolved. + *

+ * + *

Example:

+ * + * Assuming the plain asset entry test/lala.txt is being resolved by + * a class test.LaLaTest, ie. using the asset aware ClassLoader, + * one would use the following asset aware filesystem layout: + * + *
+ *  test/LaLaTest.class
+ *  assets/test/lala.txt
+ * 
+ * + * The above maybe on a plain filesystem, or within a JAR or an APK file, + * e.g. jogamp.test.apk. + * + * The above would result in the following possible URLs + * reflecting the plain and resolved state of the asset URL: + *
+ *  0 Entry          test/lala.txt
+ *  1 Plain    asset:test/lala.txt
+ *  2 Resolved asset:jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt
+ * 
+ * + *

+ * The sub protocol URL of the resolved asset + *

+ *  3 Sub-URL        jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt
+ * 
+ * can be retrieved using {@link #getSubProtocol()}. + *

+ * + * In all above cases, the asset entry is test/lala.txt, + * which can be retrieved via {@link #getEntryName()}. + * + *

+ *

General Implementation Notes:

+ * An asset URL is resolved using {@link AssetURLContext#getClassLoader()}.{@link ClassLoader#getResource(String) getResource(String)}, + * hence the only requirement for an implementation is to have an asset aware ClassLoader + * as described in {@link AssetURLContext#getClassLoader()}. + *

+ *

+ *

Warning:

+ * Since the asset protocol is currently not being implemented + * on all platform with an appropriate ClassLoader, a user shall not create the asset URL manually.
+ *

+ * + *

Android Implementation Notes:

+ *

+ * The Android ClassLoader {@link jogamp.android.launcher.AssetDexClassLoader} + * resolves the resource as an asset URL in it's {@link ClassLoader#findResource(String)} implementation.

+ *

+ * Currently we attach our asset {@link java.net.URLStreamHandlerFactory} + * to allow {@link java.net.URL} to handle asset URLs via our asset {@link java.net.URLStreamHandler} implementation. + *

+ */ +public class AssetURLConnection extends PiggybackURLConnection { + + public AssetURLConnection(URL url, AssetURLContext implHelper) { + super(url, implHelper); + } + + @Override + public String getEntryName() throws IOException { + if(!connected) { + throw new IOException("not connected"); + } + + final String urlPath ; + if(subConn instanceof JarURLConnection) { + urlPath = ((JarURLConnection)subConn).getEntryName(); + } else { + urlPath = subConn.getURL().getPath(); + } + + if(urlPath.startsWith(AssetURLContext.assets_folder)) { + return urlPath.substring(AssetURLContext.assets_folder.length()); + } else { + return urlPath; + } + } + +} diff --git a/src/java/com/jogamp/common/net/AssetURLContext.java b/src/java/com/jogamp/common/net/AssetURLContext.java new file mode 100644 index 0000000..00c7df7 --- /dev/null +++ b/src/java/com/jogamp/common/net/AssetURLContext.java @@ -0,0 +1,212 @@ +package com.jogamp.common.net; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +import jogamp.common.Debug; + +import com.jogamp.common.os.AndroidVersion; +import com.jogamp.common.util.IOUtil; + +/** + * See {@link PiggybackURLConnection} for description and examples. + */ +public abstract class AssetURLContext implements PiggybackURLContext { + private static final boolean DEBUG = Debug.isPropertyDefined("jogamp.debug.IOUtil", true); + + /** The asset URL protocol name asset */ + public static final String asset_protocol = "asset"; + + /** The asset URL protocol prefix asset: */ + public static final String asset_protocol_prefix = "asset:"; + + /** + * The optional asset folder name with ending slash assets/. + *

+ * Note that the asset folder is not used on all platforms using the asset protocol + * and you should not rely on it, use {@link AssetURLConnection#getEntryName()}. + *

+ **/ + public static final String assets_folder = "assets/"; + + public static AssetURLContext create(final ClassLoader cl) { + return new AssetURLContext() { + @Override + public ClassLoader getClassLoader() { + return cl; + } + }; + } + + public static AssetURLStreamHandler createHandler(final ClassLoader cl) { + return new AssetURLStreamHandler(create(cl)); + } + + /** + * Create an asset URL, suitable even w/o the registered asset URLStreamHandler. + *

+ * This is equivalent with: + *

+     *   return new URL(null, path.startsWith("asset:") ? path : "asset:" + path, new AssetURLStreamHandler(cl));
+     * 
+ *

+ * @param path resource path, with or w/o asset: prefix + * @param cl the ClassLoader used to resolve the location, see {@link #getClassLoader()}. + * @return + * @throws MalformedURLException + */ + public static URL createURL(String path, ClassLoader cl) throws MalformedURLException { + return new URL(null, path.startsWith(asset_protocol_prefix) ? path : asset_protocol_prefix + path, createHandler(cl)); + } + + /** + * Create an asset URL, suitable only with the registered asset URLStreamHandler. + *

+ * This is equivalent with: + *

+     *   return new URL(path.startsWith("asset:") ? path : "asset:" + path);
+     * 
+ *

+ * @param path resource path, with or w/o asset: prefix + * @return + * @throws MalformedURLException + */ + public static URL createURL(String path) throws MalformedURLException { + return new URL(path.startsWith(asset_protocol_prefix) ? path : asset_protocol_prefix + path); + } + + /** + * Returns the asset handler previously set via {@link #registerHandler(ClassLoader)}, + * or null if none was set. + */ + public static URLStreamHandler getRegisteredHandler() { + final GenericURLStreamHandlerFactory f = GenericURLStreamHandlerFactory.register(); + return ( null != f ) ? f.getHandler(asset_protocol) : null; + } + + /** + * Registers the generic URLStreamHandlerFactory via {@link GenericURLStreamHandlerFactory#register()} + * and if successful sets the asset handler for the given ClassLoader cl. + * + * @return true if successful, otherwise false + */ + public static boolean registerHandler(ClassLoader cl) { + final GenericURLStreamHandlerFactory f = GenericURLStreamHandlerFactory.register(); + if( null != f ) { + f.setHandler(asset_protocol, createHandler(cl)); + return true; + } else { + return false; + } + } + + /** + * Returns an asset aware ClassLoader. + *

+ * The ClassLoader is required to find the asset resource + * via it's URL findResource(String) implementation. + *

+ *

+ * It's URL findResource(String) implementation shall return either + * an asset URL asset:sub-protocol or just the sub-protocol URL. + *

+ *

+ * For example, on Android, we redirect all path request to assets/path. + *

+ */ + public abstract ClassLoader getClassLoader(); + + @Override + public String getImplementedProtocol() { + return asset_protocol; + } + + /** + * {@inheritDoc} + *

+ * This implementation attempts to resolve path in the following order: + *

    + *
  1. as a valid URL: new URL(path), use sub-protocol if asset URL
  2. + *
  3. via ClassLoader: {@link #getClassLoader()}.{@link ClassLoader#getResource(String) getResource(path)}, use sub-protocol if asset URL
  4. + *
  5. as a File: new File(path).toURI().toURL() + *
+ *

+ *

+ * In case of using the ClassLoader (2) and if running on Android, + * the {@link #assets_folder} is being prepended to path if missing. + *

+ **/ + @Override + public URLConnection resolve(String path) throws IOException { + return resolve(path, getClassLoader()); + } + + public static URLConnection resolve(String path, ClassLoader cl) throws IOException { + URL url = null; + URLConnection conn = null; + int type = -1; + + if(DEBUG) { + System.err.println("AssetURLContext.resolve: <"+path+">"); + } + + try { + // lookup as valid sub-protocol + url = new URL(path); + conn = open(url); + type = null != conn ? 1 : -1; + } catch(MalformedURLException e1) { if(DEBUG) { System.err.println("ERR: "+e1.getMessage()); } } + + if(null == conn && null != cl) { + // lookup via ClassLoader .. cleanup leading '/' + String cpath = path; + while(cpath.startsWith("/")) { + cpath = cpath.substring(1); + } + if(AndroidVersion.isAvailable) { + cpath = cpath.startsWith(assets_folder) ? cpath : assets_folder + cpath; + } + url = cl.getResource(cpath); + conn = open(url); + type = null != conn ? 2 : -1; + } + + if(null == conn) { + // lookup as File + try { + File file = new File(path); + if(file.exists()) { + url = IOUtil.toURLSimple(file); + conn = open(url); + type = null != conn ? 3 : -1; + } + } catch (Throwable e) { if(DEBUG) { System.err.println("ERR: "+e.getMessage()); } } + } + + if(DEBUG) { + System.err.println("AssetURLContext.resolve: type "+type+": url <"+url+">, conn <"+conn+">, connURL <"+(null!=conn?conn.getURL():null)+">"); + } + if(null == conn) { + throw new FileNotFoundException("Could not look-up: "+path+" as URL, w/ ClassLoader or as File"); + } + return conn; + } + + private static URLConnection open(URL url) { + if(null==url) { + return null; + } + try { + final URLConnection c = url.openConnection(); + c.connect(); // redundant + return c; + } catch (IOException ioe) { if(DEBUG) { System.err.println("ERR: "+ioe.getMessage()); } } + return null; + } + +} diff --git a/src/java/com/jogamp/common/net/AssetURLStreamHandler.java b/src/java/com/jogamp/common/net/AssetURLStreamHandler.java new file mode 100644 index 0000000..8d95b2d --- /dev/null +++ b/src/java/com/jogamp/common/net/AssetURLStreamHandler.java @@ -0,0 +1,37 @@ +package com.jogamp.common.net; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +import com.jogamp.common.net.AssetURLConnection; + +/** + * {@link URLStreamHandler} to handle the asset protocol. + * + *

+ * This is the asset URLStreamHandler variation + * for manual use. + *

+ *

+ * It requires passing a valid {@link AssetURLContext} + * for construction, hence it's not suitable for the pkg factory model. + *

+ */ +public class AssetURLStreamHandler extends URLStreamHandler { + AssetURLContext ctx; + + public AssetURLStreamHandler(AssetURLContext ctx) { + this.ctx = ctx; + } + + @Override + protected URLConnection openConnection(URL u) throws IOException { + final AssetURLConnection c = new AssetURLConnection(u, ctx); + c.connect(); + return c; + } + + +} diff --git a/src/java/com/jogamp/common/net/GenericURLStreamHandlerFactory.java b/src/java/com/jogamp/common/net/GenericURLStreamHandlerFactory.java new file mode 100644 index 0000000..79d7f71 --- /dev/null +++ b/src/java/com/jogamp/common/net/GenericURLStreamHandlerFactory.java @@ -0,0 +1,66 @@ +package com.jogamp.common.net; + +import java.net.URL; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Map; + +public class GenericURLStreamHandlerFactory implements URLStreamHandlerFactory { + private static GenericURLStreamHandlerFactory factory = null; + + private final Map protocolHandlers; + + private GenericURLStreamHandlerFactory() { + protocolHandlers = new HashMap(); + } + + /** + * Sets the handler for protocol. + * + * @return the previous set handler, or null if none was set. + */ + public synchronized final URLStreamHandler setHandler(String protocol, URLStreamHandler handler) { + return protocolHandlers.put(protocol, handler); + } + + /** + * Returns the protocol handler previously set via {@link #setHandler(String, URLStreamHandler)}, + * or null if none was set. + */ + public synchronized final URLStreamHandler getHandler(String protocol) { + return protocolHandlers.get(protocol); + } + + @Override + public synchronized final URLStreamHandler createURLStreamHandler(String protocol) { + return getHandler(protocol); + } + + /** + * Returns the singleton instance of the registered GenericURLStreamHandlerFactory + * or null if registration was not successful. + *

+ * Registration is only performed once. + *

+ */ + public synchronized static GenericURLStreamHandlerFactory register() { + if(null == factory) { + factory = AccessController.doPrivileged(new PrivilegedAction() { + public GenericURLStreamHandlerFactory run() { + boolean ok = false; + GenericURLStreamHandlerFactory f = new GenericURLStreamHandlerFactory(); + try { + URL.setURLStreamHandlerFactory(f); + ok = true; + } catch (Throwable e) { + System.err.println("GenericURLStreamHandlerFactory: Setting URLStreamHandlerFactory failed: "+e.getMessage()); + } + return ok ? f : null; + } } ); + } + return factory; + } +} diff --git a/src/java/com/jogamp/common/net/PiggybackURLConnection.java b/src/java/com/jogamp/common/net/PiggybackURLConnection.java new file mode 100644 index 0000000..39f1637 --- /dev/null +++ b/src/java/com/jogamp/common/net/PiggybackURLConnection.java @@ -0,0 +1,84 @@ +package com.jogamp.common.net; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +/** + * Generic resource location protocol connection, + * using another sub-protocol as the vehicle for a piggyback protocol. + *

+ * The details of the sub-protocol can be queried using {@link #getSubProtocol()}. + *

+ *

+ * See example in {@link AssetURLConnection}. + *

+ */ +public abstract class PiggybackURLConnection extends URLConnection { + protected URL subUrl; + protected URLConnection subConn; + protected I context; + + /** + * @param url the specific URL for this instance + * @param context the piggyback context, defining state independent code and constants + */ + protected PiggybackURLConnection(URL url, I context) { + super(url); + this.context = context; + } + + /** + *

+ * Resolves the URL via {@link PiggybackURLContext#resolve(String)}, + * see {@link AssetURLContext#resolve(String)} for an example. + *

+ * + * {@inheritDoc} + */ + @Override + public synchronized void connect() throws IOException { + if(!connected) { + subConn = context.resolve(url.getPath()); + subUrl = subConn.getURL(); + connected = true; + } + } + + @Override + public InputStream getInputStream() throws IOException { + if(!connected) { + throw new IOException("not connected"); + } + return subConn.getInputStream(); + } + + /** + * Returns the entry name of the asset. + *
+     * Plain     asset:test/lala.txt
+     * Resolved  asset:jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt
+     * Result          test/lala.txt
+     * 
+ * @throws IOException is not connected + **/ + public abstract String getEntryName() throws IOException; + + /** + * Returns the resolved sub protocol of the asset or null, ie: + *
+     * Plain     asset:test/lala.txt
+     * Resolved  asset:jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt
+     * Result          jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt
+     * 
+ * + * @throws IOException is not connected + */ + public URL getSubProtocol() throws IOException { + if(!connected) { + throw new IOException("not connected"); + } + return subUrl; + } +} diff --git a/src/java/com/jogamp/common/net/PiggybackURLContext.java b/src/java/com/jogamp/common/net/PiggybackURLContext.java new file mode 100644 index 0000000..2cb48b5 --- /dev/null +++ b/src/java/com/jogamp/common/net/PiggybackURLContext.java @@ -0,0 +1,18 @@ +package com.jogamp.common.net; + +import java.io.IOException; +import java.net.URLConnection; + +/** + * See {@link PiggybackURLConnection} for description and examples. + */ +public interface PiggybackURLContext { + + /** Returns the specific protocol, constant for this implementation. */ + public String getImplementedProtocol(); + + /** + * Resolving path to a URL sub protocol and return it's open URLConnection + **/ + public URLConnection resolve(String path) throws IOException; +} diff --git a/src/java/com/jogamp/common/net/asset/Handler.java b/src/java/com/jogamp/common/net/asset/Handler.java new file mode 100644 index 0000000..1063797 --- /dev/null +++ b/src/java/com/jogamp/common/net/asset/Handler.java @@ -0,0 +1,38 @@ +package com.jogamp.common.net.asset; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +import com.jogamp.common.net.AssetURLConnection; +import com.jogamp.common.net.AssetURLContext; + +/** + * {@link URLStreamHandler} to handle the asset protocol. + * + *

+ * This is the asset URLStreamHandler variation + * using this class ClassLoader for the pkg factory model. + *

+ */ +public class Handler extends URLStreamHandler { + static final AssetURLContext localCL = new AssetURLContext() { + @Override + public ClassLoader getClassLoader() { + return Handler.class.getClassLoader(); + } + }; + + public Handler() { + super(); + } + + @Override + protected URLConnection openConnection(URL u) throws IOException { + final AssetURLConnection c = new AssetURLConnection(u, localCL); + c.connect(); + return c; + } + +} diff --git a/src/java/com/jogamp/common/nio/AbstractBuffer.java b/src/java/com/jogamp/common/nio/AbstractBuffer.java index 322a617..985ba4f 100644 --- a/src/java/com/jogamp/common/nio/AbstractBuffer.java +++ b/src/java/com/jogamp/common/nio/AbstractBuffer.java @@ -39,6 +39,7 @@ import java.nio.Buffer; * @author Sven Gothel * @author Michael Bien */ +@SuppressWarnings("rawtypes") public abstract class AbstractBuffer implements NativeBuffer { protected final int elementSize; diff --git a/src/java/com/jogamp/common/os/AndroidVersion.java b/src/java/com/jogamp/common/os/AndroidVersion.java index b577631..8886273 100644 --- a/src/java/com/jogamp/common/os/AndroidVersion.java +++ b/src/java/com/jogamp/common/os/AndroidVersion.java @@ -32,9 +32,9 @@ public class AndroidVersion { static { final ClassLoader cl = AndroidVersion.class.getClassLoader(); - Class abvClass = null; + Class abvClass = null; Object abvObject= null; - Class abvcClass = null; + Class abvcClass = null; Object abvcObject= null; try { abvClass = ReflectionUtil.getClass(androidBuildVersion, true, cl); @@ -50,7 +50,7 @@ public class AndroidVersion { SDK_INT = getInt(abvClass, abvObject, "SDK_INT"); VERSION_CODES = getVersionCodes(abvcClass, abvcObject); String sdk_name = VERSION_CODES.get(new Integer(SDK_INT)); - SDK_NAME = ( null != sdk_name ) ? sdk_name : "SDK_"+SDK_INT ; + SDK_NAME = ( null != sdk_name ) ? sdk_name : "SDK_"+SDK_INT ; } else { CODENAME = null; INCREMENTAL = null; @@ -61,7 +61,7 @@ public class AndroidVersion { } } - private static final Map getVersionCodes(Class cls, Object obj) { + private static final Map getVersionCodes(Class cls, Object obj) { HashMap map = new HashMap(); Field[] fields = cls.getFields(); for(int i=0; i cls, Object obj, String name) { try { Field f = cls.getField(name); return (String) f.get(obj); @@ -83,7 +83,7 @@ public class AndroidVersion { return null; } - private static final int getInt(Class cls, Object obj, String name) { + private static final int getInt(Class cls, Object obj, String name) { try { Field f = cls.getField(name); return f.getInt(obj); diff --git a/src/java/com/jogamp/common/util/IOUtil.java b/src/java/com/jogamp/common/util/IOUtil.java index a05302a..891453e 100644 --- a/src/java/com/jogamp/common/util/IOUtil.java +++ b/src/java/com/jogamp/common/util/IOUtil.java @@ -37,7 +37,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.AccessControlContext; -import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; @@ -48,6 +47,7 @@ import jogamp.common.os.android.StaticContext; import android.content.Context; +import com.jogamp.common.net.AssetURLContext; import com.jogamp.common.nio.Buffers; import com.jogamp.common.os.AndroidVersion; import com.jogamp.common.os.MachineDescription; @@ -68,28 +68,47 @@ public class IOUtil { */ /** - * Copy the specified input stream to the specified output file. The total + * Copy the specified URL resource to the specified output file. The total * number of bytes written is returned. Both streams are closed upon completion. + * + * @param conn the open URLConnection + * @param outFile the destination + * @return + * @throws IOException */ - public static int copyURL2File(URL url, File outFile) throws IOException { - URLConnection conn = url.openConnection(); - conn.connect(); + public static int copyURLConn2File(URLConnection conn, File outFile) throws IOException { + conn.connect(); // redundant int totalNumBytes = 0; InputStream in = new BufferedInputStream(conn.getInputStream()); try { - OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile)); - try { - totalNumBytes = copyStream2Stream(in, out, conn.getContentLength()); - } finally { - out.close(); - } + totalNumBytes = copyStream2File(in, outFile, conn.getContentLength()); } finally { in.close(); } return totalNumBytes; } + /** + * Copy the specified input stream to the specified output file. The total + * number of bytes written is returned. Both streams are closed upon completion. + * + * @param in the source + * @param outFile the destination + * @param totalNumBytes informal number of expected bytes, maybe used for user feedback while processing. -1 if unknown + * @return + * @throws IOException + */ + public static int copyStream2File(InputStream in, File outFile, int totalNumBytes) throws IOException { + OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile)); + try { + totalNumBytes = copyStream2Stream(in, out, totalNumBytes); + } finally { + out.close(); + } + return totalNumBytes; + } + /** * Copy the specified input stream to the specified output stream. The total * number of bytes written is returned. @@ -301,103 +320,88 @@ public class IOUtil { */ /** - * Locating a resource using 'getResource(String path, ClassLoader cl)', - * with the + * Locating a resource using {@link #getResource(String, ClassLoader)}: *
    - *
  • context's package name-path plus the resourcePath (incl. JAR/Applets)
  • - *
  • context's ClassLoader and the resourcePath as is (filesystem)
  • + *
  • relative: context's package name-path plus resourcePath via context's ClassLoader. This allows locations relative to JAR- and other URLs.
  • + *
  • absolute: context's ClassLoader and the resourcePath as is (filesystem)
  • *
* + *

+ * Returns the resolved and open URLConnection or null if not found. + *

+ * * @see #getResource(String, ClassLoader) + * @see ClassLoader#getResource(String) + * @see ClassLoader#getSystemResource(String) */ - public static URL getResource(Class context, String resourcePath) { + public static URLConnection getResource(Class context, String resourcePath) { if(null == resourcePath) { return null; } - ClassLoader contextCL = (null!=context)?context.getClassLoader():null; - URL url = null; + ClassLoader contextCL = (null!=context)?context.getClassLoader():IOUtil.class.getClassLoader(); + URLConnection conn = null; if(null != context) { // scoping the path within the class's package String className = context.getName().replace('.', '/'); int lastSlash = className.lastIndexOf('/'); if (lastSlash >= 0) { String tmpPath = className.substring(0, lastSlash + 1) + resourcePath; - url = getResource(tmpPath, contextCL); + conn = getResource(tmpPath, contextCL); } if(DEBUG) { - System.err.println("IOUtil: found <"+resourcePath+"> within class package: "+(null!=url)); + System.err.println("IOUtil: found <"+resourcePath+"> within class package: "+(null!=conn)); } } else if(DEBUG) { System.err.println("IOUtil: null context"); } - if(null == url) { - url = getResource(resourcePath, contextCL); + if(null == conn) { + conn = getResource(resourcePath, contextCL); if(DEBUG) { - System.err.println("IOUtil: found <"+resourcePath+"> by classloader: "+(null!=url)); + System.err.println("IOUtil: found <"+resourcePath+"> by classloader: "+(null!=conn)); } } - return url; + return conn; } /** - * Locating a resource using the ClassLoader's facility if not null, - * the absolute URL and absolute file. + * Locating a resource using the ClassLoader's facilities. + *

+ * Returns the resolved and connected URLConnection or null if not found. + *

* * @see ClassLoader#getResource(String) * @see ClassLoader#getSystemResource(String) * @see URL#URL(String) * @see File#File(String) */ - public static URL getResource(String resourcePath, ClassLoader cl) { + public static URLConnection getResource(String resourcePath, ClassLoader cl) { if(null == resourcePath) { return null; } if(DEBUG) { System.err.println("IOUtil: locating <"+resourcePath+">, has cl: "+(null!=cl)); } - URL url = null; - if (cl != null) { - url = cl.getResource(resourcePath); - if(!urlExists(url, "cl.getResource()")) { - url = null; - } - } - if(null == url) { - url = ClassLoader.getSystemResource(resourcePath); - if(!urlExists(url, "cl.getSystemResource()")) { - url = null; - } - } - if(null == url) { + if(resourcePath.startsWith(AssetURLContext.asset_protocol_prefix)) { try { - url = new URL(resourcePath); - if(!urlExists(url, "new URL()")) { - url = null; - } - } catch (Throwable e) { + return AssetURLContext.createURL(resourcePath, cl).openConnection(); + } catch (IOException ioe) { if(DEBUG) { System.err.println("IOUtil: Catched Exception:"); - e.printStackTrace(); - } + ioe.printStackTrace(); + } + return null; } - } - if(null == url) { + } else { try { - File file = new File(resourcePath); - if(file.exists()) { - url = toURLSimple(file); - } - } catch (Throwable e) { + return AssetURLContext.resolve(resourcePath, cl); + } catch (IOException ioe) { if(DEBUG) { System.err.println("IOUtil: Catched Exception:"); - e.printStackTrace(); + ioe.printStackTrace(); } } - if(DEBUG) { - System.err.println("IOUtil: file.exists("+resourcePath+") - "+(null!=url)); - } } - return url; + return null; } /** @@ -411,11 +415,11 @@ public class IOUtil { return null; } - while (baseLocation != null && relativeFile.startsWith("../")) { - baseLocation = baseLocation.getParentFile(); - relativeFile = relativeFile.substring(3); - } if (baseLocation != null) { + while (relativeFile.startsWith("../")) { + baseLocation = baseLocation.getParentFile(); + relativeFile = relativeFile.substring(3); + } final File file = new File(baseLocation, relativeFile); // Handle things on Windows return slashify(file.getPath(), false, false); @@ -424,55 +428,119 @@ public class IOUtil { } /** - * Generates a path for the 'relativeFile' relative to the 'baseLocation'. + * @param path assuming a slashified path beginning with "/" as it's root directory, either denotes a file or directory. + * @return parent of path + * @throws MalformedURLException if path is empty or has parent directory available + */ + public static String getParentOf(String path) throws MalformedURLException { + final int pl = null!=path ? path.length() : 0; + if(pl == 0) { + throw new MalformedURLException("path is empty <"+path+">"); + } + + final int e = path.lastIndexOf("/"); + if( e < 0 ) { + throw new MalformedURLException("path contains no '/' <"+path+">"); + } + if( e == 0 ) { + // path is root directory + throw new MalformedURLException("path has no parents <"+path+">"); + } + if( e < pl - 1 ) { + // path is file, return it's parent directory + return path.substring(0, e+1); + } + final int j = path.lastIndexOf("!") + 1; // '!' Separates JARFile entry -> local start of path + // path is a directory .. + final int p = path.lastIndexOf("/", e-1); + if( p >= j) { + return path.substring(0, p+1); + } + throw new MalformedURLException("parent of path contains no '/' <"+path+">"); + } + + /** + * Generates a path for the 'relativeFile' relative to the 'baseLocation', + * hence the result is a absolute location. * - * @param baseLocation denotes a URL to a file + * @param baseLocation denotes a URL to a directory if ending w/ '/', otherwise we assume a file * @param relativeFile denotes a relative file to the baseLocation's parent directory + * @throws MalformedURLException */ - public static String getRelativeOf(URL baseLocation, String relativeFile) { - String urlPath = baseLocation.getPath(); + public static URL getRelativeOf(URL baseLocation, String relativeFile) throws MalformedURLException { + final String scheme = baseLocation.getProtocol(); + final String auth = baseLocation.getAuthority(); + String path = baseLocation.getPath(); + String query = baseLocation.getQuery(); + String fragment = baseLocation.getRef(); - if ( baseLocation.toString().startsWith("jar") ) { - JarURLConnection jarConnection; - try { - jarConnection = (JarURLConnection) baseLocation.openConnection(); - urlPath = jarConnection.getEntryName(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } + if(!path.endsWith("/")) { + path = getParentOf(path); } - - // Try relative path first - return getRelativeOf(new File(urlPath).getParentFile(), relativeFile); + while (relativeFile.startsWith("../")) { + path = getParentOf(path); + relativeFile = relativeFile.substring(3); + } + return compose(scheme, auth, path, relativeFile, query, fragment); } + public static URL compose(String scheme, String auth, String path1, String path2, String query, String fragment) throws MalformedURLException { + StringBuffer sb = new StringBuffer(); + if(null!=scheme) { + sb.append(scheme); + sb.append(":"); + } + if(null!=auth) { + sb.append("//"); + sb.append(auth); + } + if(null!=path1) { + sb.append(path1); + } + if(null!=path2) { + sb.append(path2); + } + if(null!=query) { + sb.append("?"); + sb.append(query); + } + if(null!=fragment) { + sb.append("#"); + sb.append(fragment); + } + return new URL(sb.toString()); + } + /** - * Returns true, if the URL exists and a connection could be opened. + * Returns the connected URLConnection, or null if not url is not available */ - public static boolean urlExists(URL url) { - return urlExists(url, "."); + public static URLConnection openURL(URL url) { + return openURL(url, "."); } - public static boolean urlExists(URL url, String dbgmsg) { - boolean v = false; + /** + * Returns the connected URLConnection, or null if not url is not available + */ + public static URLConnection openURL(URL url, String dbgmsg) { if(null!=url) { try { - url.openConnection(); - v = true; + final URLConnection c = url.openConnection(); + c.connect(); // redundant if(DEBUG) { System.err.println("IOUtil: urlExists("+url+") ["+dbgmsg+"] - true"); } + return c; } catch (IOException ioe) { if(DEBUG) { - System.err.println("IOUtil: urlExists("+url+") ["+dbgmsg+"] - false: "+ioe.getMessage()); + System.err.println("IOUtil: urlExists("+url+") ["+dbgmsg+"] - false - "+ioe.getClass().getSimpleName()+": "+ioe.getMessage()); + ioe.printStackTrace(); } } } else if(DEBUG) { System.err.println("IOUtil: no url - urlExists(null) ["+dbgmsg+"]"); } - return v; + return null; } /** @@ -507,7 +575,7 @@ public class IOUtil { * On Android a temp folder relative to the applications local folder * (see {@link Context#getDir(String, int)}) is returned, if * the Android application/activity has registered it's Application Context - * via {@link StaticContext#setContext(Context)}. + * via {@link StaticContext#init(Context, ClassLoader)}. * This allows using the temp folder w/o the need for sdcard * access, which would be the java.io.tempdir location on Android! *

@@ -517,7 +585,7 @@ public class IOUtil { * @throws RuntimeException is the property java.io.tmpdir or the resulting temp directory is invalid * * @see PropertyAccess#getProperty(String, boolean, java.security.AccessControlContext) - * @see StaticContext#setContext(Context) + * @see StaticContext#init(Context, ClassLoader) * @see Context#getDir(String, int) */ public static File getTempRoot(AccessControlContext acc) diff --git a/src/java/jogamp/android/launcher/ActivityLauncher.java b/src/java/jogamp/android/launcher/ActivityLauncher.java index 04c898e..7e2d86d 100644 --- a/src/java/jogamp/android/launcher/ActivityLauncher.java +++ b/src/java/jogamp/android/launcher/ActivityLauncher.java @@ -32,7 +32,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import android.app.Activity; -import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.widget.TextView; @@ -42,7 +41,7 @@ public class ActivityLauncher extends Activity { static final String TAG = "NEWTLauncherActivity"; TextView tv = null; Method mOnCreate, mOnDestroy, mOnPause, mOnRestart, mOnResume, - mOnStart, mOnStop, mSetIsInvokedByExternalActivity; + mOnStart, mOnStop, mSetRootActivity; Class activityClazz = null; Object activityObject = null; @@ -55,7 +54,7 @@ public class ActivityLauncher extends Activity { final LauncherUtil.DataSet data = LauncherUtil.DataSet.create(uri); data.setSystemProperties(); - ClassLoader cl = ClassLoaderUtil.createJogampClassLoaderSingleton(this, data.getPackages()); + ClassLoader cl = ClassLoaderUtil.createClassLoader(this, data.getPackages(), false); if(null != cl) { try { activityClazz = Class.forName(data.getActivityName(), true, cl); @@ -69,7 +68,7 @@ public class ActivityLauncher extends Activity { mOnResume = activityClazz.getMethod("onResume"); mOnStart = activityClazz.getMethod("onStart"); mOnStop = activityClazz.getMethod("onStop"); - mSetIsInvokedByExternalActivity = activityClazz.getMethod("setIsInvokedByExternalActivity", Activity.class); + mSetRootActivity = activityClazz.getMethod("setRootActivity", Activity.class); } catch (Exception e) { Log.d(TAG, "error: "+e, e); throw new RuntimeException(e); @@ -78,13 +77,13 @@ public class ActivityLauncher extends Activity { if( null == mOnCreate || null == mOnDestroy || null == mOnPause || null == mOnRestart || null == mOnResume || - null == mSetIsInvokedByExternalActivity ) { + null == mSetRootActivity ) { RuntimeException e = new RuntimeException("XXX - incomplete method set"); Log.d(TAG, "error: "+e, e); throw e; } - callMethod(activityObject, mSetIsInvokedByExternalActivity, this); + callMethod(activityObject, mSetRootActivity, this); callMethod(activityObject, mOnCreate, savedInstanceState); Log.d(TAG, "onCreate - X"); diff --git a/src/java/jogamp/android/launcher/AssetDexClassLoader.java b/src/java/jogamp/android/launcher/AssetDexClassLoader.java new file mode 100644 index 0000000..ec3ae6c --- /dev/null +++ b/src/java/jogamp/android/launcher/AssetDexClassLoader.java @@ -0,0 +1,29 @@ +package jogamp.android.launcher; + +import java.net.URL; + +import android.util.Log; + +import dalvik.system.DexClassLoader; + +public class AssetDexClassLoader extends DexClassLoader { + private static final boolean DEBUG = false; + private static final String assets_folder = "assets/"; + + public AssetDexClassLoader(String dexPath, String dexOutputDir, String libPath, ClassLoader parent) { + super(dexPath, dexOutputDir, libPath, parent); + if(DEBUG) { + Log.d(AssetDexClassLoader.class.getSimpleName(), "ctor: dexPath " + dexPath + ", dexOutputDir " + dexOutputDir + ", libPath " + libPath + ", parent " + parent); + } + } + + @Override + public URL findResource(String name) { + final String assetName = name.startsWith(assets_folder) ? name : assets_folder + name ; + URL url = super.findResource(assetName); + if(DEBUG) { + Log.d(AssetDexClassLoader.class.getSimpleName(), "findResource: " + name + " -> " + assetName + " -> " + url); + } + return url; + } +} diff --git a/src/java/jogamp/android/launcher/ClassLoaderUtil.java b/src/java/jogamp/android/launcher/ClassLoaderUtil.java index 319a0fd..85c0b94 100644 --- a/src/java/jogamp/android/launcher/ClassLoaderUtil.java +++ b/src/java/jogamp/android/launcher/ClassLoaderUtil.java @@ -29,89 +29,83 @@ package jogamp.android.launcher; import java.io.File; -import java.lang.ref.WeakReference; -import java.lang.reflect.Field; -import java.util.HashMap; +import java.util.Arrays; import java.util.Iterator; import java.util.List; -import android.app.Activity; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.util.Log; -import dalvik.system.DexClassLoader; public class ClassLoaderUtil { private static final String TAG = "JogampClassLoader"; - public static final String packageGlueGen = "com.jogamp.common"; - public static final String packageJogl = "javax.media.opengl"; // FIXME: a 'performance' hack + // FIXME: Need to generalize this .. (Note: native lib resources must be cached!) + private static final String[] packagesJogAmp = { "com.jogamp.common", "javax.media.opengl" }; + private static ClassLoader jogAmpClassLoader = null; - public static final String dexPathName= "jogampDex"; + // location where optimized dex files will be written + private static final String dexPathName= "jogampDex"; + private static File dexPath = null; private static LauncherTempFileCache tmpFileCache = null; - private static ClassLoader jogAmpClassLoader = null; - /** - * - * @param ctx - * @param userPackageNames list of user package names, the last entry shall reflect the Activity - * @return - */ - public static synchronized ClassLoader createJogampClassLoaderSingleton(Context ctx, List userPackageNames) { - if(null==jogAmpClassLoader) { - if(null!=tmpFileCache) { - throw new InternalError("XXX0"); - } + private static final String PATH_SEP = "/"; + private static final String ELEM_SEP = ":"; + + private static synchronized void init(Context ctx) { + if(null == tmpFileCache) { if(!LauncherTempFileCache.initSingleton(ctx)) { throw new InternalError("TempFileCache initialization error"); } tmpFileCache = new LauncherTempFileCache(); if(!tmpFileCache.isValid()) { throw new InternalError("TempFileCache instantiation error"); - } - final ApplicationInfo ai = ctx.getApplicationInfo(); - Log.d(TAG, "S: userPackageName: "+userPackageNames+", dataDir: "+ai.dataDir+", nativeLibraryDir: "+ai.nativeLibraryDir); - - final String appDir = new File(ai.dataDir).getParent(); - final String libSub = ai.nativeLibraryDir.substring(ai.nativeLibraryDir.lastIndexOf('/')+1); - Log.d(TAG, "S: appDir: "+appDir+", libSub: "+libSub); - - final String libPathName = appDir + "/" + packageGlueGen + "/" + libSub + "/:" + - appDir + "/" + packageJogl + "/" + libSub + "/" ; - Log.d(TAG, "S: libPath: "+libPathName); - - String apkGlueGen = null; - String apkJogl = null; - - try { - apkGlueGen = ctx.getPackageManager().getApplicationInfo(packageGlueGen,0).sourceDir; - apkJogl = ctx.getPackageManager().getApplicationInfo(packageJogl,0).sourceDir; - } catch (PackageManager.NameNotFoundException e) { - Log.d(TAG, "error: "+e, e); - } - if(null == apkGlueGen || null == apkJogl) { - Log.d(TAG, "not found: gluegen <"+apkGlueGen+">, jogl <"+apkJogl+">"); - return null; - } - - final String cp = apkGlueGen + ":" + apkJogl ; - Log.d(TAG, "jogamp cp: " + cp); - - final File dexPath = new File(tmpFileCache.getTempDir(), dexPathName); + } + dexPath = new File(tmpFileCache.getTempDir(), dexPathName); Log.d(TAG, "jogamp dexPath: " + dexPath.getAbsolutePath()); dexPath.mkdir(); - jogAmpClassLoader = new DexClassLoader(cp, dexPath.getAbsolutePath(), libPathName, ctx.getClassLoader()); - } else { - if(null==tmpFileCache) { - throw new InternalError("XXX1"); - } + } + } + + public static synchronized ClassLoader createClassLoader(Context ctx, List userPackageNames, boolean addLibPath) { + return createClassLoader(ctx, userPackageNames, addLibPath, null); + } + + public static synchronized ClassLoader createClassLoader(Context ctx, List userPackageNames, + boolean addLibPath, ClassLoader parent) { + init(ctx); + + if(null==jogAmpClassLoader) { + jogAmpClassLoader = createClassLoaderImpl(ctx, Arrays.asList(packagesJogAmp), true, + (null != parent ) ? parent : ctx.getClassLoader()); } + parent = jogAmpClassLoader; - StringBuilder userAPKs = new StringBuilder(); - int numUserAPKs = 0; + return createClassLoaderImpl(ctx, userPackageNames, addLibPath, jogAmpClassLoader); + } + + /** + * + * @param ctx + * @param userPackageNames list of user package names, the last entry shall reflect the Activity + * @return + */ + private static synchronized ClassLoader createClassLoaderImpl(Context ctx, List userPackageNames, + boolean addLibPath, ClassLoader parent) { + + + final ApplicationInfo appInfo = ctx.getApplicationInfo(); + final String appDir = new File(appInfo.dataDir).getParent(); + final String libSub = appInfo.nativeLibraryDir.substring(appInfo.nativeLibraryDir.lastIndexOf('/')+1); + Log.d(TAG, "S: userPackageName: "+userPackageNames+"; appName "+appInfo.name+", appDir "+appDir+", nativeLibraryDir: "+appInfo.nativeLibraryDir+"; dataDir: "+appInfo.dataDir+", libSub "+libSub); + + StringBuilder apks = new StringBuilder(); + StringBuilder libs = new StringBuilder(); + int apkCount = 0; String lastUserPackageName = null; // the very last one reflects the Activity + for(Iterator i=userPackageNames.iterator(); i.hasNext(); ) { lastUserPackageName = i.next(); String userAPK = null; @@ -121,32 +115,33 @@ public class ClassLoaderUtil { Log.d(TAG, "error: "+e, e); } if(null != userAPK) { - numUserAPKs++; - if(numUserAPKs>0) { - userAPKs.append(":"); + if(apkCount>0) { + apks.append(ELEM_SEP); + if(addLibPath) { + libs.append(ELEM_SEP); + } + } + apks.append(userAPK); + if(addLibPath) { + libs.append(appDir).append(PATH_SEP).append(lastUserPackageName).append(PATH_SEP).append(libSub).append(PATH_SEP); } - userAPKs.append(userAPK); Log.d(TAG, "APK found: <"+lastUserPackageName+"> -> <"+userAPK+">"); + apkCount++; } else { Log.d(TAG, "APK not found: <"+lastUserPackageName+">"); } } - if( userPackageNames.size()!=numUserAPKs ) { + if( userPackageNames.size()!=apkCount ) { Log.d(TAG, "APKs incomplete, abort"); return null; } - final String userAPKs_s = userAPKs.toString(); - Log.d(TAG, "user cp: " + userAPKs_s); - final File dexPath = new File(tmpFileCache.getTempDir(), lastUserPackageName); - Log.d(TAG, "user dexPath: " + dexPath.getAbsolutePath()); - dexPath.mkdir(); - ClassLoader cl = new DexClassLoader(userAPKs_s, dexPath.getAbsolutePath(), null, jogAmpClassLoader); - Log.d(TAG, "cl: " + cl); - - return cl; + // return new TraceDexClassLoader(apks.toString(), dexPath.getAbsolutePath(), libs.toString(), parent); + return new AssetDexClassLoader(apks.toString(), dexPath.getAbsolutePath(), libs.toString(), parent); } + /*** + * public boolean setAPKClassLoader(String activityPackageName, ClassLoader classLoader) { try { @@ -188,6 +183,7 @@ public class ClassLoaderUtil { } } return null; - } + } + */ } diff --git a/src/java/jogamp/android/launcher/LauncherMain.java b/src/java/jogamp/android/launcher/LauncherMain.java index bbdee1d..eb56385 100644 --- a/src/java/jogamp/android/launcher/LauncherMain.java +++ b/src/java/jogamp/android/launcher/LauncherMain.java @@ -51,7 +51,7 @@ public class LauncherMain { userPackageNames.add("com.jogamp.opengl.test"); Looper.prepareMainLooper(); ActivityGroup activityGroup = new ActivityGroup(true); - ClassLoader cl = ClassLoaderUtil.createJogampClassLoaderSingleton(activityGroup, getUserPackageNames()); + ClassLoader cl = ClassLoaderUtil.createClassLoader(activityGroup, getUserPackageNames(), false); if(null != cl) { Class activityClazz = Class.forName(getUserActivityName(), true, cl); Intent intent = new Intent(activityGroup, activityClazz); diff --git a/src/java/jogamp/android/launcher/TraceDexClassLoader.java b/src/java/jogamp/android/launcher/TraceDexClassLoader.java new file mode 100644 index 0000000..0b00489 --- /dev/null +++ b/src/java/jogamp/android/launcher/TraceDexClassLoader.java @@ -0,0 +1,27 @@ +package jogamp.android.launcher; + +import java.net.URL; + +import android.util.Log; + +import dalvik.system.DexClassLoader; + +public class TraceDexClassLoader extends DexClassLoader { + private static final boolean DEBUG = false; + + public TraceDexClassLoader(String dexPath, String dexOutputDir, String libPath, ClassLoader parent) { + super(dexPath, dexOutputDir, libPath, parent); + if(DEBUG) { + Log.d(TraceDexClassLoader.class.getSimpleName(), "ctor: dexPath " + dexPath + ", dexOutputDir " + dexOutputDir + ", libPath " + libPath + ", parent " + parent); + } + } + + @Override + public URL findResource(String name) { + final URL url = super.findResource(name); + if(DEBUG) { + Log.d(TraceDexClassLoader.class.getSimpleName(), "findResource: " + name + " -> " + url); + } + return url; + } +} diff --git a/src/java/jogamp/common/os/android/GluegenVersionActivity.java b/src/java/jogamp/common/os/android/GluegenVersionActivity.java index 8ea10e2..5923e66 100644 --- a/src/java/jogamp/common/os/android/GluegenVersionActivity.java +++ b/src/java/jogamp/common/os/android/GluegenVersionActivity.java @@ -27,6 +27,7 @@ */ package jogamp.common.os.android; + import com.jogamp.common.GlueGenVersion; import com.jogamp.common.os.Platform; import com.jogamp.common.util.VersionUtil; @@ -43,7 +44,7 @@ public class GluegenVersionActivity extends Activity { public void onCreate(Bundle savedInstanceState) { Log.d(MD.TAG, "onCreate - S"); super.onCreate(savedInstanceState); - StaticContext.setContext(this.getApplicationContext()); + StaticContext.init(this.getApplicationContext()); tv = new TextView(this); tv.setText(VersionUtil.getPlatformInfo()+Platform.NEWLINE+GlueGenVersion.getInstance()+Platform.NEWLINE+Platform.NEWLINE); setContentView(tv); @@ -109,7 +110,7 @@ public class GluegenVersionActivity extends Activity { tv.append("> destroyed"+Platform.NEWLINE); } Log.d(MD.TAG, "onDestroy - x"); - StaticContext.setContext(null); + StaticContext.clear(); super.onDestroy(); Log.d(MD.TAG, "onDestroy - X"); } diff --git a/src/java/jogamp/common/os/android/StaticContext.java b/src/java/jogamp/common/os/android/StaticContext.java index 56f8f13..cb55e65 100644 --- a/src/java/jogamp/common/os/android/StaticContext.java +++ b/src/java/jogamp/common/os/android/StaticContext.java @@ -32,13 +32,21 @@ import android.util.Log; public class StaticContext { private static Context context = null; + private static boolean DEBUG = false; - public static final synchronized void setContext(Context ctx) { - if(DEBUG) Log.d(MD.TAG, "setContext("+ctx+")"); + public static final synchronized void init(Context ctx) { + if(null != context) { + throw new RuntimeException("Context already set"); + } + if(DEBUG) { Log.d(MD.TAG, "init("+ctx+")"); } context = ctx; } + public static final synchronized void clear() { + if(DEBUG) { Log.d(MD.TAG, "clear()"); } + context = null; + } public static final synchronized Context getContext() { return context; - } + } } -- cgit v1.2.3