ruboto 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +1 -0
- data/RELEASE_CANDICATE_DOC.md +21 -8
- data/RELEASE_DOC.md +71 -27
- data/Rakefile +27 -25
- data/assets/rakelib/ruboto.rake +137 -21
- data/assets/rakelib/{stdlib.rake → ruboto.stdlib.rake} +80 -29
- data/assets/rakelib/{stdlib_dependencies.rb → ruboto.stdlib.rb} +18 -24
- data/assets/rakelib/{stdlib.yml → ruboto.stdlib.yml} +0 -0
- data/assets/ruboto.yml +28 -18
- data/assets/src/org/ruboto/DexDex.java +329 -0
- data/assets/src/org/ruboto/FrameworkHack.java +177 -0
- data/assets/src/org/ruboto/JRubyAdapter.java +28 -4
- data/assets/src/org/ruboto/ScriptLoader.java +1 -1
- data/assets/src/org/ruboto/SplashActivity.java +1 -2
- data/assets/src/ruboto/activity/reload.rb +1 -0
- data/assets/src/ruboto/activity.rb +11 -5
- data/assets/src/ruboto/util/toast.rb +2 -2
- data/lib/ruboto/commands/base.rb +85 -37
- data/lib/ruboto/util/emulator.rb +32 -14
- data/lib/ruboto/util/setup.rb +34 -12
- data/lib/ruboto/util/update.rb +70 -40
- data/lib/ruboto/version.rb +1 -1
- data/test/activity/navigation_activity_test.rb +2 -0
- data/test/activity/ssl_activity.rb +26 -9
- data/test/activity/ssl_activity_test.rb +14 -6
- data/test/app_test_methods.rb +8 -3
- data/test/ruboto_gen_test.rb +13 -7
- data/test/ruboto_setup_test.rb +21 -0
- data/test/ruboto_update_test.rb +26 -28
- data/test/test_helper.rb +25 -21
- metadata +10 -7
@@ -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", "
|
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?
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|