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.
- 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
|
-
|