ruboto 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,329 @@
1
+ /*
2
+ * Copyright 2013 ThinkFree
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package org.ruboto;
18
+
19
+ import android.app.Activity;
20
+ import android.app.ProgressDialog;
21
+ import android.content.Context;
22
+ import android.content.Intent;
23
+ import android.content.res.AssetManager;
24
+ import android.os.Build;
25
+ import android.util.Log;
26
+
27
+ import java.io.BufferedInputStream;
28
+ import java.io.BufferedOutputStream;
29
+ import java.io.File;
30
+ import java.io.FileOutputStream;
31
+ import java.io.IOException;
32
+ import java.util.ArrayList;
33
+ import java.util.Arrays;
34
+ import java.util.Observable;
35
+ import java.util.Observer;
36
+
37
+ import dalvik.system.PathClassLoader;
38
+
39
+ /**
40
+ * Easy class loading for multi-dex Android application.
41
+ *
42
+ * 1) call validateClassPath() from Application.onCreate()
43
+ * 2) check dexOptRequired then addAllJARsAssets() on non-UI thread.
44
+ *
45
+ * @author Alan Goo
46
+ */
47
+ public class DexDex {
48
+ public static final String DIR_SUBDEX = "dexdex";
49
+
50
+ private static final String TAG = "DexDex";
51
+
52
+ private static final int SDK_INT_ICS = 14;
53
+
54
+ private static final int SDK_INT_KITKAT = 19;
55
+
56
+ private static final int BUF_SIZE = 8 * 1024;
57
+ public static final int PROGRESS_COMPLETE = 100;
58
+
59
+ private static ArrayList<String> theAppended = new ArrayList<String>();
60
+
61
+ public static boolean debug = false;
62
+
63
+ public static boolean dexOptRequired = false;
64
+
65
+ private static Activity uiBlockedActivity = null;
66
+
67
+ /**
68
+ * just reuse existing interface for convenience
69
+ * @hide
70
+ */
71
+ public static Observer dexOptProgressObserver = null;
72
+
73
+ private DexDex() {
74
+ // do not create an instance
75
+ }
76
+
77
+ private static boolean shouldDexOpt(File apkFile, File dexDir, String[] names) {
78
+ boolean result = shouldDexOptImpl(apkFile, dexDir, names);
79
+ if(debug) {
80
+ Log.d(TAG, "shouldDexOpt(" + apkFile + "," + dexDir + "," + Arrays.deepToString(names) + ") => " + result
81
+ + " on " + Thread.currentThread());
82
+ }
83
+ return result;
84
+ }
85
+
86
+ private static boolean shouldDexOptImpl(File apkFile, File dexDir, String[] names) {
87
+ long apkDate = apkFile.lastModified();
88
+ // APK upgrade case
89
+ if(debug) {
90
+ Log.d(TAG, "APK Date : " + apkDate + " ,dexDir date : " + dexDir.lastModified());
91
+ }
92
+ if (apkDate > dexDir.lastModified()) {
93
+ return true;
94
+ }
95
+ // clean install (or crash during install) case
96
+ for (int i = 0; i < names.length; i++) {
97
+ String name = names[i];
98
+ File dexJar = new File(dexDir, name);
99
+ if (dexJar.exists()) {
100
+ if (dexJar.lastModified() < apkDate) {
101
+ return true;
102
+ }
103
+ } else {
104
+ return true;
105
+ }
106
+ }
107
+ return false;
108
+ }
109
+
110
+ /**
111
+ * Should be called from <code>Application.onCreate()</code>.
112
+ * it returns quickly with little disk I/O.
113
+ */
114
+ public static void validateClassPath(final Context app) {
115
+ try {
116
+ String[] arrJars = createSubDexList(app);
117
+ if(debug) {
118
+ Log.d(TAG, "validateClassPath : " + Arrays.deepToString(arrJars));
119
+ }
120
+ File apkFile = new File(app.getApplicationInfo().sourceDir);
121
+ final File dexDir = app.getDir(DIR_SUBDEX, Context.MODE_PRIVATE); // this API creates the directory if not exist
122
+ dexOptRequired = shouldDexOpt(apkFile, dexDir, arrJars);
123
+ if (dexOptRequired) {
124
+ Thread dexOptThread = new Thread("DexDex - DexOpting for " + Arrays.deepToString(arrJars)) {
125
+ @Override
126
+ public void run() {
127
+ DexDex.addAllJARsInAssets(app);
128
+ // finished
129
+ dexOptRequired = false;
130
+
131
+ if(dexOptProgressObserver!=null) {
132
+ dexOptProgressObserver.update(null, PROGRESS_COMPLETE);
133
+ dexOptProgressObserver = null;
134
+ }
135
+
136
+ if (uiBlockedActivity != null) {
137
+ uiBlockedActivity.runOnUiThread(new Runnable() {
138
+ @Override
139
+ public void run() {
140
+ // FIXME(uwe): Simplify when we stop supporting android-11
141
+ // if (Build.VERSION.SDK_INT < 11) {
142
+ Intent callerIntent = uiBlockedActivity.getIntent();
143
+ uiBlockedActivity.finish();
144
+ uiBlockedActivity.startActivity(callerIntent);
145
+ // } else {
146
+ // uiBlockedActivity.recreate();
147
+ // }
148
+ // EMXIF
149
+ uiBlockedActivity = null;
150
+ }
151
+ });
152
+ }
153
+ }
154
+ };
155
+ dexOptThread.start();
156
+ } else {
157
+ // all dex JAR are stable
158
+ appendOdexesToClassPath(app, dexDir, arrJars);
159
+ }
160
+ if(debug) {
161
+ Log.d(TAG, "validateClassPath - dexDir : " + dexDir);
162
+ }
163
+ } catch (IOException ex) {
164
+ throw new RuntimeException(ex);
165
+ }
166
+ }
167
+
168
+ /** find and append all JARs */
169
+ public static void addAllJARsInAssets(final Context cxt) {
170
+ try {
171
+ if(debug) {
172
+ Log.d(TAG, "addAllJARsInAssets on " + Thread.currentThread());
173
+ }
174
+ String[] arrJars = createSubDexList(cxt);
175
+ copyJarsFromAssets(cxt, arrJars);
176
+ } catch (IOException e) {
177
+ throw new RuntimeException(e);
178
+ }
179
+ }
180
+
181
+ private static String[] createSubDexList(final Context cxt) throws IOException {
182
+ String[] files = cxt.getAssets().list("");
183
+ ArrayList<String> jarList = new ArrayList<String>();
184
+ for (int i = 0; i < files.length; i++) {
185
+ String jar = files[i];
186
+ if (jar.endsWith(".jar")) {
187
+ jarList.add(jar);
188
+ }
189
+ }
190
+ String[] arrJars = new String[jarList.size()];
191
+ jarList.toArray(arrJars);
192
+ return arrJars;
193
+ }
194
+
195
+ /**
196
+ * MUST be called on non-Main Thread
197
+ * @param names array of file names in 'assets' directory
198
+ */
199
+ public static void copyJarsFromAssets(final Context cxt, final String[] names) {
200
+ if(debug) {
201
+ Log.d(TAG, "copyJarsFromAssets(" + Arrays.deepToString(names) + ")");
202
+ }
203
+ final File dexDir = cxt.getDir(DIR_SUBDEX, Context.MODE_PRIVATE); // this API creates the directory if not exist
204
+ File apkFile = new File(cxt.getApplicationInfo().sourceDir);
205
+ // should copy subdex JARs to dexDir?
206
+ final boolean shouldInit = shouldDexOpt(apkFile, dexDir, names);
207
+ if (shouldInit) {
208
+ try {
209
+ copyToInternal(cxt, dexDir, names);
210
+ appendOdexesToClassPath(cxt, dexDir, names);
211
+ } catch (Exception e) {
212
+ e.printStackTrace();
213
+ throw new RuntimeException(e);
214
+ }
215
+ } else {
216
+ if (!inAppended(names)) {
217
+ appendOdexesToClassPath(cxt, dexDir, names);
218
+ }
219
+ }
220
+ }
221
+
222
+ /** checks if all <code>names</code> elements are in <code>theAppended</code> */
223
+ private static boolean inAppended(String[] names) {
224
+ for (int i = 0; i < names.length; i++) {
225
+ if (!theAppended.contains(names[i])) {
226
+ return false;
227
+ }
228
+ }
229
+ return true;
230
+ }
231
+
232
+ /**
233
+ * append DexOptimized dex files to the classpath.
234
+ * @return true if additional DexOpt is required, false otherwise.
235
+ */
236
+ private static boolean appendOdexesToClassPath(Context cxt, File dexDir, String[] names) {
237
+ // non-existing ZIP in classpath causes an exception on ICS
238
+ // so filter out the non-existent
239
+ String strDexDir = dexDir.getAbsolutePath();
240
+ ArrayList<String> jarPaths = new ArrayList<String>();
241
+ for (int i = 0; i < names.length; i++) {
242
+ String jarPath = strDexDir + '/' + names[i];
243
+ File f = new File(jarPath);
244
+ if (f.isFile()) {
245
+ jarPaths.add(jarPath);
246
+ }
247
+ }
248
+
249
+ String[] jarsOfDex = new String[jarPaths.size()];
250
+ jarPaths.toArray(jarsOfDex);
251
+
252
+ PathClassLoader pcl = (PathClassLoader) cxt.getClassLoader();
253
+ // do something dangerous
254
+ try {
255
+ if (Build.VERSION.SDK_INT < SDK_INT_ICS) {
256
+ FrameworkHack.appendDexListImplUnderICS(jarsOfDex, pcl, dexDir);
257
+ } else { // ICS+
258
+ boolean kitkatPlus = Build.VERSION.SDK_INT >= SDK_INT_KITKAT;
259
+ ArrayList<File> jarFiles = DexDex.strings2Files(jarsOfDex);
260
+ FrameworkHack.appendDexListImplICS(jarFiles, pcl, dexDir, kitkatPlus);
261
+ }
262
+ // update theAppended if succeeded to prevent duplicated classpath entry
263
+ for (String jarName : names) {
264
+ theAppended.add(jarName);
265
+ }
266
+ if(debug) {
267
+ Log.d(TAG, "appendOdexesToClassPath completed : " + pcl);
268
+ Log.d(TAG, "theAppended : " + theAppended);
269
+ }
270
+ } catch (Exception ex) {
271
+ throw new RuntimeException(ex);
272
+ }
273
+ return true;
274
+ }
275
+
276
+ private static void copyToInternal(Context cxt, File destDir, String[] names) {
277
+ String strDestDir = destDir.getAbsolutePath();
278
+ AssetManager assets = cxt.getAssets();
279
+ byte[] buf = new byte[BUF_SIZE];
280
+ for (int i = 0; i < names.length; i++) {
281
+ String name = names[i];
282
+ String destPath = strDestDir + '/' + name;
283
+
284
+ try {
285
+ BufferedInputStream bis = new BufferedInputStream(assets.open(name));
286
+ BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destPath));
287
+ int len;
288
+ while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
289
+ bos.write(buf, 0, len);
290
+ }
291
+ bis.close();
292
+ bos.close();
293
+ } catch (IOException ioe) {
294
+ ioe.printStackTrace();
295
+ }
296
+ }
297
+ destDir.setLastModified(System.currentTimeMillis());
298
+ }
299
+
300
+ private static ArrayList<File> strings2Files(String[] paths) {
301
+ ArrayList<File> result = new ArrayList<File>(paths.length);
302
+ int size = paths.length;
303
+ for (int i = 0; i < size; i++) {
304
+ result.add(new File(paths[i]));
305
+ }
306
+ return result;
307
+ }
308
+
309
+ public static void showUiBlocker(Activity startActivity, CharSequence title, CharSequence msg) {
310
+ if(debug) {
311
+ Log.d(TAG, "showUiBlocker() for " + startActivity);
312
+ }
313
+ uiBlockedActivity = startActivity;
314
+ final ProgressDialog progressDialog = new ProgressDialog(startActivity);
315
+ progressDialog.setMessage(msg);
316
+ progressDialog.setTitle(title);
317
+ progressDialog.setIndeterminate(true);
318
+ dexOptProgressObserver = new Observer() {
319
+ @Override
320
+ public void update(Observable observable, Object o) {
321
+ if(o==Integer.valueOf(PROGRESS_COMPLETE)) {
322
+ progressDialog.dismiss();
323
+ }
324
+ }
325
+ };
326
+
327
+ progressDialog.show();
328
+ }
329
+ }
@@ -0,0 +1,177 @@
1
+ /*
2
+ * Copyright 2013 ThinkFree
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package org.ruboto;
18
+
19
+ import android.util.Log;
20
+ import dalvik.system.DexFile;
21
+ import dalvik.system.PathClassLoader;
22
+
23
+ import java.io.File;
24
+ import java.lang.reflect.Array;
25
+ import java.lang.reflect.Field;
26
+ import java.lang.reflect.Method;
27
+ import java.util.ArrayList;
28
+ import java.util.Arrays;
29
+ import java.util.zip.ZipFile;
30
+
31
+ /**
32
+ * Collection of dirty codes. don't tell android team this mess.
33
+ * @author Alan Goo
34
+ */
35
+ public class FrameworkHack {
36
+ private static final String TAG = "FrameworkHack";
37
+ public static boolean debug = false;
38
+
39
+ private FrameworkHack() {
40
+ // do not create an instance
41
+ }
42
+
43
+ /**
44
+ * dalvik do not have security manager
45
+ */
46
+ private static void forceSet(Object obj, Field f, Object val) throws IllegalAccessException {
47
+ f.setAccessible(true);
48
+ f.set(obj, val);
49
+ }
50
+
51
+ private static Object forceGetFirst(Object obj, Field fArray) throws IllegalAccessException {
52
+ fArray.setAccessible(true);
53
+ Object[] vArray = (Object[]) fArray.get(obj);
54
+ return vArray[0];
55
+ }
56
+
57
+ private static String joinPaths(String[] paths) {
58
+ if (paths == null) {
59
+ return "";
60
+ }
61
+ StringBuilder buf = new StringBuilder();
62
+ for (int i = 0; i < paths.length; i++) {
63
+ buf.append(paths[i]);
64
+ buf.append(':');
65
+ }
66
+ return buf.toString();
67
+ }
68
+
69
+ // https://android.googlesource.com/platform/dalvik/+/android-1.6_r1/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
70
+ public static void appendDexListImplUnderICS(String[] jarPathsToAppend, PathClassLoader pcl, File optDir)
71
+ throws Exception {
72
+ int oldSize = 1; // gonna assume the original path had single entry for simplicity
73
+ Class pclClass = pcl.getClass();
74
+ Field fPath = pclClass.getDeclaredField("path");
75
+ fPath.setAccessible(true);
76
+ String orgPath = fPath.get(pcl).toString();
77
+ String pathToAdd = joinPaths(jarPathsToAppend);
78
+ String path = orgPath + ':' + pathToAdd;
79
+ forceSet(pcl, fPath, path);
80
+
81
+ boolean wantDex = System.getProperty("android.vm.dexfile", "").equals("true");
82
+ File[] files = new File[oldSize + jarPathsToAppend.length];
83
+ ZipFile[] zips = new ZipFile[oldSize + jarPathsToAppend.length];
84
+ DexFile[] dexs = new DexFile[oldSize + jarPathsToAppend.length];
85
+
86
+ Field fmPaths = pclClass.getDeclaredField("mPaths");
87
+ String[] newMPaths = new String[oldSize + jarPathsToAppend.length];
88
+ // set originals
89
+ newMPaths[0] = (String) forceGetFirst(pcl, fmPaths);
90
+ forceSet(pcl, fmPaths, newMPaths);
91
+ Field fmFiles = pclClass.getDeclaredField("mFiles");
92
+ files[0] = (File) forceGetFirst(pcl, fmFiles);
93
+ Field fmZips = pclClass.getDeclaredField("mZips");
94
+ zips[0] = (ZipFile) forceGetFirst(pcl, fmZips);
95
+ Field fmDexs = pclClass.getDeclaredField("mDexs");
96
+ dexs[0] = (DexFile) forceGetFirst(pcl, fmDexs);
97
+
98
+ for (int i = 0; i < jarPathsToAppend.length; i++) {
99
+ newMPaths[oldSize + i] = jarPathsToAppend[i];
100
+ File pathFile = new File(jarPathsToAppend[i]);
101
+ files[oldSize + i] = pathFile;
102
+ zips[oldSize + i] = new ZipFile(pathFile);
103
+ if (wantDex) {
104
+ String outDexName = pathFile.getName() + ".dex";
105
+ File outFile = new File(optDir, outDexName);
106
+ dexs[oldSize + i] = DexFile.loadDex(pathFile.getAbsolutePath(), outFile.getAbsolutePath(), 0);
107
+ }
108
+ }
109
+ forceSet(pcl, fmFiles, files);
110
+ forceSet(pcl, fmZips, zips);
111
+ forceSet(pcl, fmDexs, dexs);
112
+ }
113
+
114
+ // https://android.googlesource.com/platform/libcore/+/master/libdvm/src/main/java/dalvik/system/BaseDexClassLoader.java
115
+ // https://android.googlesource.com/platform/libcore/+/master/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
116
+ public static void appendDexListImplICS(ArrayList<File> jarFiles, PathClassLoader pcl, File optDir,
117
+ boolean kitkatPlus) throws Exception {
118
+ if(debug) {
119
+ Log.d(TAG, "appendDexListImplICS(" + jarFiles);
120
+ }
121
+ // to save original values
122
+ Class bdclClass = Class.forName("dalvik.system.BaseDexClassLoader");
123
+ // ICS+ - pathList
124
+ Field fPathList = bdclClass.getDeclaredField("pathList");
125
+ fPathList.setAccessible(true);
126
+ Object dplObj = fPathList.get(pcl);
127
+ // to call DexPathList.makeDexElements() for additional jar(apk)s
128
+ Class dplClass = dplObj.getClass();
129
+ Field fDexElements = dplClass.getDeclaredField("dexElements");
130
+ fDexElements.setAccessible(true);
131
+ Object objOrgDexElements = fDexElements.get(dplObj);
132
+ int orgDexCount = Array.getLength(objOrgDexElements);
133
+ if(debug) {
134
+ Log.d(TAG, "orgDexCount : " + orgDexCount);
135
+ debugDexElements(objOrgDexElements);
136
+ }
137
+ Class clazzElement = Class.forName("dalvik.system.DexPathList$Element");
138
+ // create new merged array
139
+ int jarCount = jarFiles.size();
140
+ Object newDexElemArray = Array.newInstance(clazzElement, orgDexCount + jarCount);
141
+ System.arraycopy(objOrgDexElements, 0, newDexElemArray, 0, orgDexCount);
142
+ Method mMakeDexElements = null;
143
+ if (kitkatPlus) {
144
+ mMakeDexElements =
145
+ dplClass.getDeclaredMethod("makeDexElements", ArrayList.class, File.class, ArrayList.class);
146
+ } else {
147
+ mMakeDexElements = dplClass.getDeclaredMethod("makeDexElements", ArrayList.class, File.class);
148
+ }
149
+ mMakeDexElements.setAccessible(true);
150
+ Object elemsToAdd;
151
+ if (kitkatPlus) {
152
+ elemsToAdd = mMakeDexElements.invoke(null, jarFiles, optDir, new ArrayList());
153
+ } else {
154
+ elemsToAdd = mMakeDexElements.invoke(null, jarFiles, optDir);
155
+ }
156
+ for (int i = 0; i < jarCount; i++) {
157
+ int pos = orgDexCount + i;
158
+ Object elemToAdd = Array.get(elemsToAdd, i);
159
+ Array.set(newDexElemArray, pos, elemToAdd);
160
+ }
161
+ if(debug) {
162
+ Log.d(TAG, "appendDexListImplICS() " + Arrays.deepToString((Object[]) newDexElemArray));
163
+ }
164
+ forceSet(dplObj, fDexElements, newDexElemArray);
165
+ }
166
+
167
+ private static void debugDexElements(Object dexElements) throws Exception {
168
+ Object[] objArray = (Object[]) dexElements;
169
+ Class clazzElement = Class.forName("dalvik.system.DexPathList$Element");
170
+ Field fFile = clazzElement.getDeclaredField("file");
171
+ fFile.setAccessible(true);
172
+ for (int i = 0; i < objArray.length; i++) {
173
+ File f = (File) fFile.get(objArray[i]);
174
+ Log.d(TAG, "[" + i + "] " + f);
175
+ }
176
+ }
177
+ }
@@ -11,6 +11,7 @@ import android.content.pm.ApplicationInfo;
11
11
  import android.content.pm.PackageInfo;
