/**
* 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.
*
* The order of the Uri segments (any arguments) is preserved.
*
*/
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 launch
*/
public static final String SCHEME = "launch";
/** The host jogamp.org
*/
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 args = new ArrayList();
/**
* Returns the default {@link LauncherUtil#LAUNCH_ACTIVITY_NORMAL} action.
*
* Should be overridden for other action, eg. {@link LauncherUtil#LAUNCH_ACTIVITY_TRANSPARENT}.
*
*/
public String getAction() { return LAUNCH_ACTIVITY_NORMAL; }
/**
* Returns the properties, which are being propagated to the target activity.
*
* Maybe be used to set custom properties.
*
*/
public final OrderedProperties getProperties() { return props; }
/**
* Returns the commandline arguments, which are being propagated to the target activity.
*
* Maybe be used to set custom commandline arguments.
*
*/
public final ArrayList 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 true
, 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 getUsrPackages();
/** Return a list of required system packages w/ native libraries, may return null or a zero sized list. */
public abstract List 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 map = new HashMap();
ArrayList keyList = new ArrayList();
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 argKeys = props.keyList.iterator();
while(argKeys.hasNext()) {
final String key = argKeys.next();
setProperty(key, props.map.get(key));
}
}
public final void setSystemProperties() {
final Iterator argKeys = keyList.iterator();
while(argKeys.hasNext()) {
final String key = argKeys.next();
System.setProperty(key, map.get(key));
}
}
public final void clearSystemProperties() {
final Iterator argKeys = keyList.iterator();
while(argKeys.hasNext()) {
System.clearProperty(argKeys.next());
}
}
public final String getProperty(final String key) { return map.get(key); }
public final Map getProperties() { return map; }
/** Returns the list of property keys in the order, as they were added. */
public final List getPropertyKeys() { return keyList; }
}
/**
* Data set to transfer from and to launch URI consisting out of:
*
* - system packages w/ native libraries used on Android, which may use a cached ClassLoader, see {@link DataSet#getSysPackages()}.
* - user packages w/o native libraries used on Android, which do not use a cached ClassLoader, see {@link DataSet#getUsrPackages()}.
* - activity name, used to launch an Android activity, see {@link DataSet#getActivityName()}.
* - properties, which will be added to the system properties, see {@link DataSet#getProperties()}.
* - arguments, used to launch a class main-entry, see {@link DataSet#getArguments()}.
*
* {@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 sysPackages = new ArrayList();
ArrayList usrPackages = new ArrayList();
OrderedProperties properties = new OrderedProperties();
ArrayList arguments = new ArrayList();
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 plist) {
sysPackages.addAll(plist);
}
public final List getSysPackages() { return sysPackages; }
public final void addUsrPackage(final String p) {
usrPackages.add(p);
}
public final void addAllUsrPackages(final List plist) {
usrPackages.addAll(plist);
}
public final List 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 getPropertyKeys() { return properties.getPropertyKeys(); }
public final void addArgument(final String arg) { arguments.add(arg); }
public final void addAllArguments(final List args) {
arguments.addAll(args);
}
public final ArrayList 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; i0) {
if( needsQMark ) {
sb.append(QMARK);
needsQMark = false;
}
for(int i=0; i 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 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 -> "+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);
}
}