1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
|
/**
* 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(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(String key, 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(OrderedProperties props) {
Iterator<String> argKeys = props.keyList.iterator();
while(argKeys.hasNext()) {
final String key = argKeys.next();
setProperty(key, props.map.get(key));
}
}
public final void setSystemProperties() {
Iterator<String> argKeys = keyList.iterator();
while(argKeys.hasNext()) {
final String key = argKeys.next();
System.setProperty(key, map.get(key));
}
}
public final void clearSystemProperties() {
Iterator<String> argKeys = keyList.iterator();
while(argKeys.hasNext()) {
System.clearProperty(argKeys.next());
}
}
public final String getProperty(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(String name) { activityName = name; }
public final String getActivityName() { return activityName; }
public final void addSysPackage(String p) {
sysPackages.add(p);
}
public final void addAllSysPackages(List<String> plist) {
sysPackages.addAll(plist);
}
public final List<String> getSysPackages() { return sysPackages; }
public final void addUsrPackage(String p) {
usrPackages.add(p);
}
public final void addAllUsrPackages(List<String> plist) {
usrPackages.addAll(plist);
}
public final List<String> getUsrPackages() { return usrPackages; }
public final void setProperty(String key, String value) {
properties.setProperty(key, value);
}
public final void addAllProperties(OrderedProperties props) {
properties.addAll(props);
}
public final void setSystemProperties() {
properties.setSystemProperties();
}
public final void clearSystemProperties() {
properties.clearSystemProperties();
}
public final String getProperty(String key) { return properties.getProperty(key); }
public final OrderedProperties getProperties() { return properties; }
public final List<String> getPropertyKeys() { return properties.getPropertyKeys(); }
public final void addArgument(String arg) { arguments.add(arg); }
public final void addAllArguments(List<String> args) {
arguments.addAll(args);
}
public final ArrayList<String> getArguments() { return arguments; }
public final Uri getUri() {
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;
}
}
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;
}
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(Uri uri) {
if(!uri.getScheme().equals(SCHEME)) {
return null;
}
if(!uri.getHost().equals(HOST)) {
return null;
}
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) {
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(String action, 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++) {
String uri_s = args[i];
Uri uri0 = Uri.parse(uri_s);
DataSet data = DataSet.create(uri0);
if(null == data) {
errors++;
System.err.println("Error: NULL JogAmpLauncherUtil: <"+uri_s+"> -> "+uri0+" -> NULL");
} else {
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);
}
}
|