/** * Copyright 2012 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package jogamp.android.launcher; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; /** * Helper class to parse Uri's and programmatically add package names and properties to create an Uri or Intend. * <p> * The order of the Uri segments (any arguments) is preserved. * </p> */ public class LauncherUtil { /** Default launch mode. */ public static final String LAUNCH_ACTIVITY_NORMAL = "org.jogamp.launcher.action.LAUNCH_ACTIVITY_NORMAL"; /** Transparent launch mode. Note: This seems to be required to achieve translucency, since setTheme(..) doesn't work. */ public static final String LAUNCH_ACTIVITY_TRANSPARENT = "org.jogamp.launcher.action.LAUNCH_ACTIVITY_TRANSPARENT"; /** FIXME: TODO */ public static final String LAUNCH_MAIN = "org.jogamp.launcher.action.LAUNCH_MAIN"; /** FIXME: TODO */ public static final String LAUNCH_JUNIT = "org.jogamp.launcher.action.LAUNCH_JUNIT"; /** The protocol <code>launch</code> */ public static final String SCHEME = "launch"; /** The host <code>jogamp.org</code> */ public static final String HOST = "jogamp.org"; static final String SYS_PKG = "sys"; static final String USR_PKG = "pkg"; static final String ARG = "arg"; public static abstract class BaseActivityLauncher extends Activity { final OrderedProperties props = new OrderedProperties(); final ArrayList<String> args = new ArrayList<String>(); /** * Returns the default {@link LauncherUtil#LAUNCH_ACTIVITY_NORMAL} action. * <p> * Should be overridden for other action, eg. {@link LauncherUtil#LAUNCH_ACTIVITY_TRANSPARENT}. * </p> */ public String getAction() { return LAUNCH_ACTIVITY_NORMAL; } /** * Returns the properties, which are being propagated to the target activity. * <p> * Maybe be used to set custom properties. * </p> */ public final OrderedProperties getProperties() { return props; } /** * Returns the commandline arguments, which are being propagated to the target activity. * <p> * Maybe be used to set custom commandline arguments. * </p> */ public final ArrayList<String> getArguments() { return args; } /** Custom initialization hook which can be overriden to setup data, e.g. fill the properties retrieved by {@link #getProperties()}. */ public void init() { } /** Returns true if this launcher activity shall end after starting the downstream activity. Defaults to <code>true</code>, override to change behavior. */ public boolean finishAfterDelegate() { return true; } /** Must return the downstream Activity class name */ public abstract String getActivityName(); /** Must return a list of required user packages, at least one containing the activity. */ public abstract List<String> getUsrPackages(); /** Return a list of required system packages w/ native libraries, may return null or a zero sized list. */ public abstract List<String> getSysPackages(); @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); init(); final DataSet data = new DataSet(); data.setActivityName(getActivityName()); data.addAllSysPackages(getSysPackages()); data.addAllUsrPackages(getUsrPackages()); data.addAllProperties(props); data.addAllArguments(args); final Intent intent = LauncherUtil.getIntent(getAction(), data); Log.d(getClass().getSimpleName(), "Launching Activity: "+intent); startActivity (intent); if(finishAfterDelegate()) { finish(); // done } } } public static class OrderedProperties { HashMap<String, String> map = new HashMap<String, String>(); ArrayList<String> keyList = new ArrayList<String>(); public final void setProperty(final String key, final String value) { if(key.equals(SYS_PKG)) { throw new IllegalArgumentException("Illegal property key, '"+SYS_PKG+"' is reserved"); } if(key.equals(USR_PKG)) { throw new IllegalArgumentException("Illegal property key, '"+USR_PKG+"' is reserved"); } if(key.equals(ARG)) { throw new IllegalArgumentException("Illegal property key, '"+ARG+"' is reserved"); } final String oval = map.put(key, value); if(null != oval) { map.put(key, oval); // restore throw new IllegalArgumentException("Property overwriting not allowed: "+key+": "+oval+" -> "+value); } keyList.add(key); // new key } public final void addAll(final OrderedProperties props) { final Iterator<String> argKeys = props.keyList.iterator(); while(argKeys.hasNext()) { final String key = argKeys.next(); setProperty(key, props.map.get(key)); } } public final void setSystemProperties() { final Iterator<String> argKeys = keyList.iterator(); while(argKeys.hasNext()) { final String key = argKeys.next(); System.setProperty(key, map.get(key)); } } public final void clearSystemProperties() { final Iterator<String> argKeys = keyList.iterator(); while(argKeys.hasNext()) { System.clearProperty(argKeys.next()); } } public final String getProperty(final String key) { return map.get(key); } public final Map<String, String> getProperties() { return map; } /** Returns the list of property keys in the order, as they were added. */ public final List<String> getPropertyKeys() { return keyList; } } /** * Data set to transfer from and to launch URI consisting out of: * <ul> * <li>system packages w/ native libraries used on Android, which may use a cached ClassLoader, see {@link DataSet#getSysPackages()}.</li> * <li>user packages w/o native libraries used on Android, which do not use a cached ClassLoader, see {@link DataSet#getUsrPackages()}.</li> * <li>activity name, used to launch an Android activity, see {@link DataSet#getActivityName()}.</li> * <li>properties, which will be added to the system properties, see {@link DataSet#getProperties()}.</li> * <li>arguments, used to launch a class main-entry, see {@link DataSet#getArguments()}.</li> * </ul> * {@link DataSet#getUri()} returns a URI representation of all components. */ public static class DataSet { static final char SLASH = '/'; static final char QMARK = '?'; static final char AMPER = '&'; static final char ASSIG = '='; static final String COLSLASH2 = "://"; static final String EMPTY = ""; String activityName = null; ArrayList<String> sysPackages = new ArrayList<String>(); ArrayList<String> usrPackages = new ArrayList<String>(); OrderedProperties properties = new OrderedProperties(); ArrayList<String> arguments = new ArrayList<String>(); public final void setActivityName(final String name) { activityName = name; } public final String getActivityName() { return activityName; } public final void addSysPackage(final String p) { sysPackages.add(p); } public final void addAllSysPackages(final List<String> plist) { sysPackages.addAll(plist); } public final List<String> getSysPackages() { return sysPackages; } public final void addUsrPackage(final String p) { usrPackages.add(p); } public final void addAllUsrPackages(final List<String> plist) { usrPackages.addAll(plist); } public final List<String> getUsrPackages() { return usrPackages; } public final void setProperty(final String key, final String value) { properties.setProperty(key, value); } public final void addAllProperties(final OrderedProperties props) { properties.addAll(props); } public final void setSystemProperties() { properties.setSystemProperties(); } public final void clearSystemProperties() { properties.clearSystemProperties(); } public final String getProperty(final String key) { return properties.getProperty(key); } public final OrderedProperties getProperties() { return properties; } public final List<String> getPropertyKeys() { return properties.getPropertyKeys(); } public final void addArgument(final String arg) { arguments.add(arg); } public final void addAllArguments(final List<String> args) { arguments.addAll(args); } public final ArrayList<String> getArguments() { return arguments; } public final Uri getUri() { final StringBuilder sb = new StringBuilder(); sb.append(SCHEME).append(COLSLASH2).append(HOST).append(SLASH).append(getActivityName()); boolean needsQMark = true; boolean needsSep = false; if(sysPackages.size()>0) { if( needsQMark ) { sb.append(QMARK); needsQMark = false; } for(int i=0; i<sysPackages.size(); i++) { if(needsSep) { sb.append(AMPER); } sb.append(SYS_PKG).append(ASSIG).append(sysPackages.get(i)); needsSep = true; } } if(usrPackages.size()>0) { if( needsQMark ) { sb.append(QMARK); needsQMark = false; } for(int i=0; i<usrPackages.size(); i++) { if(needsSep) { sb.append(AMPER); } sb.append(USR_PKG).append(ASSIG).append(usrPackages.get(i)); needsSep = true; } } final Iterator<String> propKeys = properties.keyList.iterator(); while(propKeys.hasNext()) { if( needsQMark ) { sb.append(QMARK); needsQMark = false; } if(needsSep) { sb.append(AMPER); } final String key = propKeys.next(); sb.append(key).append(ASSIG).append(properties.map.get(key)); needsSep = true; } final Iterator<String> args = arguments.iterator(); while(args.hasNext()) { if( needsQMark ) { sb.append(QMARK); needsQMark = false; } if(needsSep) { sb.append(AMPER); } sb.append(ARG).append(ASSIG).append(args.next()); needsSep = true; } return Uri.parse(sb.toString()); } public static final DataSet create(final Uri uri) { if(!uri.getScheme().equals(SCHEME)) { return null; } if(!uri.getHost().equals(HOST)) { return null; } final DataSet data = new DataSet(); { String an = uri.getPath(); if(SLASH == an.charAt(0)) { an = an.substring(1); } if(SLASH == an.charAt(an.length()-1)) { an = an.substring(0, an.length()-1); } data.setActivityName(an); } final String q = uri.getQuery(); final int q_l = null != q ? q.length() : -1; int q_e = -1; while(q_e < q_l) { final int q_b = q_e + 1; // next term q_e = q.indexOf(AMPER, q_b); if(0 == q_e) { // single separator continue; } if(0 > q_e) { // end q_e = q_l; } // n-part final String part = q.substring(q_b, q_e); final int assignment = part.indexOf(ASSIG); if(0 < assignment) { // assignment final String k = part.substring(0, assignment); final String v = part.substring(assignment+1); if(k.equals(SYS_PKG)) { if(v.length()==0) { throw new IllegalArgumentException("Empty package name: part <"+part+">, query <"+q+"> of "+uri); } data.addSysPackage(v); } else if(k.equals(USR_PKG)) { if(v.length()==0) { throw new IllegalArgumentException("Empty package name: part <"+part+">, query <"+q+"> of "+uri); } data.addUsrPackage(v); } else if(k.equals(ARG)) { if(v.length()==0) { throw new IllegalArgumentException("Empty argument name: part <"+part+">, query <"+q+"> of "+uri); } data.addArgument(v); } else { data.setProperty(k, v); } } else { // property key only if( part.equals(USR_PKG) || part.equals(ARG) ) { throw new IllegalArgumentException("Reserved key <"+part+"> in query <"+q+"> of "+uri); } data.setProperty(part, EMPTY); } } data.validate(); return data; } public final void validate() { if(null == activityName) { throw new RuntimeException("Activity is not NULL"); } } } public final static Intent getIntent(final String action, final DataSet data) { data.validate(); return new Intent(action, data.getUri()); } public static void main(String[] args) { if(args.length==0) { args = new String[] { SCHEME+"://"+HOST+"/com.jogamp.TestActivity?"+SYS_PKG+"=jogamp.pack1&"+SYS_PKG+"=javax.pack2&"+USR_PKG+"=com.jogamp.pack3&"+USR_PKG+"=com.jogamp.pack4&jogamp.common.debug=true&com.jogamp.test=false", SCHEME+"://"+HOST+"/com.jogamp.TestActivity?"+SYS_PKG+"=jogamp.pack1&jogamp.common.debug=true&com.jogamp.test=false", SCHEME+"://"+HOST+"/com.jogamp.TestActivity?"+USR_PKG+"=jogamp.pack1&jogamp.common.debug=true&com.jogamp.test=false", SCHEME+"://"+HOST+"/com.jogamp.TestActivity?"+USR_PKG+"=jogamp.pack1&"+USR_PKG+"=com.jogamp.pack2", SCHEME+"://"+HOST+"/com.jogamp.TestActivity?"+USR_PKG+"=jogamp.pack1&"+USR_PKG+"=javax.pack2&"+USR_PKG+"=com.jogamp.pack3&jogamp.common.debug=true&com.jogamp.test=false&"+ARG+"=arg1&"+ARG+"=arg2=arg2value&"+ARG+"=arg3", SCHEME+"://"+HOST+"/com.jogamp.TestActivity?"+USR_PKG+"=jogamp.pack1&jogamp.common.debug=true&com.jogamp.test=false&"+ARG+"=arg1&"+ARG+"=arg2=arg2value&"+ARG+"=arg3", SCHEME+"://"+HOST+"/com.jogamp.TestActivity?"+USR_PKG+"=jogamp.pack1&"+ARG+"=arg1&"+ARG+"=arg2=arg2value&"+ARG+"=arg3" }; } int errors = 0; for(int i=0; i<args.length; i++) { final String uri_s = args[i]; final Uri uri0 = Uri.parse(uri_s); final DataSet data = DataSet.create(uri0); if(null == data) { errors++; System.err.println("Error: NULL JogAmpLauncherUtil: <"+uri_s+"> -> "+uri0+" -> NULL"); } else { final Uri uri1 = data.getUri(); if(!uri0.equals(uri1)) { errors++; System.err.println("Error: Not equal: <"+uri_s+"> -> "+uri0+" -> "+uri1); } else { System.err.println("OK: "+uri1); } } } System.err.println("LauncherUtil Self Test: Errors: "+errors); } }