ruboto 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
-