12
12
  import android.content.pm.PackageManager;
13
13
  import android.content.pm.PackageManager.NameNotFoundException;
14
+ import android.os.Build;
14
15
  import android.os.Environment;
15
16
  import dalvik.system.PathClassLoader;
16
17
 
@@ -135,7 +136,7 @@ public class JRubyAdapter {
135
136
  System.setProperty("jruby.backtrace.style", "normal"); // normal raw full mri
136
137
  System.setProperty("jruby.bytecode.version", "1.6");
137
138
  // BEGIN Ruboto RubyVersion
138
- // System.setProperty("jruby.compat.version", "RUBY1_9"); // RUBY1_9 is the default in JRuby 1.7
139
+ // System.setProperty("jruby.compat.version", "RUBY2_0"); // RUBY1_9 is the default in JRuby 1.7
139
140
  // END Ruboto RubyVersion
140
141
  // System.setProperty("jruby.compile.backend", "DALVIK");
141
142
  System.setProperty("jruby.compile.mode", "OFF"); // OFF OFFIR JITIR? FORCE FORCEIR
@@ -157,6 +158,18 @@ public class JRubyAdapter {
157
158
  System.setProperty("jruby.ji.upper.case.package.name.allowed", "true");
158
159
  System.setProperty("jruby.class.cache.path", appContext.getDir("dex", 0).getAbsolutePath());
159
160
 
161
+ // FIXME(uwe): Simplify when we stop supporting android-15
162
+ if (Build.VERSION.SDK_INT >= 16) {
163
+ DexDex.debug = true;
164
+ DexDex.validateClassPath(appContext);
165
+ while (DexDex.dexOptRequired) {
166
+ System.out.println("Waiting for class loader setup...");
167
+ try {
168
+ Thread.sleep(100);
169
+ } catch (InterruptedException ie) {}
170
+ }
171
+ }
172
+
160
173
  ClassLoader classLoader;
161
174
  Class<?> scriptingContainerClass;
162
175
  String apkName = null;
@@ -240,6 +253,9 @@ public class JRubyAdapter {
240
253
  rubyInstanceConfigClass.getMethod("setError", PrintStream.class).invoke(config, output);
241
254
  }
242
255
 
256
+ System.out.println("Ruby version: " + rubyInstanceConfigClass
257
+ .getMethod("getCompatVersion").invoke(config));
258
+
243
259
  // This will become the global runtime and be used by our ScriptingContainer
244
260
  rubyClass.getMethod("newInstance", rubyInstanceConfigClass).invoke(null, config);
245
261
 
@@ -265,19 +281,27 @@ public class JRubyAdapter {
265
281
 
266
282
  Thread.currentThread().setContextClassLoader(classLoader);
267
283
 
284
+ String scriptsDir = scriptsDirName(appContext);
285
+ addLoadPath(scriptsDir);
268
286
  if (appContext.getFilesDir() != null) {
269
287
  String defaultCurrentDir = appContext.getFilesDir().getPath();
270
288
  Log.d("Setting JRuby current directory to " + defaultCurrentDir);
271
289
  callScriptingContainerMethod(Void.class, "setCurrentDirectory", defaultCurrentDir);
272
290
  } else {
273
291
  Log.e("Unable to find app files dir!");
292
+ if (new File(scriptsDir).exists()) {
293
+ Log.d("Changing JRuby current directory to " + scriptsDir);
294
+ callScriptingContainerMethod(Void.class, "setCurrentDirectory", scriptsDir);
295
+ }
274
296
  }
275
297
 
276
- addLoadPath(scriptsDirName(appContext));
277
298
  put("$package_name", appContext.getPackageName());
278
299
 
279
300
  runScriptlet("::RUBOTO_JAVA_PROXIES = {}");
280
301
 
302
+ System.out.println("JRuby version: " + Class.forName("org.jruby.runtime.Constants", true, scriptingContainerClass.getClassLoader())
303
+ .getDeclaredField("VERSION").get(String.class));
304
+
281
305
  initialized = true;
282
306
  } catch (ClassNotFoundException e) {
283
307
  handleInitException(e);
@@ -293,6 +317,8 @@ public class JRubyAdapter {
293
317
  handleInitException(e);
294
318
  } catch (NoSuchMethodException e) {
295
319
  handleInitException(e);
320
+ } catch (NoSuchFieldException e) {
321
+ handleInitException(e);
296
322
  }
297
323
  }
298
324
  return initialized;
@@ -311,8 +337,6 @@ public class JRubyAdapter {
311
337
  Log.i("Added directory to load path: " + scriptsDir);
312
338
  Script.addDir(scriptsDir);
313
339
  runScriptlet("$:.unshift '" + scriptsDir + "' ; $:.uniq!");
314
- Log.d("Changing JRuby current directory to " + scriptsDir);
315
- callScriptingContainerMethod(Void.class, "setCurrentDirectory", scriptsDir);
316
340
  return true;
317
341
  } else {
318
342
  Log.i("Extra scripts dir not present: " + scriptsDir);
@@ -34,7 +34,7 @@ public class ScriptLoader {
34
34
  Log.d("Found script.");
35
35
  rubyInstance = component;
36
36
  final String script = rubyScript.getContents();
37
- boolean scriptContainsClass = script.matches("(?s).*class "
37
+ boolean scriptContainsClass = script.matches("(?s).*class\\s+"
38
38
  + component.getScriptInfo().getRubyClassName() + ".*");
39
39
  boolean hasBackingJavaClass = component.getScriptInfo().getRubyClassName()
40
40
  .equals(component.getClass().getSimpleName());
@@ -38,6 +38,7 @@ public class SplashActivity extends Activity {
38
38
  } catch (Exception e) {
39
39
  splash = -1;
40
40
  }
41
+ requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
41
42
  if (!JRubyAdapter.isInitialized()) {
42
43
  initJRuby(true);
43
44
  }
@@ -164,7 +165,6 @@ public class SplashActivity extends Activity {
164
165
  if (loadingDialog == null) {
165
166
  if (splash > 0) {
166
167
  Log.i("Showing splash");
167
- requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
168
168
  setContentView(splash);
169
169
  } else {
170
170
  Log.i("Showing progress");
@@ -184,7 +184,6 @@ public class SplashActivity extends Activity {
184
184
  if (loadingDialog == null) {
185
185
  if (splash > 0) {
186
186
  Log.i("Showing splash");
187
- requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
188
187
  setContentView(splash);
189
188
  } else {
190
189
  Log.i("Showing progress");
@@ -43,6 +43,7 @@ module Ruboto::Activity::Reload
43
43
  file_string = reload_intent.get_string_extra('reload')
44
44
  if file_string
45
45
  scripts_dir = @activity.getExternalFilesDir(nil).absolute_path + '/scripts'
46
+ files = file_string.split(/(?<!&);/).map { |f| f.gsub(/&(.)/) { |m| m[1] } }
46
47
  files.each do |file|
47
48
  Log.d "load file: #{scripts_dir}/#{file}"
48
49
  load "#{scripts_dir}/#{file}"
@@ -59,12 +59,18 @@ module Ruboto
59
59
 
60
60
  script_name = options.delete(:script)
61
61
  extras = options.delete(:extras)
62
+ flags = options.delete(:flags)
63
+
62
64
  raise "Unknown options: #{options}" unless options.empty?
63
65
 
64
- if class_name.nil? && block_given?
65
- src_desc = source_descriptor(block)
66
- class_name =
67
- "#{java_class.name.split('::').last}_#{src_desc[0].split('/').last.gsub(/[.-]+/, '_')}_#{src_desc[1]}"
66
+ if class_name.nil?
67
+ if block_given?
68
+ src_desc = source_descriptor(block)
69
+ class_name =
70
+ "#{java_class.name.split('::').last}_#{src_desc[0].split('/').last.gsub(/[.-]+/, '_')}_#{src_desc[1]}"
71
+ else
72
+ class_name = java_class.name.split('::').last
73
+ end
68
74
  end
69
75
 
70
76
  class_name = class_name.to_s
@@ -76,6 +82,7 @@ module Ruboto
76
82
  end
77
83
  i = android.content.Intent.new
78
84
  i.setClass self, java_class.java_class
85
+ i.add_flags(flags) if flags
79
86
  i.putExtra(Ruboto::THEME_KEY, theme) if theme
80
87
  i.putExtra(Ruboto::CLASS_NAME_KEY, class_name) if class_name
81
88
  i.putExtra(Ruboto::SCRIPT_NAME_KEY, script_name) if script_name
@@ -124,4 +131,3 @@ end
124
131
  java_import 'android.app.Activity'
125
132
  java_import 'org.ruboto.RubotoActivity'
126
133
  ruboto_configure_activity(RubotoActivity)
127
-