getPlugins() {
197 | return pluginPkgToInfoMap.values();
198 | }
199 |
200 | /**
201 | * 指定包名卸载一个插件
202 | *
203 | * @param pkg 插件包名
204 | */
205 | public void uninstallPluginByPkg(String pkg) {
206 | removePlugByPkg(pkg);
207 | }
208 |
209 |
210 | private PlugInfo removePlugByPkg(String pkg) {
211 | PlugInfo pl;
212 | synchronized (this) {
213 | pl = pluginPkgToInfoMap.remove(pkg);
214 | if (pl == null) {
215 | return null;
216 | }
217 | }
218 | return pl;
219 | }
220 |
221 | /**
222 | * 加载指定插件或指定目录下的所有插件
223 | *
224 | * 都使用文件名作为Id
225 | *
226 | * @param pluginSrcDirFile - apk或apk目录
227 | * @return 插件集合
228 | * @throws Exception
229 | */
230 | public Collection loadPlugin(final File pluginSrcDirFile)
231 | throws Exception {
232 | if (pluginSrcDirFile == null || !pluginSrcDirFile.exists()) {
233 | Trace.store("invalidate plugin file or Directory :"
234 | + pluginSrcDirFile);
235 | return null;
236 | }
237 | if (pluginSrcDirFile.isFile()) {
238 | PlugInfo one = buildPlugInfo(pluginSrcDirFile, null, null);
239 | if (one != null) {
240 | savePluginToMap(one);
241 | }
242 | return Collections.singletonList(one);
243 | }
244 | // synchronized (this) {
245 | // pluginPkgToInfoMap.clear();
246 | // }
247 | File[] pluginApkFiles = pluginSrcDirFile.listFiles(this);
248 | if (pluginApkFiles == null || pluginApkFiles.length == 0) {
249 | throw new FileNotFoundException("could not find plugins in:"
250 | + pluginSrcDirFile);
251 | }
252 | for (File pluginApk : pluginApkFiles) {
253 | try {
254 | PlugInfo plugInfo = buildPlugInfo(pluginApk, null, null);
255 | if (plugInfo != null) {
256 | savePluginToMap(plugInfo);
257 | }
258 | } catch (Throwable e) {
259 | e.printStackTrace();
260 | }
261 | }
262 | return pluginPkgToInfoMap.values();
263 | }
264 |
265 | private synchronized void savePluginToMap(PlugInfo plugInfo) {
266 | pluginPkgToInfoMap.put(plugInfo.getPackageName(), plugInfo);
267 | }
268 |
269 |
270 | private PlugInfo buildPlugInfo(File pluginApk, String pluginId,
271 | String targetFileName) throws Exception {
272 | PlugInfo info = new PlugInfo();
273 | info.setId(pluginId == null ? pluginApk.getName() : pluginId);
274 |
275 | File privateFile = new File(dexInternalStoragePath,
276 | targetFileName == null ? pluginApk.getName() : targetFileName);
277 |
278 | info.setFilePath(privateFile.getAbsolutePath());
279 | //Copy Plugin to Private Dir
280 | if (!pluginApk.getAbsolutePath().equals(privateFile.getAbsolutePath())) {
281 | copyApkToPrivatePath(pluginApk, privateFile);
282 | }
283 | String dexPath = privateFile.getAbsolutePath();
284 | //Load Plugin Manifest
285 | PluginManifestUtil.setManifestInfo(context, dexPath, info);
286 | //Load Plugin Res
287 | try {
288 | AssetManager am = AssetManager.class.newInstance();
289 | am.getClass().getMethod("addAssetPath", String.class)
290 | .invoke(am, dexPath);
291 | info.setAssetManager(am);
292 | Resources hotRes = context.getResources();
293 | Resources res = new Resources(am, hotRes.getDisplayMetrics(),
294 | hotRes.getConfiguration());
295 | info.setResources(res);
296 | } catch (Exception e) {
297 | throw new RuntimeException("Unable to create Resources&Assets for "
298 | + info.getPackageName() + " : " + e.getMessage());
299 | }
300 | //Load classLoader for Plugin
301 | PluginClassLoader pluginClassLoader = new PluginClassLoader(info, dexPath, dexOutputPath
302 | , getPluginLibPath(info).getAbsolutePath(), pluginParentClassLoader);
303 | info.setClassLoader(pluginClassLoader);
304 | ApplicationInfo appInfo = info.getPackageInfo().applicationInfo;
305 | Application app = makeApplication(info, appInfo);
306 | attachBaseContext(info, app);
307 | info.setApplication(app);
308 | Trace.store("Build pluginInfo => " + info);
309 | return info;
310 | }
311 |
312 | private void attachBaseContext(PlugInfo info, Application app) {
313 | try {
314 | Field mBase = ContextWrapper.class.getDeclaredField("mBase");
315 | mBase.setAccessible(true);
316 | mBase.set(app, new PluginContext(context.getApplicationContext(), info));
317 | } catch (Throwable e) {
318 | e.printStackTrace();
319 | }
320 | }
321 |
322 |
323 | /**
324 | * 插件中可能需要公用某些依赖库以减小体积,当你有这个需求的时候,请使用本API.
325 | * @param parentClassLoader classLoader
326 | */
327 | public void setPluginParentClassLoader(ClassLoader parentClassLoader) {
328 | if (parentClassLoader != null) {
329 | this.pluginParentClassLoader = parentClassLoader;
330 | }else {
331 | this.pluginParentClassLoader = ClassLoader.getSystemClassLoader().getParent();
332 | }
333 | }
334 |
335 | public ClassLoader getPluginParentClassLoader() {
336 | return pluginParentClassLoader;
337 | }
338 |
339 | /**
340 | * 构造插件的Application
341 | *
342 | * @param plugInfo 插件信息
343 | * @param appInfo 插件ApplicationInfo
344 | * @return 插件App
345 | */
346 | private Application makeApplication(PlugInfo plugInfo, ApplicationInfo appInfo) {
347 | String appClassName = appInfo.className;
348 | if (appClassName == null) {
349 | //Default Application
350 | appClassName = Application.class.getName();
351 | }
352 | try {
353 | return (Application) plugInfo.getClassLoader().loadClass(appClassName).newInstance();
354 | } catch (Throwable e) {
355 | throw new RuntimeException("Unable to create Application for "
356 | + plugInfo.getPackageName() + ": "
357 | + e.getMessage());
358 | }
359 | }
360 |
361 |
362 | /**
363 | * 将Apk复制到私有目录
364 | * @see PluginOverdueVerifier
365 | * 可设置插件覆盖校检器来验证插件是否需要覆盖
366 | *
367 | * @param pluginApk 插件apk原始路径
368 | * @param targetPutApk 要拷贝到的目标位置
369 | */
370 | private void copyApkToPrivatePath(File pluginApk, File targetPutApk) {
371 | if (pluginOverdueVerifier != null) {
372 | //仅当私有目录中插件已存在时需要检验
373 | if (targetPutApk.exists() && pluginOverdueVerifier.isOverdue(pluginApk, targetPutApk)) {
374 | return;
375 | }
376 | }
377 | FileUtil.copyFile(pluginApk, targetPutApk);
378 | }
379 |
380 | /**
381 | * @return 存储插件的私有目录
382 | */
383 | File getDexInternalStoragePath() {
384 | return dexInternalStoragePath;
385 | }
386 |
387 | Context getContext() {
388 | return context;
389 | }
390 |
391 | /**
392 | * @return 插件Activity生命周期监听器
393 | */
394 | public PluginActivityLifeCycleCallback getPluginActivityLifeCycleCallback() {
395 | return pluginActivityLifeCycleCallback;
396 | }
397 |
398 | /**
399 | * 设置插件Activity生命周期监听器
400 | *
401 | * @param pluginActivityLifeCycleCallback 插件Activity生命周期监听器
402 | */
403 | public void setPluginActivityLifeCycleCallback(
404 | PluginActivityLifeCycleCallback pluginActivityLifeCycleCallback) {
405 | this.pluginActivityLifeCycleCallback = pluginActivityLifeCycleCallback;
406 | }
407 |
408 | /**
409 | * @return 插件验证校检器
410 | */
411 | public PluginOverdueVerifier getPluginOverdueVerifier() {
412 | return pluginOverdueVerifier;
413 | }
414 |
415 | /**
416 | * 设置插件验证校检器
417 | *
418 | * @param pluginOverdueVerifier 插件验证校检器
419 | */
420 | public void setPluginOverdueVerifier(PluginOverdueVerifier pluginOverdueVerifier) {
421 | this.pluginOverdueVerifier = pluginOverdueVerifier;
422 | }
423 |
424 | @Override
425 | public boolean accept(File pathname) {
426 | return !pathname.isDirectory() && pathname.getName().endsWith(".apk");
427 | }
428 |
429 |
430 | //======================================================
431 | //=================启动插件相关方法=======================
432 | //======================================================
433 |
434 |
435 | /**
436 | * 启动插件的主Activity
437 | *
438 | * @param from fromContext
439 | * @param plugInfo 插件Info
440 | * @param intent 通过此Intent可以向插件传参, 可以为null
441 | */
442 | public void startMainActivity(Context from, PlugInfo plugInfo, Intent intent) {
443 | if (!pluginPkgToInfoMap.containsKey(plugInfo.getPackageName())) {
444 | return;
445 | }
446 | ActivityInfo activityInfo = plugInfo.getMainActivity().activityInfo;
447 | if (activityInfo == null) {
448 | throw new ActivityNotFoundException("Cannot find Main Activity from plugin.");
449 | }
450 | startActivity(from, plugInfo, activityInfo, intent);
451 |
452 | }
453 |
454 | /**
455 | * 启动插件的主Activity
456 | *
457 | * @param from fromContext
458 | * @param plugInfo 插件Info
459 | */
460 | public void startMainActivity(Context from, PlugInfo plugInfo) {
461 | startMainActivity(from, plugInfo, null);
462 | }
463 |
464 | /**
465 | * 启动插件的主Activity
466 | * @param from fromContext
467 | * @param pluginPkgName 插件包名
468 | * @throws PluginNotFoundException 该插件未加载时抛出
469 | * @throws ActivityNotFoundException 插件Activity信息找不到时抛出
470 | */
471 | public void startMainActivity(Context from, String pluginPkgName) throws PluginNotFoundException, ActivityNotFoundException {
472 | PlugInfo plugInfo = tryGetPluginInfo(pluginPkgName);
473 | startMainActivity(from, plugInfo);
474 | }
475 |
476 |
477 | /**
478 | * 启动插件的指定Activity
479 | *
480 | * @param from fromContext
481 | * @param plugInfo 插件信息
482 | * @param activityInfo 要启动的插件activity信息
483 | * @param intent 通过此Intent可以向插件传参, 可以为null
484 | */
485 | public void startActivity(Context from, PlugInfo plugInfo, ActivityInfo activityInfo, Intent intent) {
486 | if (activityInfo == null) {
487 | throw new ActivityNotFoundException("Cannot find ActivityInfo from plugin, could you declare this Activity in plugin?");
488 | }
489 | if (intent == null) {
490 | intent = new Intent();
491 | }
492 | CreateActivityData createActivityData = new CreateActivityData(activityInfo.name, plugInfo.getPackageName());
493 | intent.setClass(from, activitySelector.selectDynamicActivity(activityInfo));
494 | intent.putExtra(Globals.FLAG_ACTIVITY_FROM_PLUGIN, createActivityData);
495 | from.startActivity(intent);
496 | }
497 |
498 |
499 | public DynamicActivitySelector getActivitySelector() {
500 | return activitySelector;
501 | }
502 |
503 | public void setActivitySelector(DynamicActivitySelector activitySelector) {
504 | if (activitySelector == null) {
505 | activitySelector = DefaultActivitySelector.getDefault();
506 | }
507 | this.activitySelector = activitySelector;
508 | }
509 |
510 | /**
511 | * 启动插件的指定Activity
512 | *
513 | * @param from fromContext
514 | * @param plugInfo 插件信息
515 | * @param targetActivity 要启动的插件activity类名
516 | * @param intent 通过此Intent可以向插件传参, 可以为null
517 | */
518 | public void startActivity(Context from, PlugInfo plugInfo, String targetActivity, Intent intent) {
519 | ActivityInfo activityInfo = plugInfo.findActivityByClassName(targetActivity);
520 | startActivity(from, plugInfo, activityInfo, intent);
521 | }
522 |
523 | /**
524 | * 启动插件的指定Activity
525 | *
526 | * @param from fromContext
527 | * @param plugInfo 插件信息
528 | * @param targetActivity 要启动的插件activity类名
529 | */
530 | public void startActivity(Context from, PlugInfo plugInfo, String targetActivity) {
531 | startActivity(from, plugInfo, targetActivity, null);
532 | }
533 |
534 |
535 | /**
536 | * 启动插件的指定Activity
537 | *
538 | * @param from fromContext
539 | * @param pluginPkgName 插件包名
540 | * @param targetActivity 要启动的插件activity类名
541 | */
542 | public void startActivity(Context from, String pluginPkgName, String targetActivity) throws PluginNotFoundException, ActivityNotFoundException {
543 | startActivity(from, pluginPkgName, targetActivity, null);
544 | }
545 |
546 | /**
547 | * 启动插件的指定Activity
548 | *
549 | * @param from fromContext
550 | * @param pluginPkgName 插件包名
551 | * @param targetActivity 要启动的插件activity类名
552 | * @param intent 通过此Intent可以向插件传参, 可以为null
553 | */
554 | public void startActivity(Context from, String pluginPkgName, String targetActivity, Intent intent) throws PluginNotFoundException, ActivityNotFoundException {
555 | PlugInfo plugInfo = tryGetPluginInfo(pluginPkgName);
556 | startActivity(from, plugInfo, targetActivity, intent);
557 | }
558 |
559 |
560 | public void dump() {
561 | Trace.store(pluginPkgToInfoMap.size() + " Plugins is loaded, " + Arrays.toString(pluginPkgToInfoMap.values().toArray()));
562 | }
563 |
564 | /**
565 | * @return 当前是否为主线程
566 | */
567 | public boolean isMainThread() {
568 | return Looper.getMainLooper() == Looper.myLooper();
569 | }
570 | }
571 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/delegate/DelegateActivityThread.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.delegate;
2 |
3 | import android.app.ActivityThread;
4 | import android.app.Application;
5 | import android.app.Instrumentation;
6 |
7 | import androidx.pluginmgr.reflect.Reflect;
8 |
9 | /**
10 | * @author Lody
11 | * @version 1.0
12 | */
13 | public final class DelegateActivityThread {
14 |
15 | private static DelegateActivityThread SINGLETON = new DelegateActivityThread();
16 |
17 | private Reflect activityThreadReflect;
18 |
19 | public DelegateActivityThread() {
20 | activityThreadReflect = Reflect.on(ActivityThread.currentActivityThread());
21 | }
22 |
23 | public static DelegateActivityThread getSingleton() {
24 | return SINGLETON;
25 | }
26 |
27 | public Application getInitialApplication() {
28 | return activityThreadReflect.get("mInitialApplication");
29 | }
30 |
31 | public Instrumentation getInstrumentation() {
32 | return activityThreadReflect.get("mInstrumentation");
33 | }
34 |
35 | public void setInstrumentation(Instrumentation newInstrumentation) {
36 | activityThreadReflect.set("mInstrumentation", newInstrumentation);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/delegate/DelegateInstrumentation.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.delegate;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Activity;
5 | import android.app.Application;
6 | import android.app.Fragment;
7 | import android.app.Instrumentation;
8 | import android.app.UiAutomation;
9 | import android.content.ComponentName;
10 | import android.content.Context;
11 | import android.content.Intent;
12 | import android.content.IntentFilter;
13 | import android.content.pm.ActivityInfo;
14 | import android.os.Build;
15 | import android.os.Bundle;
16 | import android.os.IBinder;
17 | import android.view.KeyEvent;
18 | import android.view.MotionEvent;
19 |
20 | /**
21 | * NOTICE: 如果导入的时候这个类出现了红色,
22 | * 请删除相关方法,因为不同API版本下这个类的方法稍有增删.
23 | *
24 | * @author Lody
25 | * @version 1.0
26 | */
27 | public class DelegateInstrumentation extends Instrumentation {
28 |
29 | private Instrumentation mBase;
30 |
31 | /**
32 | * @param mBase 真正的Instrumentation
33 | */
34 | public DelegateInstrumentation(Instrumentation mBase) {
35 | this.mBase = mBase;
36 | }
37 |
38 | @Override
39 | public void onCreate(Bundle arguments) {
40 | mBase.onCreate(arguments);
41 | }
42 |
43 | @Override
44 | public void start() {
45 | mBase.start();
46 | }
47 |
48 | @Override
49 | public void onStart() {
50 | mBase.onStart();
51 | }
52 |
53 | @Override
54 | public boolean onException(Object obj, Throwable e) {
55 | return mBase.onException(obj, e);
56 | }
57 |
58 | @Override
59 | public void sendStatus(int resultCode, Bundle results) {
60 | mBase.sendStatus(resultCode, results);
61 | }
62 |
63 | @Override
64 | public void finish(int resultCode, Bundle results) {
65 | mBase.finish(resultCode, results);
66 | }
67 |
68 | @Override
69 | public void setAutomaticPerformanceSnapshots() {
70 | mBase.setAutomaticPerformanceSnapshots();
71 | }
72 |
73 | @Override
74 | public void startPerformanceSnapshot() {
75 | mBase.startPerformanceSnapshot();
76 | }
77 |
78 | @Override
79 | public void endPerformanceSnapshot() {
80 | mBase.endPerformanceSnapshot();
81 | }
82 |
83 | @Override
84 | public void onDestroy() {
85 | mBase.onDestroy();
86 | }
87 |
88 | @Override
89 | public Context getContext() {
90 | return mBase.getContext();
91 | }
92 |
93 | @Override
94 | public ComponentName getComponentName() {
95 | return mBase.getComponentName();
96 | }
97 |
98 | @Override
99 | public Context getTargetContext() {
100 | return mBase.getTargetContext();
101 | }
102 |
103 | @Override
104 | public boolean isProfiling() {
105 | return mBase.isProfiling();
106 | }
107 |
108 | @Override
109 | public void startProfiling() {
110 | mBase.startProfiling();
111 | }
112 |
113 | @Override
114 | public void stopProfiling() {
115 | mBase.stopProfiling();
116 | }
117 |
118 | @Override
119 | public void setInTouchMode(boolean inTouch) {
120 | mBase.setInTouchMode(inTouch);
121 | }
122 |
123 | @Override
124 | public void waitForIdle(Runnable recipient) {
125 | mBase.waitForIdle(recipient);
126 | }
127 |
128 | @Override
129 | public void waitForIdleSync() {
130 | mBase.waitForIdleSync();
131 | }
132 |
133 | @Override
134 | public void runOnMainSync(Runnable runner) {
135 | mBase.runOnMainSync(runner);
136 | }
137 |
138 | @Override
139 | public Activity startActivitySync(Intent intent) {
140 | return mBase.startActivitySync(intent);
141 | }
142 |
143 | @Override
144 | public void addMonitor(ActivityMonitor monitor) {
145 | mBase.addMonitor(monitor);
146 | }
147 |
148 | @Override
149 | public ActivityMonitor addMonitor(IntentFilter filter, ActivityResult result, boolean block) {
150 | return mBase.addMonitor(filter, result, block);
151 | }
152 |
153 | @Override
154 | public ActivityMonitor addMonitor(String cls, ActivityResult result, boolean block) {
155 | return mBase.addMonitor(cls, result, block);
156 | }
157 |
158 | @Override
159 | public boolean checkMonitorHit(ActivityMonitor monitor, int minHits) {
160 | return mBase.checkMonitorHit(monitor, minHits);
161 | }
162 |
163 | @Override
164 | public Activity waitForMonitor(ActivityMonitor monitor) {
165 | return mBase.waitForMonitor(monitor);
166 | }
167 |
168 | @Override
169 | public Activity waitForMonitorWithTimeout(ActivityMonitor monitor, long timeOut) {
170 | return mBase.waitForMonitorWithTimeout(monitor, timeOut);
171 | }
172 |
173 | @Override
174 | public void removeMonitor(ActivityMonitor monitor) {
175 | mBase.removeMonitor(monitor);
176 | }
177 |
178 | @Override
179 | public boolean invokeMenuActionSync(Activity targetActivity, int id, int flag) {
180 | return mBase.invokeMenuActionSync(targetActivity, id, flag);
181 | }
182 |
183 | @Override
184 | public boolean invokeContextMenuAction(Activity targetActivity, int id, int flag) {
185 | return mBase.invokeContextMenuAction(targetActivity, id, flag);
186 | }
187 |
188 | @Override
189 | public void sendStringSync(String text) {
190 | mBase.sendStringSync(text);
191 | }
192 |
193 | @Override
194 | public void sendKeySync(KeyEvent event) {
195 | mBase.sendKeySync(event);
196 | }
197 |
198 | @Override
199 | public void sendKeyDownUpSync(int key) {
200 | mBase.sendKeyDownUpSync(key);
201 | }
202 |
203 | @Override
204 | public void sendCharacterSync(int keyCode) {
205 | mBase.sendCharacterSync(keyCode);
206 | }
207 |
208 | @Override
209 | public void sendPointerSync(MotionEvent event) {
210 | mBase.sendPointerSync(event);
211 | }
212 |
213 | @Override
214 | public void sendTrackballEventSync(MotionEvent event) {
215 | mBase.sendTrackballEventSync(event);
216 | }
217 |
218 | @Override
219 | public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
220 | return mBase.newApplication(cl, className, context);
221 | }
222 |
223 | @Override
224 | public void callApplicationOnCreate(Application app) {
225 | mBase.callApplicationOnCreate(app);
226 | }
227 |
228 | @Override
229 | public Activity newActivity(Class> clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException {
230 | return mBase.newActivity(clazz, context, token, application, intent, info, title, parent, id, lastNonConfigurationInstance);
231 | }
232 |
233 | @Override
234 | public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
235 | return mBase.newActivity(cl, className, intent);
236 | }
237 |
238 | @Override
239 | public void callActivityOnCreate(Activity activity, Bundle icicle) {
240 | mBase.callActivityOnCreate(activity, icicle);
241 | }
242 |
243 | @Override
244 | public void callActivityOnDestroy(Activity activity) {
245 | mBase.callActivityOnDestroy(activity);
246 | }
247 |
248 | @Override
249 | public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState) {
250 | mBase.callActivityOnRestoreInstanceState(activity, savedInstanceState);
251 | }
252 |
253 | @Override
254 | public void callActivityOnPostCreate(Activity activity, Bundle icicle) {
255 | mBase.callActivityOnPostCreate(activity, icicle);
256 | }
257 |
258 | @Override
259 | public void callActivityOnNewIntent(Activity activity, Intent intent) {
260 | mBase.callActivityOnNewIntent(activity, intent);
261 | }
262 |
263 | @Override
264 | public void callActivityOnStart(Activity activity) {
265 | mBase.callActivityOnStart(activity);
266 | }
267 |
268 | @Override
269 | public void callActivityOnRestart(Activity activity) {
270 | mBase.callActivityOnRestart(activity);
271 | }
272 |
273 | @Override
274 | public void callActivityOnResume(Activity activity) {
275 | mBase.callActivityOnResume(activity);
276 | }
277 |
278 | @Override
279 | public void callActivityOnStop(Activity activity) {
280 | mBase.callActivityOnStop(activity);
281 | }
282 |
283 | @Override
284 | public void callActivityOnSaveInstanceState(Activity activity, Bundle outState) {
285 | mBase.callActivityOnSaveInstanceState(activity, outState);
286 | }
287 |
288 | @Override
289 | public void callActivityOnPause(Activity activity) {
290 | mBase.callActivityOnPause(activity);
291 | }
292 |
293 | @TargetApi(Build.VERSION_CODES.CUPCAKE)
294 | @Override
295 | public void callActivityOnUserLeaving(Activity activity) {
296 | mBase.callActivityOnUserLeaving(activity);
297 | }
298 |
299 | @Override
300 | public void startAllocCounting() {
301 | mBase.startAllocCounting();
302 | }
303 |
304 | @Override
305 | public void stopAllocCounting() {
306 | mBase.stopAllocCounting();
307 | }
308 |
309 | @Override
310 | public Bundle getAllocCounts() {
311 | return mBase.getAllocCounts();
312 | }
313 |
314 | @Override
315 | public Bundle getBinderCounts() {
316 | return mBase.getBinderCounts();
317 | }
318 |
319 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
320 | @Override
321 | public UiAutomation getUiAutomation() {
322 | return mBase.getUiAutomation();
323 | }
324 |
325 | @Override
326 | public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment fragment, Intent intent, int requestCode) {
327 | return mBase.execStartActivity(who, contextThread, token, fragment, intent, requestCode);
328 | }
329 |
330 | @Override
331 | public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment fragment, Intent intent, int requestCode, Bundle options) {
332 | return mBase.execStartActivity(who, contextThread, token, fragment, intent, requestCode, options);
333 | }
334 |
335 | @Override
336 | public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {
337 | return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode);
338 | }
339 |
340 | @Override
341 | public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
342 | return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);
343 | }
344 |
345 | }
346 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/delegate/LayoutInflaterProxyContext.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.delegate;
2 |
3 | import android.content.Context;
4 | import android.content.ContextWrapper;
5 | import android.view.LayoutInflater;
6 |
7 | /**
8 | * @author Lody
9 | * @version 1.0
10 | */
11 | public class LayoutInflaterProxyContext extends ContextWrapper {
12 |
13 | private LayoutInflater mInflater;
14 |
15 | public LayoutInflaterProxyContext(Context base) {
16 | super(base);
17 | }
18 |
19 | @Override
20 | public Object getSystemService(String name) {
21 | if (LAYOUT_INFLATER_SERVICE.equals(name)) {
22 | if (mInflater == null) {
23 | mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
24 | }
25 | return mInflater;
26 | }
27 | return super.getSystemService(name);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/environment/CreateActivityData.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.environment;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * 存储一个将要创建的插件Activity的数据
9 | *
10 | * @author Lody
11 | * @version 1.0
12 | */
13 | public final class CreateActivityData implements Serializable {
14 | /**
15 | * 要创建的Activity的类名
16 | */
17 | public String activityName;
18 |
19 | /**
20 | * 插件的ID或包名
21 | */
22 | public String pluginPkg;
23 |
24 | public CreateActivityData(String activityName, String pluginPkg) {
25 | this.activityName = activityName;
26 | this.pluginPkg = pluginPkg;
27 | }
28 |
29 | @Override
30 | public boolean equals(Object o) {
31 | if (o instanceof CreateActivityData) {
32 | CreateActivityData another = (CreateActivityData) o;
33 | return TextUtils.equals(activityName,another.activityName)
34 | && TextUtils.equals(pluginPkg,another.pluginPkg);
35 | }
36 | return false;
37 | }
38 |
39 | @Override
40 | public int hashCode() {
41 | int result = activityName != null ? activityName.hashCode() : 0;
42 | result = 31 * result + (pluginPkg != null ? pluginPkg.hashCode() : 0);
43 | return result;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/environment/PlugInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 HouKx
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 | package androidx.pluginmgr.environment;
17 |
18 | import android.app.Application;
19 | import android.content.BroadcastReceiver;
20 | import android.content.pm.ActivityInfo;
21 | import android.content.pm.PackageInfo;
22 | import android.content.pm.ResolveInfo;
23 | import android.content.pm.ServiceInfo;
24 | import android.content.res.AssetManager;
25 | import android.content.res.Resources;
26 |
27 | import java.io.Serializable;
28 | import java.util.ArrayList;
29 | import java.util.Collection;
30 | import java.util.HashMap;
31 | import java.util.List;
32 | import java.util.Map;
33 |
34 | import androidx.pluginmgr.utils.Trace;
35 |
36 | /**
37 | * 插件Bean
38 | *
39 | * @author HouKangxi
40 | * @author Lody
41 | *
42 | */
43 | public class PlugInfo implements Serializable {
44 |
45 | // ================== FIELDS ==================
46 | private String id;
47 | private String filePath;
48 | private PackageInfo packageInfo;
49 | private Map activities;
50 | private ResolveInfo mainActivity;
51 | private List services;
52 | private List receivers;
53 | private List providers;
54 |
55 | private transient ClassLoader classLoader;
56 | private transient Application application;
57 | private transient AssetManager assetManager;
58 | private transient Resources resources;
59 | private transient boolean isApplicationOnCreated;
60 |
61 | public String getPackageName() {
62 | return packageInfo.packageName;
63 | }
64 |
65 |
66 | public ActivityInfo findActivityByClassNameFromPkg(String actName) {
67 | if (actName.startsWith(".")) {
68 | actName = getPackageName() + actName;
69 | }
70 | if (packageInfo.activities == null) {
71 | return null;
72 | }
73 | for (ActivityInfo act : packageInfo.activities) {
74 | if(act.name.equals(actName)){
75 | return act;
76 | }
77 | }
78 | return null;
79 | }
80 | public ActivityInfo findActivityByClassName(String actName) {
81 | if (packageInfo.activities == null) {
82 | return null;
83 | }
84 | if (actName.startsWith(".")) {
85 | actName = getPackageName() + actName;
86 | }
87 | ResolveInfo act = activities.get(actName);
88 | if (act == null) {
89 | return null;
90 | }
91 | return act.activityInfo;
92 | }
93 |
94 | public ActivityInfo findActivityByAction(String action) {
95 | if (activities == null || activities.isEmpty()) {
96 | return null;
97 | }
98 |
99 | for (ResolveInfo act : activities.values()) {
100 | if (act.filter != null && act.filter.hasAction(action)) {
101 | return act.activityInfo;
102 | }
103 | }
104 | return null;
105 | }
106 |
107 | public ActivityInfo findReceiverByClassName(String className) {
108 | if (packageInfo.receivers == null) {
109 | return null;
110 | }
111 | for (ActivityInfo receiver : packageInfo.receivers) {
112 | if (receiver.name.equals(className)) {
113 | return receiver;
114 | }
115 | }
116 | return null;
117 |
118 | }
119 | public ServiceInfo findServiceByClassName(String className) {
120 | if (packageInfo.services == null) {
121 | return null;
122 | }
123 | for (ServiceInfo service : packageInfo.services) {
124 | if (service.name.equals(className)) {
125 | return service;
126 | }
127 | }
128 | return null;
129 |
130 | }
131 | public ServiceInfo findServiceByAction(String action) {
132 | if (services == null || services.isEmpty()) {
133 | return null;
134 | }
135 | for (ResolveInfo ser : services) {
136 | if (ser.filter != null && ser.filter.hasAction(action)) {
137 | return ser.serviceInfo;
138 | }
139 | }
140 | return null;
141 | }
142 | public void addActivity(ResolveInfo activity) {
143 | if (activities == null) {
144 | activities = new HashMap(15);
145 | }
146 | fixActivityInfo(activity.activityInfo);
147 | activities.put(activity.activityInfo.name,activity);
148 | if (mainActivity == null && activity.filter != null
149 | && activity.filter.hasAction("android.intent.action.MAIN")
150 | && activity.filter.hasCategory("android.intent.category.LAUNCHER")
151 | ) {
152 | mainActivity = activity;
153 | }
154 | }
155 |
156 | private void fixActivityInfo(ActivityInfo activityInfo) {
157 | if (activityInfo != null) {
158 | if (activityInfo.name.startsWith(".")) {
159 | activityInfo.name = getPackageName() + activityInfo.name;
160 | }
161 | }
162 | }
163 |
164 | public void addReceiver(ResolveInfo receiver) {
165 | if (receivers == null) {
166 | receivers = new ArrayList();
167 | }
168 | receivers.add(receiver);
169 | }
170 |
171 | public void addService(ResolveInfo service) {
172 | if (services == null) {
173 | services = new ArrayList();
174 | }
175 | services.add(service);
176 | }
177 |
178 | public String getId() {
179 | return id;
180 | }
181 |
182 | public void setId(String id) {
183 | this.id = id;
184 | }
185 |
186 | public String getFilePath() {
187 | return filePath;
188 | }
189 |
190 | public void setFilePath(String filePath) {
191 | this.filePath = filePath;
192 | }
193 |
194 | public PackageInfo getPackageInfo() {
195 | return packageInfo;
196 | }
197 |
198 | public void setPackageInfo(PackageInfo packageInfo) {
199 | this.packageInfo = packageInfo;
200 | activities = new HashMap(packageInfo.activities.length);
201 | }
202 |
203 | public Application getApplication() {
204 | return application;
205 | }
206 |
207 | public void setApplication(Application application) {
208 | this.application = application;
209 | }
210 |
211 | public AssetManager getAssets() {
212 | return assetManager;
213 | }
214 |
215 | public void setAssetManager(AssetManager assetManager) {
216 | this.assetManager = assetManager;
217 | }
218 |
219 | public Resources getResources() {
220 | return resources;
221 | }
222 |
223 | public void setResources(Resources resources) {
224 | this.resources = resources;
225 | }
226 |
227 |
228 | public Collection getActivities() {
229 | if (activities == null) {
230 | return null;
231 | }
232 | return activities.values();
233 | }
234 |
235 | public List getServices() {
236 | return services;
237 | }
238 |
239 | public void setServices(List services) {
240 | this.services = services;
241 | }
242 |
243 | public List getProviders() {
244 | return providers;
245 | }
246 |
247 | public void setProviders(List providers) {
248 | this.providers = providers;
249 | }
250 |
251 | public ResolveInfo getMainActivity() {
252 | return mainActivity;
253 | }
254 |
255 | public List getReceivers() {
256 | return receivers;
257 | }
258 |
259 | public ClassLoader getClassLoader() {
260 | return classLoader;
261 | }
262 |
263 | public void setClassLoader(ClassLoader classLoader) {
264 | this.classLoader = classLoader;
265 | }
266 |
267 |
268 | @Override
269 | public int hashCode() {
270 | final int prime = 31;
271 | int result = 1;
272 | result = prime * result + ((id == null) ? 0 : id.hashCode());
273 | return result;
274 | }
275 |
276 | @Override
277 | public boolean equals(Object obj) {
278 | if (this == obj)
279 | return true;
280 | if (obj == null)
281 | return false;
282 | if (getClass() != obj.getClass())
283 | return false;
284 | PlugInfo other = (PlugInfo) obj;
285 | if (id == null) {
286 | if (other.id != null)
287 | return false;
288 | } else if (!id.equals(other.id))
289 | return false;
290 | return true;
291 | }
292 |
293 | public boolean isApplicationCreated() {
294 | //Fix at 2016/3/16
295 | return application != null || isApplicationOnCreated;
296 | }
297 |
298 | public void ensureApplicationCreated() {
299 | if (isApplicationCreated()) {
300 | synchronized (this) {
301 | try {
302 | application.onCreate();
303 | if (receivers != null && receivers.size() > 0) {
304 | for (ResolveInfo resolveInfo : receivers) {
305 | if (resolveInfo.activityInfo != null) {
306 | try {
307 | BroadcastReceiver broadcastReceiver = (BroadcastReceiver) classLoader.loadClass(resolveInfo.activityInfo.name).newInstance();
308 | application.registerReceiver(broadcastReceiver, resolveInfo.filter);
309 | } catch (Throwable e) {
310 | e.printStackTrace();
311 | Trace.store("Unable to create Receiver : " + resolveInfo.activityInfo.name);
312 | }
313 | }
314 | }
315 | }
316 | } catch (Throwable ignored) {
317 | }
318 | isApplicationOnCreated = true;
319 | }
320 | }
321 | }
322 |
323 | @Override
324 | public String toString() {
325 | return super.toString() + "[ id=" + id + ", pkg=" + getPackageName()
326 | + " ]";
327 | }
328 | }
329 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/environment/PluginClassLoader.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.environment;
2 |
3 | import android.annotation.TargetApi;
4 | import android.os.Build;
5 |
6 | import dalvik.system.DexClassLoader;
7 |
8 | /**
9 | * @author Lody
10 | * @version 1.0
11 | */
12 | @TargetApi(Build.VERSION_CODES.CUPCAKE)
13 | public class PluginClassLoader extends DexClassLoader {
14 |
15 | protected PlugInfo plugInfo;
16 |
17 | public PluginClassLoader(PlugInfo plugInfo, String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
18 | super(dexPath, optimizedDirectory, libraryPath, parent);
19 | this.plugInfo = plugInfo;
20 | }
21 |
22 | public PlugInfo getPlugInfo() {
23 | return plugInfo;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/environment/PluginContext.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.environment;
2 |
3 | import android.content.Context;
4 | import android.content.ContextWrapper;
5 | import android.content.res.AssetManager;
6 | import android.content.res.Resources;
7 |
8 | import androidx.pluginmgr.Globals;
9 | import androidx.pluginmgr.delegate.LayoutInflaterProxyContext;
10 |
11 | /**
12 | * @author Lody
13 | * @version 1.0
14 | */
15 | public class PluginContext extends LayoutInflaterProxyContext {
16 |
17 | private PlugInfo plugInfo;
18 |
19 | public PluginContext(Context hostContext, PlugInfo plugInfo) {
20 | super(hostContext);
21 | if (plugInfo == null) {
22 | throw new IllegalStateException("Create a plugin context, but not given host context!");
23 | }
24 | this.plugInfo = plugInfo;
25 | }
26 |
27 | @Override
28 | public Resources getResources() {
29 | return plugInfo.getResources();
30 | }
31 |
32 | @Override
33 | public AssetManager getAssets() {
34 | return plugInfo.getAssets();
35 | }
36 |
37 | @Override
38 | public ClassLoader getClassLoader() {
39 | return plugInfo.getClassLoader();
40 | }
41 |
42 | @Override
43 | public Context getBaseContext() {
44 | return getBaseContextInner(super.getBaseContext());
45 | }
46 |
47 | private Context getBaseContextInner(Context baseContext) {
48 | Context realBaseContext = baseContext;
49 | while (realBaseContext instanceof ContextWrapper) {
50 | realBaseContext = ((ContextWrapper) realBaseContext).getBaseContext();
51 | }
52 | return realBaseContext;
53 | }
54 |
55 | @Override
56 | public Object getSystemService(String name) {
57 | if (name.equals(Globals.GET_HOST_CONTEXT)) {
58 | return super.getBaseContext();
59 | }else if (name.equals(Globals.GET_HOST_RESOURCE)) {
60 | return plugInfo.getResources();
61 | }else if (name.equals(Globals.GET_HOST_ASSETS)) {
62 | return plugInfo.getAssets();
63 | }else if (name.equals(Globals.GET_HOST_CLASS_LOADER)) {
64 | return plugInfo.getClassLoader();
65 | }else if (name.equals(Globals.GET_PLUGIN_PATH)) {
66 | return plugInfo.getFilePath();
67 | }else if (name.equals(Globals.GET_PLUGIN_PKG_NAME)) {
68 | return plugInfo.getPackageName();
69 | }else if (name.equals(Globals.GET_PLUGIN_PKG_INFO)) {
70 | return plugInfo.getPackageInfo();
71 | }
72 | return super.getSystemService(name);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/environment/PluginInstrumentation.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.environment;
2 |
3 | import android.app.Activity;
4 | import android.app.Fragment;
5 | import android.app.Instrumentation;
6 | import android.content.ComponentName;
7 | import android.content.Context;
8 | import android.content.ContextWrapper;
9 | import android.content.Intent;
10 | import android.content.pm.ActivityInfo;
11 | import android.os.Bundle;
12 | import android.os.IBinder;
13 | import android.util.Log;
14 | import android.view.ContextThemeWrapper;
15 | import android.view.LayoutInflater;
16 | import android.view.Window;
17 |
18 | import java.lang.reflect.Field;
19 |
20 | import androidx.pluginmgr.Globals;
21 | import androidx.pluginmgr.PluginManager;
22 | import androidx.pluginmgr.delegate.DelegateInstrumentation;
23 | import androidx.pluginmgr.reflect.Reflect;
24 | import androidx.pluginmgr.reflect.ReflectException;
25 | import androidx.pluginmgr.utils.Trace;
26 | import androidx.pluginmgr.verify.PluginNotFoundException;
27 | import androidx.pluginmgr.widget.LayoutInflaterWrapper;
28 |
29 | /**
30 | * @author Lody
31 | * @version 1.0
32 | */
33 | public class PluginInstrumentation extends DelegateInstrumentation
34 | {
35 |
36 | /**
37 | * 当前正在运行的插件
38 | */
39 | private PlugInfo currentPlugin;
40 |
41 | /**
42 | * @param mBase 真正的Instrumentation
43 | */
44 | public PluginInstrumentation(Instrumentation mBase)
45 | {
46 | super(mBase);
47 | }
48 |
49 | @Override
50 | public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException
51 | {
52 | CreateActivityData activityData = (CreateActivityData) intent.getSerializableExtra(Globals.FLAG_ACTIVITY_FROM_PLUGIN);
53 | //如果activityData存在,那么说明将要创建的是插件Activity
54 | if (activityData != null && PluginManager.getSingleton().getPlugins().size() > 0) {
55 | //这里找不到插件信息就会抛异常的,不用担心空指针
56 | PlugInfo plugInfo;
57 | try
58 | {
59 | Log.d(getClass().getSimpleName(), "+++ Start Plugin Activity => " + activityData.pluginPkg + " / " + activityData.activityName);
60 | plugInfo = PluginManager.getSingleton().tryGetPluginInfo(activityData.pluginPkg);
61 | plugInfo.ensureApplicationCreated();
62 | }
63 | catch (PluginNotFoundException e)
64 | {
65 | PluginManager.getSingleton().dump();
66 | throw new IllegalAccessException("Cannot get plugin Info : " + activityData.pluginPkg);
67 | }
68 | if (activityData.activityName != null)
69 | {
70 | className = activityData.activityName;
71 | cl = plugInfo.getClassLoader();
72 | }
73 | }
74 | return super.newActivity(cl, className, intent);
75 | }
76 |
77 |
78 |
79 | @Override
80 | public void callActivityOnCreate(Activity activity, Bundle icicle)
81 | {
82 | lookupActivityInPlugin(activity);
83 | if (currentPlugin != null)
84 | {
85 | //初始化插件Activity
86 | Context baseContext = activity.getBaseContext();
87 | PluginContext pluginContext = new PluginContext(baseContext, currentPlugin);
88 | try
89 | {
90 | try
91 | {
92 | //在许多设备上,Activity自身hold资源
93 | Reflect.on(activity).set("mResources", pluginContext.getResources());
94 |
95 | }
96 | catch (Throwable ignored)
97 | {}
98 |
99 | Field field = ContextWrapper.class.getDeclaredField("mBase");
100 | field.setAccessible(true);
101 | field.set(activity, pluginContext);
102 | try {
103 | Reflect.on(activity).set("mApplication", currentPlugin.getApplication());
104 | }catch (ReflectException e) {
105 | Trace.store("Application not inject success into : " + activity);
106 | }
107 | }
108 | catch (Throwable e)
109 | {
110 | e.printStackTrace();
111 | }
112 |
113 | ActivityInfo activityInfo = currentPlugin.findActivityByClassName(activity.getClass().getName());
114 | if (activityInfo != null)
115 | {
116 | //根据AndroidManifest.xml中的参数设置Theme
117 | int resTheme = activityInfo.getThemeResource();
118 | if (resTheme != 0) {
119 | boolean hasNotSetTheme = true;
120 | try {
121 | Field mTheme = ContextThemeWrapper.class
122 | .getDeclaredField("mTheme");
123 | mTheme.setAccessible(true);
124 | hasNotSetTheme = mTheme.get(activity) == null;
125 | } catch (Exception e) {
126 | e.printStackTrace();
127 | }
128 | if (hasNotSetTheme) {
129 | changeActivityInfo(activityInfo, activity);
130 | activity.setTheme(resTheme);
131 | }
132 | }
133 |
134 | }
135 |
136 | // 如果是三星手机,则使用包装的LayoutInflater替换原LayoutInflater
137 | // 这款手机在解析内置的布局文件时有各种错误
138 | if (android.os.Build.MODEL.startsWith("GT")) {
139 | Window window = activity.getWindow();
140 | Reflect windowRef = Reflect.on(window);
141 | try {
142 | LayoutInflater originInflater = window.getLayoutInflater();
143 | if (!(originInflater instanceof LayoutInflaterWrapper)) {
144 | windowRef.set("mLayoutInflater", new LayoutInflaterWrapper(originInflater));
145 | }
146 | } catch (Throwable e) {
147 | e.printStackTrace();
148 | }
149 | }
150 | }
151 |
152 |
153 | super.callActivityOnCreate(activity, icicle);
154 | }
155 |
156 | @Override
157 | public void callActivityOnResume(Activity activity)
158 | {
159 | lookupActivityInPlugin(activity);
160 | super.callActivityOnResume(activity);
161 | }
162 |
163 | private static void changeActivityInfo(ActivityInfo activityInfo, Activity activity) {
164 | Field field_mActivityInfo;
165 | try {
166 | field_mActivityInfo = Activity.class.getDeclaredField("mActivityInfo");
167 | field_mActivityInfo.setAccessible(true);
168 | } catch (Exception e) {
169 | e.printStackTrace();
170 | return;
171 | }
172 | try {
173 | field_mActivityInfo.set(activity, activityInfo);
174 | } catch (Exception e) {
175 | e.printStackTrace();
176 | }
177 |
178 | }
179 |
180 | @Override
181 | public void callActivityOnDestroy(Activity activity)
182 | {
183 | super.callActivityOnDestroy(activity);
184 | }
185 |
186 | /**
187 | * 检查跳转目标是不是来自插件
188 | *
189 | * @param activity Activity
190 | */
191 | private void lookupActivityInPlugin(Activity activity)
192 | {
193 | ClassLoader classLoader = activity.getClass().getClassLoader();
194 | if (classLoader instanceof PluginClassLoader)
195 | {
196 | currentPlugin = ((PluginClassLoader) classLoader).getPlugInfo();
197 | }
198 | else
199 | {
200 | currentPlugin = null;
201 | }
202 | }
203 |
204 | private void replaceIntentTargetIfNeed(Context from, Intent intent)
205 | {
206 | if (!intent.hasExtra(Globals.FLAG_ACTIVITY_FROM_PLUGIN) && currentPlugin != null)
207 | {
208 | ComponentName componentName = intent.getComponent();
209 | if (componentName != null)
210 | {
211 | String pkgName = componentName.getPackageName();
212 | String activityName = componentName.getClassName();
213 | if (pkgName != null)
214 | {
215 | CreateActivityData createActivityData = new CreateActivityData(activityName, currentPlugin.getPackageName());
216 | ActivityInfo activityInfo = currentPlugin.findActivityByClassName(activityName);
217 | if (activityInfo != null) {
218 | intent.setClass(from, PluginManager.getSingleton().getActivitySelector().selectDynamicActivity(activityInfo));
219 | intent.putExtra(Globals.FLAG_ACTIVITY_FROM_PLUGIN, createActivityData);
220 | intent.setExtrasClassLoader(currentPlugin.getClassLoader());
221 | }
222 | }
223 | }
224 | }
225 | }
226 |
227 |
228 | @Override
229 | public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment fragment, Intent intent, int requestCode)
230 | {
231 | replaceIntentTargetIfNeed(who, intent);
232 | return super.execStartActivity(who, contextThread, token, fragment, intent, requestCode);
233 | }
234 |
235 |
236 | @Override
237 | public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment fragment, Intent intent, int requestCode, Bundle options)
238 | {
239 | replaceIntentTargetIfNeed(who, intent);
240 | return super.execStartActivity(who, contextThread, token, fragment, intent, requestCode, options);
241 | }
242 |
243 | @Override
244 | public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode)
245 | {
246 | replaceIntentTargetIfNeed(who, intent);
247 | return super.execStartActivity(who, contextThread, token, target, intent, requestCode);
248 | }
249 |
250 | @Override
251 | public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options)
252 | {
253 | replaceIntentTargetIfNeed(who, intent);
254 | return super.execStartActivity(who, contextThread, token, target, intent, requestCode, options);
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/environment/ServiceEnvironment.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.environment;
2 |
3 | /**
4 | * @author Lody
5 | * @version 1.0
6 | *
7 | * TODO: Service support
8 | *
9 | */
10 | public class ServiceEnvironment {
11 |
12 |
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/reflect/NULL.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.reflect;
2 |
3 | /**
4 | * 用来表示null的类.
5 | *
6 | * @author Lody
7 | * @version 1.0
8 | */
9 | public class NULL {
10 | }
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/reflect/Reflect.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.reflect;
2 |
3 | import android.annotation.SuppressLint;
4 |
5 | import java.lang.reflect.AccessibleObject;
6 | import java.lang.reflect.Constructor;
7 | import java.lang.reflect.Field;
8 | import java.lang.reflect.InvocationHandler;
9 | import java.lang.reflect.Member;
10 | import java.lang.reflect.Method;
11 | import java.lang.reflect.Modifier;
12 | import java.lang.reflect.Proxy;
13 | import java.util.Arrays;
14 | import java.util.LinkedHashMap;
15 | import java.util.Map;
16 |
17 | /**
18 | * 一个拥有流畅特性(Fluent-API)的反射工具类,
19 | * 使用起来就像直接调用一样流畅易懂.
20 | *
21 | * @author Lody
22 | */
23 | @SuppressLint("DefaultLocale")
24 | public class Reflect {
25 |
26 |
27 | private final Object object;
28 | private final boolean isClass;
29 |
30 | private Reflect(Class> type) {
31 | this.object = type;
32 | this.isClass = true;
33 | }
34 |
35 | private Reflect(Object object) {
36 | this.object = object;
37 | this.isClass = false;
38 | }
39 |
40 | /**
41 | * 根据指定的类名构建反射工具类
42 | *
43 | * @param name 类的全名
44 | * @return 反射工具类
45 | * @throws ReflectException 如果反射出现意外
46 | * @see #on(Class)
47 | */
48 | public static Reflect on(String name) throws ReflectException {
49 | return on(forName(name));
50 | }
51 |
52 | /**
53 | * 从指定的类加载起寻找类,并构建反射工具类
54 | *
55 | * @param name 类的全名
56 | * @param classLoader 需要构建工具类的类的类加载器
57 | * loaded.
58 | * @return 反射工具类
59 | * @throws ReflectException 如果反射出现意外
60 | * @see #on(Class)
61 | */
62 | public static Reflect on(String name, ClassLoader classLoader) throws ReflectException {
63 | return on(forName(name, classLoader));
64 | }
65 |
66 | /**
67 | * 根据指定的类构建反射工具类
68 | *
69 | * 当你需要访问静态字段的时候本方法适合你,
70 | * 你还可以通过调用 {@link #create(Object...)} 创建一个对象.
71 | *
72 | * @param clazz 需要构建反射工具类的类
73 | * @return 反射工具类
74 | */
75 | public static Reflect on(Class> clazz) {
76 | return new Reflect(clazz);
77 | }
78 |
79 | // ---------------------------------------------------------------------
80 | // 构造器
81 | // ---------------------------------------------------------------------
82 |
83 | /**
84 | * Wrap an object.
85 | *
86 | * Use this when you want to access instance fields and methods on any
87 | * {@link Object}
88 | *
89 | * @param object The object to be wrapped
90 | * @return A wrapped object, to be used for further reflection.
91 | */
92 | public static Reflect on(Object object) {
93 | return new Reflect(object);
94 | }
95 |
96 | /**
97 | * 让一个{@link AccessibleObject}可访问.
98 | *
99 | * @param accessible
100 | * @param
101 | * @return
102 | */
103 | public static T accessible(T accessible) {
104 | if (accessible == null) {
105 | return null;
106 | }
107 |
108 | if (accessible instanceof Member) {
109 | Member member = (Member) accessible;
110 |
111 | if (Modifier.isPublic(member.getModifiers()) &&
112 | Modifier.isPublic(member.getDeclaringClass().getModifiers())) {
113 |
114 | return accessible;
115 | }
116 | }
117 | if (!accessible.isAccessible()) {
118 | accessible.setAccessible(true);
119 | }
120 |
121 | return accessible;
122 | }
123 |
124 | // ---------------------------------------------------------------------
125 | // Fluent Reflection API
126 | // ---------------------------------------------------------------------
127 |
128 | /**
129 | * 将给定字符串的开头改为小写.
130 | *
131 | * @param string
132 | * @return
133 | */
134 | @SuppressLint("DefaultLocale")
135 | private static String property(String string) {
136 | int length = string.length();
137 |
138 | if (length == 0) {
139 | return "";
140 | } else if (length == 1) {
141 | return string.toLowerCase();
142 | } else {
143 | return string.substring(0, 1).toLowerCase() + string.substring(1);
144 | }
145 | }
146 |
147 | private static Reflect on(Constructor> constructor, Object... args) throws ReflectException {
148 | try {
149 | return on(accessible(constructor).newInstance(args));
150 | } catch (Exception e) {
151 | throw new ReflectException(e);
152 | }
153 | }
154 |
155 | private static Reflect on(Method method, Object object, Object... args) throws ReflectException {
156 | try {
157 | accessible(method);
158 |
159 | if (method.getReturnType() == void.class) {
160 | method.invoke(object, args);
161 | return on(object);
162 | } else {
163 | return on(method.invoke(object, args));
164 | }
165 | } catch (Exception e) {
166 | throw new ReflectException(e);
167 | }
168 | }
169 |
170 | /**
171 | * 取得内部维护的对象.
172 | */
173 | private static Object unwrap(Object object) {
174 | if (object instanceof Reflect) {
175 | return ((Reflect) object).get();
176 | }
177 |
178 | return object;
179 | }
180 |
181 | /**
182 | * 将Object数组转换为其类型的数组.
183 | * 如果对象中包含null,我们用NULL.class代替.
184 | *
185 | * @see Object#getClass()
186 | */
187 | private static Class>[] types(Object... values) {
188 | if (values == null) {
189 | return new Class[0];
190 | }
191 |
192 | Class>[] result = new Class[values.length];
193 |
194 | for (int i = 0; i < values.length; i++) {
195 | Object value = values[i];
196 | result[i] = value == null ? NULL.class : value.getClass();
197 | }
198 |
199 | return result;
200 | }
201 |
202 | /**
203 | * 取得一个类,此操作会初始化类的static区域.
204 | *
205 | * @see Class#forName(String)
206 | */
207 | private static Class> forName(String name) throws ReflectException {
208 | try {
209 | return Class.forName(name);
210 | } catch (Exception e) {
211 | throw new ReflectException(e);
212 | }
213 | }
214 |
215 | private static Class> forName(String name, ClassLoader classLoader) throws ReflectException {
216 | try {
217 | return Class.forName(name, true, classLoader);
218 | } catch (Exception e) {
219 | throw new ReflectException(e);
220 | }
221 | }
222 |
223 | /**
224 | * 如果给定的Class是原始类型,那么将其包装为对象类型,
225 | * 否则返回本身.
226 | */
227 | public static Class> wrapper(Class> type) {
228 | if (type == null) {
229 | return null;
230 | } else if (type.isPrimitive()) {
231 | if (boolean.class == type) {
232 | return Boolean.class;
233 | } else if (int.class == type) {
234 | return Integer.class;
235 | } else if (long.class == type) {
236 | return Long.class;
237 | } else if (short.class == type) {
238 | return Short.class;
239 | } else if (byte.class == type) {
240 | return Byte.class;
241 | } else if (double.class == type) {
242 | return Double.class;
243 | } else if (float.class == type) {
244 | return Float.class;
245 | } else if (char.class == type) {
246 | return Character.class;
247 | } else if (void.class == type) {
248 | return Void.class;
249 | }
250 | }
251 |
252 | return type;
253 | }
254 |
255 | /**
256 | * 取得内部维护的实际对象
257 | *
258 | * @param
259 | * @return
260 | */
261 | @SuppressWarnings("unchecked")
262 | public T get() {
263 | return (T) object;
264 | }
265 |
266 | /**
267 | * 设置指定字段为指定值
268 | *
269 | * @param name
270 | * @param value
271 | * @return
272 | * @throws ReflectException
273 | */
274 | public Reflect set(String name, Object value) throws ReflectException {
275 | try {
276 | Field field = field0(name);
277 | field.setAccessible(true);
278 | field.set(object, unwrap(value));
279 | return this;
280 | } catch (Exception e) {
281 | throw new ReflectException(e);
282 | }
283 | }
284 |
285 | /**
286 | * @param name
287 | * @param
288 | * @return
289 | * @throws ReflectException
290 | */
291 | public T get(String name) throws ReflectException {
292 | return field(name).get();
293 | }
294 |
295 | /**
296 | * 取得指定名称的字段
297 | *
298 | * @param name
299 | * @return
300 | * @throws ReflectException
301 | */
302 | public Reflect field(String name) throws ReflectException {
303 | try {
304 | Field field = field0(name);
305 | return on(field.get(object));
306 | } catch (Exception e) {
307 | throw new ReflectException(e);
308 | }
309 | }
310 |
311 | private Field field0(String name) throws ReflectException {
312 | Class> type = type();
313 |
314 | // 先尝试取得公有字段
315 | try {
316 | return type.getField(name);
317 | }
318 |
319 | //此时尝试非公有字段
320 | catch (NoSuchFieldException e) {
321 | do {
322 | try {
323 | return accessible(type.getDeclaredField(name));
324 | } catch (NoSuchFieldException ignore) {
325 | }
326 |
327 | type = type.getSuperclass();
328 | }
329 | while (type != null);
330 |
331 | throw new ReflectException(e);
332 | }
333 | }
334 |
335 | /**
336 | * 取得一个Map,map中的key为字段名,value为字段对应的反射工具类
337 | *
338 | * @return
339 | */
340 | public Map fields() {
341 | Map result = new LinkedHashMap();
342 | Class> type = type();
343 |
344 | do {
345 | for (Field field : type.getDeclaredFields()) {
346 | if (!isClass ^ Modifier.isStatic(field.getModifiers())) {
347 | String name = field.getName();
348 |
349 | if (!result.containsKey(name))
350 | result.put(name, field(name));
351 | }
352 | }
353 |
354 | type = type.getSuperclass();
355 | }
356 | while (type != null);
357 |
358 | return result;
359 | }
360 |
361 | /**
362 | * 调用指定的无参数方法
363 | *
364 | * @param name
365 | * @return
366 | * @throws ReflectException
367 | */
368 | public Reflect call(String name) throws ReflectException {
369 | return call(name, new Object[0]);
370 | }
371 |
372 | /**
373 | * 调用方法根据传入的参数
374 | *
375 | * @param name
376 | * @param args
377 | * @return
378 | * @throws ReflectException
379 | */
380 | public Reflect call(String name, Object... args) throws ReflectException {
381 | Class>[] types = types(args);
382 |
383 |
384 | try {
385 | Method method = exactMethod(name, types);
386 | return on(method, object, args);
387 | } catch (NoSuchMethodException e) {
388 | try {
389 | Method method = similarMethod(name, types);
390 | return on(method, object, args);
391 | } catch (NoSuchMethodException e1) {
392 | throw new ReflectException(e1);
393 | }
394 | }
395 | }
396 |
397 | private Method exactMethod(String name, Class>[] types) throws NoSuchMethodException {
398 | Class> type = type();
399 |
400 | try {
401 | return type.getMethod(name, types);
402 | } catch (NoSuchMethodException e) {
403 | do {
404 | try {
405 | return type.getDeclaredMethod(name, types);
406 | } catch (NoSuchMethodException ignore) {
407 | }
408 |
409 | type = type.getSuperclass();
410 | }
411 | while (type != null);
412 |
413 | throw new NoSuchMethodException();
414 | }
415 | }
416 |
417 | /**
418 | * 根据参数和名称匹配方法,如果找不到方法,
419 | */
420 | private Method similarMethod(String name, Class>[] types) throws NoSuchMethodException {
421 | Class> type = type();
422 |
423 | for (Method method : type.getMethods()) {
424 | if (isSimilarSignature(method, name, types)) {
425 | return method;
426 | }
427 | }
428 |
429 | do {
430 | for (Method method : type.getDeclaredMethods()) {
431 | if (isSimilarSignature(method, name, types)) {
432 | return method;
433 | }
434 | }
435 |
436 | type = type.getSuperclass();
437 | }
438 | while (type != null);
439 |
440 | throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type " + type() + ".");
441 | }
442 |
443 | private boolean isSimilarSignature(Method possiblyMatchingMethod, String desiredMethodName, Class>[] desiredParamTypes) {
444 | return possiblyMatchingMethod.getName().equals(desiredMethodName) && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes);
445 | }
446 |
447 | /**
448 | * 创建一个实例通过默认构造器
449 | *
450 | * @return
451 | * @throws ReflectException
452 | */
453 | public Reflect create() throws ReflectException {
454 | return create(new Object[0]);
455 | }
456 |
457 | /**
458 | * 创建一个实例根据传入的参数
459 | *
460 | * @param args
461 | * @return
462 | * @throws ReflectException
463 | */
464 | public Reflect create(Object... args) throws ReflectException {
465 | Class>[] types = types(args);
466 |
467 |
468 | try {
469 | Constructor> constructor = type().getDeclaredConstructor(types);
470 | return on(constructor, args);
471 | } catch (NoSuchMethodException e) {
472 | for (Constructor> constructor : type().getDeclaredConstructors()) {
473 | if (match(constructor.getParameterTypes(), types)) {
474 | return on(constructor, args);
475 | }
476 | }
477 |
478 | throw new ReflectException(e);
479 | }
480 | }
481 |
482 | /**
483 | * 创建一个动态代理根据传入的类型.
484 | * 如果我们正在维护的是一个Map,那么当调用出现异常时我们将从Map中取值.
485 | *
486 | * @param proxyType 需要动态代理的类型
487 | * @return 动态代理生成的对象
488 | */
489 | @SuppressWarnings("unchecked")
490 | public P as(Class
proxyType) {
491 | final boolean isMap = (object instanceof Map);
492 | final InvocationHandler handler = new InvocationHandler() {
493 | @Override
494 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
495 | String name = method.getName();
496 | try {
497 | return on(object).call(name, args).get();
498 | } catch (ReflectException e) {
499 | if (isMap) {
500 | Map map = (Map) object;
501 | int length = (args == null ? 0 : args.length);
502 |
503 | if (length == 0 && name.startsWith("get")) {
504 | return map.get(property(name.substring(3)));
505 | } else if (length == 0 && name.startsWith("is")) {
506 | return map.get(property(name.substring(2)));
507 | } else if (length == 1 && name.startsWith("set")) {
508 | map.put(property(name.substring(3)), args[0]);
509 | return null;
510 | }
511 | }
512 |
513 | throw e;
514 | }
515 | }
516 | };
517 |
518 | return (P) Proxy.newProxyInstance(proxyType.getClassLoader(), new Class[]{proxyType}, handler);
519 | }
520 |
521 | /**
522 | * 检查两个数组的类型是否匹配,如果数组中包含原始类型,将它们转换为对应的包装类型.
523 | */
524 | private boolean match(Class>[] declaredTypes, Class>[] actualTypes) {
525 | if (declaredTypes.length == actualTypes.length) {
526 | for (int i = 0; i < actualTypes.length; i++) {
527 | if (actualTypes[i] == NULL.class)
528 | continue;
529 |
530 | if (wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i])))
531 | continue;
532 |
533 | return false;
534 | }
535 |
536 | return true;
537 | } else {
538 | return false;
539 | }
540 | }
541 |
542 | /**
543 | * {@inheritDoc}
544 | */
545 | @Override
546 | public int hashCode() {
547 | return object.hashCode();
548 | }
549 |
550 | /**
551 | * {@inheritDoc}
552 | */
553 | @Override
554 | public boolean equals(Object obj) {
555 | if (obj instanceof Reflect) {
556 | return object.equals(((Reflect) obj).get());
557 | }
558 |
559 | return false;
560 | }
561 |
562 | /**
563 | * {@inheritDoc}
564 | */
565 | @Override
566 | public String toString() {
567 | return object.toString();
568 | }
569 |
570 | /**
571 | * 取得我们正在反射的对象的类型.
572 | *
573 | * @see Object#getClass()
574 | */
575 | public Class> type() {
576 | if (isClass) {
577 | return (Class>) object;
578 | } else {
579 | return object.getClass();
580 | }
581 | }
582 |
583 |
584 | }
585 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/reflect/ReflectException.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.reflect;
2 |
3 | /**
4 | * @author Lody
5 | */
6 | public class ReflectException extends RuntimeException {
7 |
8 | private static final long serialVersionUID = 663038727503637969L;
9 |
10 | public ReflectException(String message) {
11 | super(message);
12 | }
13 |
14 | public ReflectException(String message, Throwable cause) {
15 | super(message, cause);
16 | }
17 |
18 | public ReflectException() {
19 | super();
20 | }
21 |
22 | public ReflectException(Throwable cause) {
23 | super(cause);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/selector/DefaultActivitySelector.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.selector;
2 |
3 | import android.app.Activity;
4 | import android.content.pm.ActivityInfo;
5 |
6 | import androidx.pluginmgr.DynamicActivity;
7 |
8 | /**
9 | * @author Lody
10 | * @version 1.0
11 | */
12 | public class DefaultActivitySelector implements DynamicActivitySelector {
13 |
14 | private static DynamicActivitySelector DEFAULT = new DefaultActivitySelector();
15 |
16 | @Override
17 | public Class extends Activity> selectDynamicActivity(ActivityInfo pluginActivityInfo) {
18 | return DynamicActivity.class;
19 | }
20 |
21 | public static DynamicActivitySelector getDefault() {
22 | return DEFAULT;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/selector/DynamicActivitySelector.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.selector;
2 |
3 | import android.app.Activity;
4 | import android.content.pm.ActivityInfo;
5 |
6 | /**
7 | * @author Lody
8 | * @version 1.0
9 | */
10 | public interface DynamicActivitySelector {
11 |
12 | Class extends Activity> selectDynamicActivity(ActivityInfo pluginActivityInfo);
13 |
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/utils/FileUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 HouKx
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 | package androidx.pluginmgr.utils;
17 |
18 | import java.io.BufferedOutputStream;
19 | import java.io.File;
20 | import java.io.FileInputStream;
21 | import java.io.FileOutputStream;
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.nio.channels.FileChannel;
25 |
26 | /**
27 | * 文件操作工具类
28 | *
29 | * 使用nio以提高性能
30 | *
31 | * @author HouKangxi
32 | *
33 | */
34 | public class FileUtil {
35 |
36 | public static void writeToFile(InputStream dataIns, File target) throws IOException {
37 | final int BUFFER = 1024;
38 | BufferedOutputStream bos = new BufferedOutputStream(
39 | new FileOutputStream(target));
40 | int count;
41 | byte data[] = new byte[BUFFER];
42 | while ((count = dataIns.read(data, 0, BUFFER)) != -1) {
43 | bos.write(data, 0, count);
44 | }
45 | bos.close();
46 | }
47 |
48 | /**
49 | *
50 | * 复制文件
51 | *
52 | * @param source
53 | * - 源文件
54 | *
55 | * @param target
56 | * - 目标文件
57 | *
58 | */
59 | public static void copyFile(File source, File target) {
60 |
61 | FileInputStream fi = null;
62 | FileOutputStream fo = null;
63 |
64 | FileChannel in = null;
65 |
66 | FileChannel out = null;
67 |
68 | try {
69 | fi = new FileInputStream(source);
70 |
71 | fo = new FileOutputStream(target);
72 |
73 | in = fi.getChannel();// 得到对应的文件通道
74 |
75 | out = fo.getChannel();// 得到对应的文件通道
76 |
77 | in.transferTo(0, in.size(), out);// 连接两个通道,并且从in通道读取,然后写入out通道
78 |
79 | } catch (IOException e) {
80 | e.printStackTrace();
81 | } finally {
82 | try {
83 | if (fi != null) {
84 | fi.close();
85 | }
86 |
87 | if (in != null) {
88 | in.close();
89 | }
90 |
91 | if (fo != null) {
92 | fo.close();
93 | }
94 |
95 | if (out != null) {
96 | out.close();
97 | }
98 |
99 | } catch (IOException e) {
100 | e.printStackTrace();
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/utils/PluginManifestUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 HouKx
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 | package androidx.pluginmgr.utils;
17 |
18 | import android.content.Context;
19 | import android.content.IntentFilter;
20 | import android.content.pm.ApplicationInfo;
21 | import android.content.pm.PackageInfo;
22 | import android.content.pm.PackageManager;
23 | import android.content.pm.ResolveInfo;
24 | import android.os.Build;
25 |
26 | import org.xmlpull.v1.XmlPullParser;
27 | import org.xmlpull.v1.XmlPullParserException;
28 | import org.xmlpull.v1.XmlPullParserFactory;
29 |
30 | import java.io.File;
31 | import java.io.IOException;
32 | import java.io.StringReader;
33 | import java.util.Enumeration;
34 | import java.util.HashMap;
35 | import java.util.LinkedList;
36 | import java.util.List;
37 | import java.util.Map;
38 | import java.util.zip.ZipEntry;
39 | import java.util.zip.ZipFile;
40 |
41 | import androidx.pluginmgr.PluginManager;
42 | import androidx.pluginmgr.environment.PlugInfo;
43 |
44 | /**
45 | * @author HouKangxi
46 | *
47 | */
48 | public class PluginManifestUtil {
49 | public static void setManifestInfo(Context context, String apkPath, PlugInfo info)
50 | throws XmlPullParserException, IOException {
51 |
52 | ZipFile zipFile = new ZipFile(new File(apkPath), ZipFile.OPEN_READ);
53 | ZipEntry manifestXmlEntry = zipFile.getEntry(XmlManifestReader.DEFAULT_XML);
54 |
55 | String manifestXML = XmlManifestReader.getManifestXMLFromAPK(zipFile,
56 | manifestXmlEntry);
57 | PackageInfo pkgInfo = context.getPackageManager()
58 | .getPackageArchiveInfo(
59 | apkPath,
60 | PackageManager.GET_ACTIVITIES
61 | | PackageManager.GET_RECEIVERS//
62 | | PackageManager.GET_PROVIDERS//
63 | | PackageManager.GET_META_DATA//
64 | | PackageManager.GET_SHARED_LIBRARY_FILES//
65 | | PackageManager.GET_SERVICES//
66 | // | PackageManager.GET_SIGNATURES//
67 | );
68 | if (pkgInfo == null || pkgInfo.activities == null) {
69 | throw new XmlPullParserException("No any activity in " + apkPath);
70 | }
71 | pkgInfo.applicationInfo.publicSourceDir = apkPath;
72 | pkgInfo.applicationInfo.sourceDir = apkPath;
73 |
74 | File libDir = PluginManager.getSingleton().getPluginLibPath(info);
75 | try {
76 | if (extractLibFile(zipFile, libDir)) {
77 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
78 | pkgInfo.applicationInfo.nativeLibraryDir = libDir.getAbsolutePath();
79 | }
80 | }
81 | } finally {
82 | zipFile.close();
83 | }
84 | info.setPackageInfo(pkgInfo);
85 | setAttrs(info, manifestXML);
86 | }
87 | private static boolean extractLibFile(ZipFile zip, File tardir)
88 | throws IOException {
89 |
90 | if(!tardir.exists()){
91 | tardir.mkdirs();
92 | }
93 |
94 | String defaultArch = "armeabi";
95 | Map> archLibEntries = new HashMap>();
96 | for (Enumeration extends ZipEntry> e = zip.entries(); e
97 | .hasMoreElements();) {
98 | ZipEntry entry = e.nextElement();
99 | String name = entry.getName();
100 | if (name.startsWith("/")) {
101 | name = name.substring(1);
102 | }
103 | if (name.startsWith("lib/")) {
104 | if(entry.isDirectory()){
105 | continue;
106 | }
107 | int sp = name.indexOf('/', 4);
108 | String en2add;
109 | if (sp > 0) {
110 | String osArch = name.substring(4, sp);
111 | en2add=osArch.toLowerCase();
112 | } else {
113 | en2add=defaultArch;
114 | }
115 | List ents = archLibEntries.get(en2add);
116 | if (ents == null) {
117 | ents = new LinkedList();
118 | archLibEntries.put(en2add, ents);
119 | }
120 | ents.add(entry);
121 | }
122 | }
123 | String arch = System.getProperty("os.arch");
124 | List libEntries = archLibEntries.get(arch.toLowerCase());
125 | if (libEntries == null) {
126 | libEntries = archLibEntries.get(defaultArch);
127 | }
128 | boolean hasLib = false;
129 | if (libEntries != null) {
130 | hasLib = true;
131 | if (!tardir.exists()) {
132 | tardir.mkdirs();
133 | }
134 | for (ZipEntry libEntry : libEntries) {
135 | String ename = libEntry.getName();
136 | String pureName = ename.substring(ename.lastIndexOf('/') + 1);
137 | File target = new File(tardir, pureName);
138 | FileUtil.writeToFile(zip.getInputStream(libEntry), target);
139 | }
140 | }
141 |
142 | return hasLib;
143 | }
144 | private static void setAttrs(PlugInfo info, String manifestXML)
145 | throws XmlPullParserException, IOException {
146 | XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
147 | factory.setNamespaceAware(true);
148 | XmlPullParser parser = factory.newPullParser();
149 | parser.setInput(new StringReader(manifestXML));
150 | int eventType = parser.getEventType();
151 | String namespaceAndroid = null;
152 | do {
153 | switch (eventType) {
154 | case XmlPullParser.START_DOCUMENT: {
155 | break;
156 | }
157 | case XmlPullParser.START_TAG: {
158 | String tag = parser.getName();
159 | if (tag.equals("manifest")) {
160 | namespaceAndroid = parser.getNamespace("android");
161 | } else if ("activity".equals(parser.getName())) {
162 | addActivity(info, namespaceAndroid, parser);
163 | } else if ("receiver".equals(parser.getName())) {
164 | addReceiver(info, namespaceAndroid, parser);
165 | } else if ("service".equals(parser.getName())) {
166 | addService(info, namespaceAndroid, parser);
167 | }else if("application".equals(parser.getName())){
168 | parseApplicationInfo(info, namespaceAndroid, parser);
169 | }
170 | break;
171 | }
172 | case XmlPullParser.END_TAG: {
173 | break;
174 | }
175 | }
176 | eventType = parser.next();
177 | } while (eventType != XmlPullParser.END_DOCUMENT);
178 | }
179 |
180 | private static void parseApplicationInfo(PlugInfo info,
181 | String namespace, XmlPullParser parser) throws XmlPullParserException, IOException{
182 | String applicationName = parser.getAttributeValue(namespace, "name");
183 | String packageName = info.getPackageInfo().packageName;
184 | ApplicationInfo applicationInfo = info.getPackageInfo().applicationInfo;
185 | applicationInfo.name = getName(applicationName, packageName);
186 | }
187 |
188 | private static void addActivity(PlugInfo info, String namespace,
189 | XmlPullParser parser) throws XmlPullParserException, IOException {
190 | int eventType = parser.getEventType();
191 | String activityName = parser.getAttributeValue(namespace, "name");
192 | String packageName = info.getPackageInfo().packageName;
193 | activityName = getName(activityName, packageName);
194 | ResolveInfo act = new ResolveInfo();
195 | act.activityInfo = info.findActivityByClassNameFromPkg(activityName);
196 | do {
197 | switch (eventType) {
198 | case XmlPullParser.START_TAG: {
199 | String tag = parser.getName();
200 | if ("intent-filter".equals(tag)) {
201 | if (act.filter == null) {
202 | act.filter = new IntentFilter();
203 | }
204 | } else if ("action".equals(tag)) {
205 | String actionName = parser.getAttributeValue(namespace,
206 | "name");
207 | act.filter.addAction(actionName);
208 | } else if ("category".equals(tag)) {
209 | String category = parser.getAttributeValue(namespace,
210 | "name");
211 | act.filter.addCategory(category);
212 | } else if ("data".equals(tag)) {
213 | // TODO parse data
214 | }
215 | break;
216 | }
217 | }
218 | eventType = parser.next();
219 | } while (!"activity".equals(parser.getName()));
220 | //
221 | info.addActivity(act);
222 | }
223 |
224 | private static void addService(PlugInfo info, String namespace,
225 | XmlPullParser parser) throws XmlPullParserException, IOException {
226 | int eventType = parser.getEventType();
227 | String serviceName = parser.getAttributeValue(namespace, "name");
228 | String packageName = info.getPackageInfo().packageName;
229 | serviceName = getName(serviceName, packageName);
230 | ResolveInfo service = new ResolveInfo();
231 | service.serviceInfo = info.findServiceByClassName(serviceName);
232 | do {
233 | switch (eventType) {
234 | case XmlPullParser.START_TAG: {
235 | String tag = parser.getName();
236 | if ("intent-filter".equals(tag)) {
237 | if (service.filter == null) {
238 | service.filter = new IntentFilter();
239 | }
240 | } else if ("action".equals(tag)) {
241 | String actionName = parser.getAttributeValue(namespace,
242 | "name");
243 | service.filter.addAction(actionName);
244 | } else if ("category".equals(tag)) {
245 | String category = parser.getAttributeValue(namespace,
246 | "name");
247 | service.filter.addCategory(category);
248 | } else if ("data".equals(tag)) {
249 | // TODO parse data
250 | }
251 | break;
252 | }
253 | }
254 | eventType = parser.next();
255 | } while (!"service".equals(parser.getName()));
256 | //
257 | info.addService(service);
258 | }
259 |
260 | private static void addReceiver(PlugInfo info, String namespace,
261 | XmlPullParser parser) throws XmlPullParserException, IOException {
262 | int eventType = parser.getEventType();
263 | String receiverName = parser.getAttributeValue(namespace, "name");
264 | String packageName = info.getPackageInfo().packageName;
265 | receiverName = getName(receiverName, packageName);
266 | ResolveInfo receiver = new ResolveInfo();
267 | // 此时的activityInfo 表示 receiverInfo
268 | receiver.activityInfo = info.findReceiverByClassName(receiverName);
269 | do {
270 | switch (eventType) {
271 | case XmlPullParser.START_TAG: {
272 | String tag = parser.getName();
273 | if ("intent-filter".equals(tag)) {
274 | if (receiver.filter == null) {
275 | receiver.filter = new IntentFilter();
276 | }
277 | } else if ("action".equals(tag)) {
278 | String actionName = parser.getAttributeValue(namespace,
279 | "name");
280 | receiver.filter.addAction(actionName);
281 | } else if ("category".equals(tag)) {
282 | String category = parser.getAttributeValue(namespace,
283 | "name");
284 | receiver.filter.addCategory(category);
285 | } else if ("data".equals(tag)) {
286 | // TODO parse data
287 | }
288 | break;
289 | }
290 | }
291 | eventType = parser.next();
292 | } while (!"receiver".equals(parser.getName()));
293 | //
294 | info.addReceiver(receiver);
295 | }
296 |
297 | private static String getName(String nameOrig, String pkgName) {
298 | if (nameOrig == null) {
299 | return null;
300 | }
301 | StringBuilder sb;
302 | if (nameOrig.startsWith(".")) {
303 | sb = new StringBuilder();
304 | sb.append(pkgName);
305 | sb.append(nameOrig);
306 | } else if (!nameOrig.contains(".")) {
307 | sb = new StringBuilder();
308 | sb.append(pkgName);
309 | sb.append('.');
310 | sb.append(nameOrig);
311 | } else {
312 | return nameOrig;
313 | }
314 | return sb.toString();
315 | }
316 | }
317 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/utils/Trace.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.utils;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * @author Lody
7 | * @version 1.0
8 | */
9 | public class Trace {
10 |
11 | /**
12 | * 是否要输出到LogCat
13 | */
14 | public static boolean LOG_OUTPUT = true;
15 |
16 |
17 | private static final StringBuilder _TRACE_BUILDER = new StringBuilder(100);
18 |
19 | public static void store(Object msg) {
20 |
21 | String msgStr = object2Msg(msg);
22 |
23 | _TRACE_BUILDER.append(msgStr).append("\n");
24 |
25 | if (LOG_OUTPUT) {
26 | Log.d("PluginMgr-Trace", msgStr);
27 | }
28 | }
29 |
30 |
31 | public static String getTrace() {
32 | return _TRACE_BUILDER.toString();
33 | }
34 |
35 | public static void clearTrace() {
36 | _TRACE_BUILDER.delete(0, _TRACE_BUILDER.length() - 1);
37 | }
38 |
39 | private static String object2Msg(Object object) {
40 | return object != null ? object.toString() : " ";
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/utils/XmlManifestReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 Android4ME
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 | package androidx.pluginmgr.utils;
17 |
18 | import android.util.TypedValue;
19 |
20 | import org.xmlpull.v1.XmlPullParser;
21 | import org.xmlpull.v1.XmlPullParserException;
22 |
23 | import java.io.EOFException;
24 | import java.io.File;
25 | import java.io.IOException;
26 | import java.io.InputStream;
27 | import java.io.Reader;
28 | import java.util.zip.ZipEntry;
29 | import java.util.zip.ZipFile;
30 |
31 | /**
32 | *
33 | * Read xml document from Android's binary xml file.
34 | * 之所以不用反射直接调用而将它抠出来是因为这样能够保证稳定性.
35 | *
36 | *
37 | */
38 | class XmlManifestReader {
39 | public static final String DEFAULT_XML = "AndroidManifest.xml";
40 |
41 | private XmlManifestReader() {
42 | }
43 |
44 | public static String getManifestXMLFromAPK(String apkPath) {
45 | ZipFile file = null;
46 | String rs = null;
47 | try {
48 | File apkFile = new File(apkPath);
49 | file = new ZipFile(apkFile, ZipFile.OPEN_READ);
50 | ZipEntry entry = file.getEntry(DEFAULT_XML);
51 | rs = getManifestXMLFromAPK(file, entry);
52 | } catch (Exception e) {
53 | e.printStackTrace();
54 | } finally {
55 | if (file != null) {
56 | try {
57 | file.close();
58 | } catch (IOException e) {
59 | e.printStackTrace();
60 | }
61 | }
62 | }
63 | return rs;
64 | }
65 |
66 | public static String getManifestXMLFromAPK(ZipFile file, ZipEntry entry) {
67 | StringBuilder xmlSb = new StringBuilder(100);
68 | XmlResourceParser parser = null;
69 | try {
70 | parser = new XmlResourceParser();
71 | parser.open(file.getInputStream(entry));
72 |
73 | StringBuilder sb = new StringBuilder(10);
74 | final String indentStep = " ";
75 |
76 | int type;
77 | while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
78 | switch (type) {
79 | case XmlPullParser.START_DOCUMENT: {
80 | log(xmlSb, "");
81 | break;
82 | }
83 | case XmlPullParser.START_TAG: {
84 | log(false, xmlSb, "%s<%s%s", sb,
85 | getNamespacePrefix(parser.getPrefix()),
86 | parser.getName());
87 | sb.append(indentStep);
88 |
89 | int namespaceCountBefore = parser.getNamespaceCount(parser
90 | .getDepth() - 1);
91 | int namespaceCount = parser.getNamespaceCount(parser
92 | .getDepth());
93 |
94 | for (int i = namespaceCountBefore; i != namespaceCount; ++i) {
95 | log(xmlSb, "%sxmlns:%s=\"%s\"",
96 | i == namespaceCountBefore ? " " : sb,
97 | parser.getNamespacePrefix(i),
98 | parser.getNamespaceUri(i));
99 | }
100 |
101 | for (int i = 0, size = parser.getAttributeCount(); i != size; ++i) {
102 | log(false,
103 | xmlSb,
104 | "%s%s%s=\"%s\"",
105 | " ",
106 | getNamespacePrefix(parser.getAttributePrefix(i)),
107 | parser.getAttributeName(i),
108 | getAttributeValue(parser, i));
109 | }
110 | // log("%s>",sb);
111 | log(xmlSb, ">");
112 | break;
113 | }
114 | case XmlPullParser.END_TAG: {
115 | sb.setLength(sb.length() - indentStep.length());
116 | log(xmlSb, "%s%s%s>", sb,
117 | getNamespacePrefix(parser.getPrefix()),
118 | parser.getName());
119 | break;
120 | }
121 | case XmlPullParser.TEXT: {
122 | log(xmlSb, "%s%s", sb, parser.getText());
123 | break;
124 | }
125 | }
126 | }
127 | } catch (Exception e) {
128 | e.printStackTrace();
129 | } finally {
130 | parser.close();
131 | }
132 | return xmlSb.toString();
133 | }
134 |
135 |
136 | private static String getNamespacePrefix(String prefix) {
137 | if (prefix == null || prefix.length() == 0) {
138 | return "";
139 | }
140 | return prefix + ":";
141 | }
142 |
143 | private static String getAttributeValue(XmlResourceParser parser, int index) {
144 | int type = parser.getAttributeValueType(index);
145 | int data = parser.getAttributeValueData(index);
146 | if (type == TypedValue.TYPE_STRING) {
147 | return parser.getAttributeValue(index);
148 | }
149 | if (type == TypedValue.TYPE_ATTRIBUTE) {
150 | return String.format("?%s%08X", getPackage(data), data);
151 | }
152 | if (type == TypedValue.TYPE_REFERENCE) {
153 | return String.format("@%s%08X", getPackage(data), data);
154 | }
155 | if (type == TypedValue.TYPE_FLOAT) {
156 | return String.valueOf(Float.intBitsToFloat(data));
157 | }
158 | if (type == TypedValue.TYPE_INT_HEX) {
159 | return String.format("0x%08X", data);
160 | }
161 | if (type == TypedValue.TYPE_INT_BOOLEAN) {
162 | return data != 0 ? "true" : "false";
163 | }
164 | if (type == TypedValue.TYPE_DIMENSION) {
165 | return Float.toString(complexToFloat(data))
166 | + DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
167 | }
168 | if (type == TypedValue.TYPE_FRACTION) {
169 | return Float.toString(complexToFloat(data))
170 | + FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
171 | }
172 | if (type >= TypedValue.TYPE_FIRST_COLOR_INT
173 | && type <= TypedValue.TYPE_LAST_COLOR_INT) {
174 | return String.format("#%08X", data);
175 | }
176 | if (type >= TypedValue.TYPE_FIRST_INT
177 | && type <= TypedValue.TYPE_LAST_INT) {
178 | return String.valueOf(data);
179 | }
180 | return String.format("<0x%X, type 0x%02X>", data, type);
181 | }
182 |
183 | private static String getPackage(int id) {
184 | if (id >>> 24 == 1) {
185 | return "android:";
186 | }
187 | return "";
188 | }
189 |
190 | private static void log(StringBuilder xmlSb, String format,
191 | Object... arguments) {
192 | log(true, xmlSb, format, arguments);
193 | }
194 |
195 | private static void log(boolean newLine, StringBuilder xmlSb,
196 | String format, Object... arguments) {
197 | // System.out.printf(format,arguments);
198 | // if(newLine) System.out.println();
199 | xmlSb.append(String.format(format, arguments));
200 | if (newLine)
201 | xmlSb.append("\n");
202 | }
203 |
204 | // ///////////////////////////////// ILLEGAL STUFF, DONT LOOK :)
205 |
206 | private static float complexToFloat(int complex) {
207 | return (float) (complex & 0xFFFFFF00) * RADIX_MULTS[(complex >> 4) & 3];
208 | }
209 |
210 | private static final float RADIX_MULTS[] = { 0.00390625F, 3.051758E-005F,
211 | 1.192093E-007F, 4.656613E-010F };
212 | private static final String DIMENSION_UNITS[] = { "px", "dip", "sp", "pt",
213 | "in", "mm", "", "" };
214 | private static final String FRACTION_UNITS[] = { "%", "%p", "", "", "", "",
215 | "", "" };
216 |
217 | }
218 |
219 | /**
220 | * @author Dmitry Skiba
221 | *
222 | * Binary xml files parser.
223 | *
224 | * Parser has only two states: (1) Operational state, which parser
225 | * obtains after first successful call to next() and retains until
226 | * open(), close(), or failed call to next(). (2) Closed state, which
227 | * parser obtains after open(), close(), or failed call to next(). In
228 | * this state methods return invalid values or throw exceptions.
229 | *
230 | * TODO: * check all methods in closed state
231 | *
232 | */
233 | class XmlResourceParser implements android.content.res.XmlResourceParser {
234 |
235 | public XmlResourceParser() {
236 | resetEventInfo();
237 | }
238 |
239 | public void open(InputStream stream) {
240 | close();
241 | if (stream != null) {
242 | m_reader = new IntReader(stream, false);
243 | }
244 | }
245 |
246 | public void close() {
247 | if (!m_operational) {
248 | return;
249 | }
250 | m_operational = false;
251 | m_reader.close();
252 | m_reader = null;
253 | m_strings = null;
254 | m_resourceIDs = null;
255 | m_namespaces.reset();
256 | resetEventInfo();
257 | }
258 |
259 | public static final void readCheckType(IntReader reader, int expectedType)
260 | throws IOException {
261 | int type = reader.readInt();
262 | if (type != expectedType) {
263 | throw new IOException("Expected chunk of type 0x"
264 | + Integer.toHexString(expectedType) + ", read 0x"
265 | + Integer.toHexString(type) + ".");
266 | }
267 | }
268 |
269 | // ///////////////////////////////// iteration
270 |
271 | public int next() throws XmlPullParserException, IOException {
272 | if (m_reader == null) {
273 | throw new XmlPullParserException("Parser is not opened.", this,
274 | null);
275 | }
276 | try {
277 | doNext();
278 | return m_event;
279 | } catch (IOException e) {
280 | close();
281 | throw e;
282 | }
283 | }
284 |
285 | public int nextToken() throws XmlPullParserException, IOException {
286 | return next();
287 | }
288 |
289 | public int nextTag() throws XmlPullParserException, IOException {
290 | int eventType = next();
291 | if (eventType == TEXT && isWhitespace()) {
292 | eventType = next();
293 | }
294 | if (eventType != START_TAG && eventType != END_TAG) {
295 | throw new XmlPullParserException("Expected start or end tag.",
296 | this, null);
297 | }
298 | return eventType;
299 | }
300 |
301 | public String nextText() throws XmlPullParserException, IOException {
302 | if (getEventType() != START_TAG) {
303 | throw new XmlPullParserException(
304 | "Parser must be on START_TAG to read next text.", this,
305 | null);
306 | }
307 | int eventType = next();
308 | if (eventType == TEXT) {
309 | String result = getText();
310 | eventType = next();
311 | if (eventType != END_TAG) {
312 | throw new XmlPullParserException(
313 | "Event TEXT must be immediately followed by END_TAG.",
314 | this, null);
315 | }
316 | return result;
317 | } else if (eventType == END_TAG) {
318 | return "";
319 | } else {
320 | throw new XmlPullParserException(
321 | "Parser must be on START_TAG or TEXT to read text.", this,
322 | null);
323 | }
324 | }
325 |
326 | public void require(int type, String namespace, String name)
327 | throws XmlPullParserException, IOException {
328 | if (type != getEventType()
329 | || (namespace != null && !namespace.equals(getNamespace()))
330 | || (name != null && !name.equals(getName()))) {
331 | throw new XmlPullParserException(TYPES[type] + " is expected.",
332 | this, null);
333 | }
334 | }
335 |
336 | public int getDepth() {
337 | return m_namespaces.getDepth() - 1;
338 | }
339 |
340 | public int getEventType() throws XmlPullParserException {
341 | return m_event;
342 | }
343 |
344 | public int getLineNumber() {
345 | return m_lineNumber;
346 | }
347 |
348 | public String getName() {
349 | if (m_name == -1 || (m_event != START_TAG && m_event != END_TAG)) {
350 | return null;
351 | }
352 | return m_strings.getString(m_name);
353 | }
354 |
355 | public String getText() {
356 | if (m_name == -1 || m_event != TEXT) {
357 | return null;
358 | }
359 | return m_strings.getString(m_name);
360 | }
361 |
362 | public char[] getTextCharacters(int[] holderForStartAndLength) {
363 | String text = getText();
364 | if (text == null) {
365 | return null;
366 | }
367 | holderForStartAndLength[0] = 0;
368 | holderForStartAndLength[1] = text.length();
369 | char[] chars = new char[text.length()];
370 | text.getChars(0, text.length(), chars, 0);
371 | return chars;
372 | }
373 |
374 | public String getNamespace() {
375 | return m_strings.getString(m_namespaceUri);
376 | }
377 |
378 | public String getPrefix() {
379 | int prefix = m_namespaces.findPrefix(m_namespaceUri);
380 | return m_strings.getString(prefix);
381 | }
382 |
383 | public String getPositionDescription() {
384 | return "XML line #" + getLineNumber();
385 | }
386 |
387 | public int getNamespaceCount(int depth) throws XmlPullParserException {
388 | return m_namespaces.getAccumulatedCount(depth);
389 | }
390 |
391 | public String getNamespacePrefix(int pos) throws XmlPullParserException {
392 | int prefix = m_namespaces.getPrefix(pos);
393 | return m_strings.getString(prefix);
394 | }
395 |
396 | public String getNamespaceUri(int pos) throws XmlPullParserException {
397 | int uri = m_namespaces.getUri(pos);
398 | return m_strings.getString(uri);
399 | }
400 |
401 | // ///////////////////////////////// attributes
402 |
403 | public String getClassAttribute() {
404 | if (m_classAttribute == -1) {
405 | return null;
406 | }
407 | int offset = getAttributeOffset(m_classAttribute);
408 | int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
409 | return m_strings.getString(value);
410 | }
411 |
412 | public String getIdAttribute() {
413 | if (m_idAttribute == -1) {
414 | return null;
415 | }
416 | int offset = getAttributeOffset(m_idAttribute);
417 | int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
418 | return m_strings.getString(value);
419 | }
420 |
421 | public int getIdAttributeResourceValue(int defaultValue) {
422 | if (m_idAttribute == -1) {
423 | return defaultValue;
424 | }
425 | int offset = getAttributeOffset(m_idAttribute);
426 | int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
427 | if (valueType != TypedValue.TYPE_REFERENCE) {
428 | return defaultValue;
429 | }
430 | return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
431 | }
432 |
433 | public int getStyleAttribute() {
434 | if (m_styleAttribute == -1) {
435 | return 0;
436 | }
437 | int offset = getAttributeOffset(m_styleAttribute);
438 | return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
439 | }
440 |
441 | public int getAttributeCount() {
442 | if (m_event != START_TAG) {
443 | return -1;
444 | }
445 | return m_attributes.length / ATTRIBUTE_LENGHT;
446 | }
447 |
448 | public String getAttributeNamespace(int index) {
449 | int offset = getAttributeOffset(index);
450 | int namespace = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
451 | if (namespace == -1) {
452 | return "";
453 | }
454 | return m_strings.getString(namespace);
455 | }
456 |
457 | public String getAttributePrefix(int index) {
458 | int offset = getAttributeOffset(index);
459 | int uri = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
460 | int prefix = m_namespaces.findPrefix(uri);
461 | if (prefix == -1) {
462 | return "";
463 | }
464 | return m_strings.getString(prefix);
465 | }
466 |
467 | public String getAttributeName(int index) {
468 | int offset = getAttributeOffset(index);
469 | int name = m_attributes[offset + ATTRIBUTE_IX_NAME];
470 | if (name == -1) {
471 | return "";
472 | }
473 | return m_strings.getString(name);
474 | }
475 |
476 | public int getAttributeNameResource(int index) {
477 | int offset = getAttributeOffset(index);
478 | int name = m_attributes[offset + ATTRIBUTE_IX_NAME];
479 | if (m_resourceIDs == null || name < 0 || name >= m_resourceIDs.length) {
480 | return 0;
481 | }
482 | return m_resourceIDs[name];
483 | }
484 |
485 | public int getAttributeValueType(int index) {
486 | int offset = getAttributeOffset(index);
487 | return m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
488 | }
489 |
490 | public int getAttributeValueData(int index) {
491 | int offset = getAttributeOffset(index);
492 | return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
493 | }
494 |
495 | @SuppressWarnings("unused")
496 | public String getAttributeValue(int index) {
497 | int offset = getAttributeOffset(index);
498 | int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
499 | if (valueType == TypedValue.TYPE_STRING) {
500 | int valueString = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
501 | return m_strings.getString(valueString);
502 | }
503 | int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
504 | return "";// TypedValue.coerceToString(valueType,valueData);
505 | }
506 |
507 | public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
508 | return getAttributeIntValue(index, defaultValue ? 1 : 0) != 0;
509 | }
510 |
511 | public float getAttributeFloatValue(int index, float defaultValue) {
512 | int offset = getAttributeOffset(index);
513 | int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
514 | if (valueType == TypedValue.TYPE_FLOAT) {
515 | int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
516 | return Float.intBitsToFloat(valueData);
517 | }
518 | return defaultValue;
519 | }
520 |
521 | public int getAttributeIntValue(int index, int defaultValue) {
522 | int offset = getAttributeOffset(index);
523 | int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
524 | if (valueType >= TypedValue.TYPE_FIRST_INT
525 | && valueType <= TypedValue.TYPE_LAST_INT) {
526 | return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
527 | }
528 | return defaultValue;
529 | }
530 |
531 | public int getAttributeUnsignedIntValue(int index, int defaultValue) {
532 | return getAttributeIntValue(index, defaultValue);
533 | }
534 |
535 | public int getAttributeResourceValue(int index, int defaultValue) {
536 | int offset = getAttributeOffset(index);
537 | int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
538 | if (valueType == TypedValue.TYPE_REFERENCE) {
539 | return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
540 | }
541 | return defaultValue;
542 | }
543 |
544 | public String getAttributeValue(String namespace, String attribute) {
545 | int index = findAttribute(namespace, attribute);
546 | if (index == -1) {
547 | return null;
548 | }
549 | return getAttributeValue(index);
550 | }
551 |
552 | public boolean getAttributeBooleanValue(String namespace, String attribute,
553 | boolean defaultValue) {
554 | int index = findAttribute(namespace, attribute);
555 | if (index == -1) {
556 | return defaultValue;
557 | }
558 | return getAttributeBooleanValue(index, defaultValue);
559 | }
560 |
561 | public float getAttributeFloatValue(String namespace, String attribute,
562 | float defaultValue) {
563 | int index = findAttribute(namespace, attribute);
564 | if (index == -1) {
565 | return defaultValue;
566 | }
567 | return getAttributeFloatValue(index, defaultValue);
568 | }
569 |
570 | public int getAttributeIntValue(String namespace, String attribute,
571 | int defaultValue) {
572 | int index = findAttribute(namespace, attribute);
573 | if (index == -1) {
574 | return defaultValue;
575 | }
576 | return getAttributeIntValue(index, defaultValue);
577 | }
578 |
579 | public int getAttributeUnsignedIntValue(String namespace, String attribute,
580 | int defaultValue) {
581 | int index = findAttribute(namespace, attribute);
582 | if (index == -1) {
583 | return defaultValue;
584 | }
585 | return getAttributeUnsignedIntValue(index, defaultValue);
586 | }
587 |
588 | public int getAttributeResourceValue(String namespace, String attribute,
589 | int defaultValue) {
590 | int index = findAttribute(namespace, attribute);
591 | if (index == -1) {
592 | return defaultValue;
593 | }
594 | return getAttributeResourceValue(index, defaultValue);
595 | }
596 |
597 | public int getAttributeListValue(int index, String[] options,
598 | int defaultValue) {
599 | // TODO implement
600 | return 0;
601 | }
602 |
603 | public int getAttributeListValue(String namespace, String attribute,
604 | String[] options, int defaultValue) {
605 | // TODO implement
606 | return 0;
607 | }
608 |
609 | public String getAttributeType(int index) {
610 | return "CDATA";
611 | }
612 |
613 | public boolean isAttributeDefault(int index) {
614 | return false;
615 | }
616 |
617 | // ///////////////////////////////// dummies
618 |
619 | public void setInput(InputStream stream, String inputEncoding)
620 | throws XmlPullParserException {
621 | throw new XmlPullParserException(E_NOT_SUPPORTED);
622 | }
623 |
624 | public void setInput(Reader reader) throws XmlPullParserException {
625 | throw new XmlPullParserException(E_NOT_SUPPORTED);
626 | }
627 |
628 | public String getInputEncoding() {
629 | return null;
630 | }
631 |
632 | public int getColumnNumber() {
633 | return -1;
634 | }
635 |
636 | public boolean isEmptyElementTag() throws XmlPullParserException {
637 | return false;
638 | }
639 |
640 | public boolean isWhitespace() throws XmlPullParserException {
641 | return false;
642 | }
643 |
644 | public void defineEntityReplacementText(String entityName,
645 | String replacementText) throws XmlPullParserException {
646 | throw new XmlPullParserException(E_NOT_SUPPORTED);
647 | }
648 |
649 | public String getNamespace(String prefix) {
650 | throw new RuntimeException(E_NOT_SUPPORTED);
651 | }
652 |
653 | public Object getProperty(String name) {
654 | return null;
655 | }
656 |
657 | public void setProperty(String name, Object value)
658 | throws XmlPullParserException {
659 | throw new XmlPullParserException(E_NOT_SUPPORTED);
660 | }
661 |
662 | public boolean getFeature(String feature) {
663 | return false;
664 | }
665 |
666 | public void setFeature(String name, boolean value)
667 | throws XmlPullParserException {
668 | throw new XmlPullParserException(E_NOT_SUPPORTED);
669 | }
670 |
671 | // final void fetchAttributes(int[] styleableIDs,TypedArray result) {
672 | // result.resetIndices();
673 | // if (m_attributes==null || m_resourceIDs==null) {
674 | // return;
675 | // }
676 | // boolean needStrings=false;
677 | // for (int i=0,e=styleableIDs.length;i!=e;++i) {
678 | // int id=styleableIDs[i];
679 | // for (int o=0;o!=m_attributes.length;o+=ATTRIBUTE_LENGHT) {
680 | // int name=m_attributes[o+ATTRIBUTE_IX_NAME];
681 | // if (name>=m_resourceIDs.length ||
682 | // m_resourceIDs[name]!=id)
683 | // {
684 | // continue;
685 | // }
686 | // int valueType=m_attributes[o+ATTRIBUTE_IX_VALUE_TYPE];
687 | // int valueData;
688 | // int assetCookie;
689 | // if (valueType==TypedValue.TYPE_STRING) {
690 | // valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_STRING];
691 | // assetCookie=-1;
692 | // needStrings=true;
693 | // } else {
694 | // valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_DATA];
695 | // assetCookie=0;
696 | // }
697 | // result.addValue(i,valueType,valueData,assetCookie,id,0);
698 | // }
699 | // }
700 | // if (needStrings) {
701 | // result.setStrings(m_strings);
702 | // }
703 | // }
704 |
705 | final StringBlock getStrings() {
706 | return m_strings;
707 | }
708 |
709 | // /////////////////////////////////
710 |
711 | private final int getAttributeOffset(int index) {
712 | if (m_event != START_TAG) {
713 | throw new IndexOutOfBoundsException(
714 | "Current event is not START_TAG.");
715 | }
716 | int offset = index * 5;
717 | if (offset >= m_attributes.length) {
718 | throw new IndexOutOfBoundsException("Invalid attribute index ("
719 | + index + ").");
720 | }
721 | return offset;
722 | }
723 |
724 | private final int findAttribute(String namespace, String attribute) {
725 | if (m_strings == null || attribute == null) {
726 | return -1;
727 | }
728 | int name = m_strings.find(attribute);
729 | if (name == -1) {
730 | return -1;
731 | }
732 | int uri = (namespace != null) ? m_strings.find(namespace) : -1;
733 | for (int o = 0; o != m_attributes.length; ++o) {
734 | if (name == m_attributes[o + ATTRIBUTE_IX_NAME]
735 | && (uri == -1 || uri == m_attributes[o
736 | + ATTRIBUTE_IX_NAMESPACE_URI])) {
737 | return o / ATTRIBUTE_LENGHT;
738 | }
739 | }
740 | return -1;
741 | }
742 |
743 | private final void resetEventInfo() {
744 | m_event = -1;
745 | m_lineNumber = -1;
746 | m_name = -1;
747 | m_namespaceUri = -1;
748 | m_attributes = null;
749 | m_idAttribute = -1;
750 | m_classAttribute = -1;
751 | m_styleAttribute = -1;
752 | }
753 |
754 | private final void doNext() throws IOException {
755 | // Delayed initialization.
756 | if (m_strings == null) {
757 | readCheckType(m_reader, CHUNK_AXML_FILE);
758 | /* chunkSize */m_reader.skipInt();
759 | m_strings = StringBlock.read(m_reader);
760 | m_namespaces.increaseDepth();
761 | m_operational = true;
762 | }
763 |
764 | if (m_event == END_DOCUMENT) {
765 | return;
766 | }
767 |
768 | int event = m_event;
769 | resetEventInfo();
770 |
771 | while (true) {
772 | if (m_decreaseDepth) {
773 | m_decreaseDepth = false;
774 | m_namespaces.decreaseDepth();
775 | }
776 |
777 | // Fake END_DOCUMENT event.
778 | if (event == END_TAG && m_namespaces.getDepth() == 1
779 | && m_namespaces.getCurrentCount() == 0) {
780 | m_event = END_DOCUMENT;
781 | break;
782 | }
783 |
784 | int chunkType;
785 | if (event == START_DOCUMENT) {
786 | // Fake event, see CHUNK_XML_START_TAG handler.
787 | chunkType = CHUNK_XML_START_TAG;
788 | } else {
789 | chunkType = m_reader.readInt();
790 | }
791 |
792 | if (chunkType == CHUNK_RESOURCEIDS) {
793 | int chunkSize = m_reader.readInt();
794 | if (chunkSize < 8 || (chunkSize % 4) != 0) {
795 | throw new IOException("Invalid resource ids size ("
796 | + chunkSize + ").");
797 | }
798 | m_resourceIDs = m_reader.readIntArray(chunkSize / 4 - 2);
799 | continue;
800 | }
801 |
802 | if (chunkType < CHUNK_XML_FIRST || chunkType > CHUNK_XML_LAST) {
803 | throw new IOException("Invalid chunk type (" + chunkType + ").");
804 | }
805 |
806 | // Fake START_DOCUMENT event.
807 | if (chunkType == CHUNK_XML_START_TAG && event == -1) {
808 | m_event = START_DOCUMENT;
809 | break;
810 | }
811 |
812 | // Common header.
813 | /* chunkSize */m_reader.skipInt();
814 | int lineNumber = m_reader.readInt();
815 | /* 0xFFFFFFFF */m_reader.skipInt();
816 |
817 | if (chunkType == CHUNK_XML_START_NAMESPACE
818 | || chunkType == CHUNK_XML_END_NAMESPACE) {
819 | if (chunkType == CHUNK_XML_START_NAMESPACE) {
820 | int prefix = m_reader.readInt();
821 | int uri = m_reader.readInt();
822 | m_namespaces.push(prefix, uri);
823 | } else {
824 | /* prefix */m_reader.skipInt();
825 | /* uri */m_reader.skipInt();
826 | m_namespaces.pop();
827 | }
828 | continue;
829 | }
830 |
831 | m_lineNumber = lineNumber;
832 |
833 | if (chunkType == CHUNK_XML_START_TAG) {
834 | m_namespaceUri = m_reader.readInt();
835 | m_name = m_reader.readInt();
836 | /* flags? */m_reader.skipInt();
837 | int attributeCount = m_reader.readInt();
838 | m_idAttribute = (attributeCount >>> 16) - 1;
839 | attributeCount &= 0xFFFF;
840 | m_classAttribute = m_reader.readInt();
841 | m_styleAttribute = (m_classAttribute >>> 16) - 1;
842 | m_classAttribute = (m_classAttribute & 0xFFFF) - 1;
843 | m_attributes = m_reader.readIntArray(attributeCount
844 | * ATTRIBUTE_LENGHT);
845 | for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < m_attributes.length;) {
846 | m_attributes[i] = (m_attributes[i] >>> 24);
847 | i += ATTRIBUTE_LENGHT;
848 | }
849 | m_namespaces.increaseDepth();
850 | m_event = START_TAG;
851 | break;
852 | }
853 |
854 | if (chunkType == CHUNK_XML_END_TAG) {
855 | m_namespaceUri = m_reader.readInt();
856 | m_name = m_reader.readInt();
857 | m_event = END_TAG;
858 | m_decreaseDepth = true;
859 | break;
860 | }
861 |
862 | if (chunkType == CHUNK_XML_TEXT) {
863 | m_name = m_reader.readInt();
864 | /* ? */m_reader.skipInt();
865 | /* ? */m_reader.skipInt();
866 | m_event = TEXT;
867 | break;
868 | }
869 | }
870 | }
871 |
872 | // ///////////////////////////////// data
873 |
874 | /*
875 | * All values are essentially indices, e.g. m_name is an index of name in
876 | * m_strings.
877 | */
878 |
879 | private IntReader m_reader;
880 | private boolean m_operational = false;
881 |
882 | private StringBlock m_strings;
883 | private int[] m_resourceIDs;
884 | private NamespaceStack m_namespaces = new NamespaceStack();
885 |
886 | private boolean m_decreaseDepth;
887 |
888 | private int m_event;
889 | private int m_lineNumber;
890 | private int m_name;
891 | private int m_namespaceUri;
892 | private int[] m_attributes;
893 | private int m_idAttribute;
894 | private int m_classAttribute;
895 | private int m_styleAttribute;
896 |
897 | private static final String E_NOT_SUPPORTED = "Method is not supported.";
898 |
899 | private static final int ATTRIBUTE_IX_NAMESPACE_URI = 0,
900 | ATTRIBUTE_IX_NAME = 1, ATTRIBUTE_IX_VALUE_STRING = 2,
901 | ATTRIBUTE_IX_VALUE_TYPE = 3, ATTRIBUTE_IX_VALUE_DATA = 4,
902 | ATTRIBUTE_LENGHT = 5;
903 |
904 | private static final int CHUNK_AXML_FILE = 0x00080003,
905 | CHUNK_RESOURCEIDS = 0x00080180, CHUNK_XML_FIRST = 0x00100100,
906 | CHUNK_XML_START_NAMESPACE = 0x00100100,
907 | CHUNK_XML_END_NAMESPACE = 0x00100101,
908 | CHUNK_XML_START_TAG = 0x00100102, CHUNK_XML_END_TAG = 0x00100103,
909 | CHUNK_XML_TEXT = 0x00100104, CHUNK_XML_LAST = 0x00100104;
910 |
911 | }
912 |
913 | final class IntReader {
914 |
915 | public IntReader() {
916 | }
917 |
918 | public IntReader(InputStream stream, boolean bigEndian) {
919 | reset(stream, bigEndian);
920 | }
921 |
922 | public final void reset(InputStream stream, boolean bigEndian) {
923 | m_stream = stream;
924 | m_bigEndian = bigEndian;
925 | m_position = 0;
926 | }
927 |
928 | public final void close() {
929 | if (m_stream == null) {
930 | return;
931 | }
932 | try {
933 | m_stream.close();
934 | } catch (IOException e) {
935 | }
936 | reset(null, false);
937 | }
938 |
939 | public final InputStream getStream() {
940 | return m_stream;
941 | }
942 |
943 | public final boolean isBigEndian() {
944 | return m_bigEndian;
945 | }
946 |
947 | public final void setBigEndian(boolean bigEndian) {
948 | m_bigEndian = bigEndian;
949 | }
950 |
951 | public final int readByte() throws IOException {
952 | return readInt(1);
953 | }
954 |
955 | public final int readShort() throws IOException {
956 | return readInt(2);
957 | }
958 |
959 | public final int readInt() throws IOException {
960 | return readInt(4);
961 | }
962 |
963 | public final int readInt(int length) throws IOException {
964 | if (length < 0 || length > 4) {
965 | throw new IllegalArgumentException();
966 | }
967 | int result = 0;
968 | if (m_bigEndian) {
969 | for (int i = (length - 1) * 8; i >= 0; i -= 8) {
970 | int b = m_stream.read();
971 | if (b == -1) {
972 | throw new EOFException();
973 | }
974 | m_position += 1;
975 | result |= (b << i);
976 | }
977 | } else {
978 | length *= 8;
979 | for (int i = 0; i != length; i += 8) {
980 | int b = m_stream.read();
981 | if (b == -1) {
982 | throw new EOFException();
983 | }
984 | m_position += 1;
985 | result |= (b << i);
986 | }
987 | }
988 | return result;
989 | }
990 |
991 | public final int[] readIntArray(int length) throws IOException {
992 | int[] array = new int[length];
993 | readIntArray(array, 0, length);
994 | return array;
995 | }
996 |
997 | public final void readIntArray(int[] array, int offset, int length)
998 | throws IOException {
999 | for (; length > 0; length -= 1) {
1000 | array[offset++] = readInt();
1001 | }
1002 | }
1003 |
1004 | public final byte[] readByteArray(int length) throws IOException {
1005 | byte[] array = new byte[length];
1006 | int read = m_stream.read(array);
1007 | m_position += read;
1008 | if (read != length) {
1009 | throw new EOFException();
1010 | }
1011 | return array;
1012 | }
1013 |
1014 | public final void skip(int bytes) throws IOException {
1015 | if (bytes <= 0) {
1016 | return;
1017 | }
1018 | long skipped = m_stream.skip(bytes);
1019 | m_position += skipped;
1020 | if (skipped != bytes) {
1021 | throw new EOFException();
1022 | }
1023 | }
1024 |
1025 | public final void skipInt() throws IOException {
1026 | skip(4);
1027 | }
1028 |
1029 | public final int available() throws IOException {
1030 | return m_stream.available();
1031 | }
1032 |
1033 | public final int getPosition() {
1034 | return m_position;
1035 | }
1036 |
1037 | // ///////////////////////////////// data
1038 |
1039 | private InputStream m_stream;
1040 | private boolean m_bigEndian;
1041 | private int m_position;
1042 | }
1043 |
1044 | // /////////////////////////////////////////// implementation
1045 |
1046 | /**
1047 | * Namespace stack, holds prefix+uri pairs, as well as depth information. All
1048 | * information is stored in one int[] array. Array consists of depth frames:
1049 | * Data=DepthFrame*; DepthFrame=Count+[Prefix+Uri]*+Count; Count='count of
1050 | * Prefix+Uri pairs'; Yes, count is stored twice, to enable bottom-up traversal.
1051 | * increaseDepth adds depth frame, decreaseDepth removes it. push/pop operations
1052 | * operate only in current depth frame. decreaseDepth removes any remaining (not
1053 | * pop'ed) namespace pairs. findXXX methods search all depth frames starting
1054 | * from the last namespace pair of current depth frame. All functions that
1055 | * operate with int, use -1 as 'invalid value'.
1056 | *
1057 | * !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
1058 | *
1059 | */
1060 | final class NamespaceStack {
1061 | public NamespaceStack() {
1062 | m_data = new int[32];
1063 | }
1064 |
1065 | public final void reset() {
1066 | m_dataLength = 0;
1067 | m_count = 0;
1068 | m_depth = 0;
1069 | }
1070 |
1071 | @SuppressWarnings("unused")
1072 | public final int getTotalCount() {
1073 | return m_count;
1074 | }
1075 |
1076 | public final int getCurrentCount() {
1077 | if (m_dataLength == 0) {
1078 | return 0;
1079 | }
1080 | int offset = m_dataLength - 1;
1081 | return m_data[offset];
1082 | }
1083 |
1084 | public final int getAccumulatedCount(int depth) {
1085 | if (m_dataLength == 0 || depth < 0) {
1086 | return 0;
1087 | }
1088 | if (depth > m_depth) {
1089 | depth = m_depth;
1090 | }
1091 | int accumulatedCount = 0;
1092 | int offset = 0;
1093 | for (; depth != 0; --depth) {
1094 | int count = m_data[offset];
1095 | accumulatedCount += count;
1096 | offset += (2 + count * 2);
1097 | }
1098 | return accumulatedCount;
1099 | }
1100 |
1101 | public final void push(int prefix, int uri) {
1102 | if (m_depth == 0) {
1103 | increaseDepth();
1104 | }
1105 | ensureDataCapacity(2);
1106 | int offset = m_dataLength - 1;
1107 | int count = m_data[offset];
1108 | m_data[offset - 1 - count * 2] = count + 1;
1109 | m_data[offset] = prefix;
1110 | m_data[offset + 1] = uri;
1111 | m_data[offset + 2] = count + 1;
1112 | m_dataLength += 2;
1113 | m_count += 1;
1114 | }
1115 |
1116 | @SuppressWarnings("unused")
1117 | public final boolean pop(int prefix, int uri) {
1118 | if (m_dataLength == 0) {
1119 | return false;
1120 | }
1121 | int offset = m_dataLength - 1;
1122 | int count = m_data[offset];
1123 | for (int i = 0, o = offset - 2; i != count; ++i, o -= 2) {
1124 | if (m_data[o] != prefix || m_data[o + 1] != uri) {
1125 | continue;
1126 | }
1127 | count -= 1;
1128 | if (i == 0) {
1129 | m_data[o] = count;
1130 | o -= (1 + count * 2);
1131 | m_data[o] = count;
1132 | } else {
1133 | m_data[offset] = count;
1134 | offset -= (1 + 2 + count * 2);
1135 | m_data[offset] = count;
1136 | System.arraycopy(m_data, o + 2, m_data, o, m_dataLength - o);
1137 | }
1138 | m_dataLength -= 2;
1139 | m_count -= 1;
1140 | return true;
1141 | }
1142 | return false;
1143 | }
1144 |
1145 | public final boolean pop() {
1146 | if (m_dataLength == 0) {
1147 | return false;
1148 | }
1149 | int offset = m_dataLength - 1;
1150 | int count = m_data[offset];
1151 | if (count == 0) {
1152 | return false;
1153 | }
1154 | count -= 1;
1155 | offset -= 2;
1156 | m_data[offset] = count;
1157 | offset -= (1 + count * 2);
1158 | m_data[offset] = count;
1159 | m_dataLength -= 2;
1160 | m_count -= 1;
1161 | return true;
1162 | }
1163 |
1164 | public final int getPrefix(int index) {
1165 | return get(index, true);
1166 | }
1167 |
1168 | public final int getUri(int index) {
1169 | return get(index, false);
1170 | }
1171 |
1172 | public final int findPrefix(int uri) {
1173 | return find(uri, false);
1174 | }
1175 |
1176 | @SuppressWarnings("unused")
1177 | public final int findUri(int prefix) {
1178 | return find(prefix, true);
1179 | }
1180 |
1181 | public final int getDepth() {
1182 | return m_depth;
1183 | }
1184 |
1185 | public final void increaseDepth() {
1186 | ensureDataCapacity(2);
1187 | int offset = m_dataLength;
1188 | m_data[offset] = 0;
1189 | m_data[offset + 1] = 0;
1190 | m_dataLength += 2;
1191 | m_depth += 1;
1192 | }
1193 |
1194 | public final void decreaseDepth() {
1195 | if (m_dataLength == 0) {
1196 | return;
1197 | }
1198 | int offset = m_dataLength - 1;
1199 | int count = m_data[offset];
1200 | if ((offset - 1 - count * 2) == 0) {
1201 | return;
1202 | }
1203 | m_dataLength -= 2 + count * 2;
1204 | m_count -= count;
1205 | m_depth -= 1;
1206 | }
1207 |
1208 | private void ensureDataCapacity(int capacity) {
1209 | int available = (m_data.length - m_dataLength);
1210 | if (available > capacity) {
1211 | return;
1212 | }
1213 | int newLength = (m_data.length + available) * 2;
1214 | int[] newData = new int[newLength];
1215 | System.arraycopy(m_data, 0, newData, 0, m_dataLength);
1216 | m_data = newData;
1217 | }
1218 |
1219 | private final int find(int prefixOrUri, boolean prefix) {
1220 | if (m_dataLength == 0) {
1221 | return -1;
1222 | }
1223 | int offset = m_dataLength - 1;
1224 | for (int i = m_depth; i != 0; --i) {
1225 | int count = m_data[offset];
1226 | offset -= 2;
1227 | for (; count != 0; --count) {
1228 | if (prefix) {
1229 | if (m_data[offset] == prefixOrUri) {
1230 | return m_data[offset + 1];
1231 | }
1232 | } else {
1233 | if (m_data[offset + 1] == prefixOrUri) {
1234 | return m_data[offset];
1235 | }
1236 | }
1237 | offset -= 2;
1238 | }
1239 | }
1240 | return -1;
1241 | }
1242 |
1243 | private final int get(int index, boolean prefix) {
1244 | if (m_dataLength == 0 || index < 0) {
1245 | return -1;
1246 | }
1247 | int offset = 0;
1248 | for (int i = m_depth; i != 0; --i) {
1249 | int count = m_data[offset];
1250 | if (index >= count) {
1251 | index -= count;
1252 | offset += (2 + count * 2);
1253 | continue;
1254 | }
1255 | offset += (1 + index * 2);
1256 | if (!prefix) {
1257 | offset += 1;
1258 | }
1259 | return m_data[offset];
1260 | }
1261 | return -1;
1262 | }
1263 |
1264 | private int[] m_data;
1265 | private int m_dataLength;
1266 | private int m_count;
1267 | private int m_depth;
1268 | }
1269 |
1270 | /**
1271 | * @author Dmitry Skiba
1272 | *
1273 | * Block of strings, used in binary xml and arsc.
1274 | *
1275 | * TODO: - implement get()
1276 | *
1277 | */
1278 | class StringBlock {
1279 |
1280 | /**
1281 | * Reads whole (including chunk type) string block from stream. Stream must
1282 | * be at the chunk type.
1283 | */
1284 | public static StringBlock read(IntReader reader) throws IOException {
1285 | XmlResourceParser.readCheckType(reader, CHUNK_TYPE);
1286 | int chunkSize = reader.readInt();
1287 | int stringCount = reader.readInt();
1288 | int styleOffsetCount = reader.readInt();
1289 | /* ? */reader.readInt();
1290 | int stringsOffset = reader.readInt();
1291 | int stylesOffset = reader.readInt();
1292 |
1293 | StringBlock block = new StringBlock();
1294 | block.m_stringOffsets = reader.readIntArray(stringCount);
1295 | if (styleOffsetCount != 0) {
1296 | block.m_styleOffsets = reader.readIntArray(styleOffsetCount);
1297 | }
1298 | {
1299 | int size = ((stylesOffset == 0) ? chunkSize : stylesOffset)
1300 | - stringsOffset;
1301 | if ((size % 4) != 0) {
1302 | throw new IOException("String data size is not multiple of 4 ("
1303 | + size + ").");
1304 | }
1305 | block.m_strings = reader.readIntArray(size / 4);
1306 | }
1307 | if (stylesOffset != 0) {
1308 | int size = (chunkSize - stylesOffset);
1309 | if ((size % 4) != 0) {
1310 | throw new IOException("Style data size is not multiple of 4 ("
1311 | + size + ").");
1312 | }
1313 | block.m_styles = reader.readIntArray(size / 4);
1314 | }
1315 |
1316 | return block;
1317 | }
1318 |
1319 | /**
1320 | * Returns number of strings in block.
1321 | */
1322 | public int getCount() {
1323 | return m_stringOffsets != null ? m_stringOffsets.length : 0;
1324 | }
1325 |
1326 | /**
1327 | * Returns raw string (without any styling information) at specified index.
1328 | */
1329 | public String getString(int index) {
1330 | if (index < 0 || m_stringOffsets == null
1331 | || index >= m_stringOffsets.length) {
1332 | return null;
1333 | }
1334 | int offset = m_stringOffsets[index];
1335 | int length = getShort(m_strings, offset);
1336 | StringBuilder result = new StringBuilder(length);
1337 | for (; length != 0; length -= 1) {
1338 | offset += 2;
1339 | result.append((char) getShort(m_strings, offset));
1340 | }
1341 | return result.toString();
1342 | }
1343 |
1344 | /**
1345 | * Not yet implemented.
1346 | *
1347 | * Returns string with style information (if any).
1348 | */
1349 | public CharSequence get(int index) {
1350 | return getString(index);
1351 | }
1352 |
1353 | /**
1354 | * Returns string with style tags (html-like).
1355 | */
1356 | public String getHTML(int index) {
1357 | String raw = getString(index);
1358 | if (raw == null) {
1359 | return raw;
1360 | }
1361 | int[] style = getStyle(index);
1362 | if (style == null) {
1363 | return raw;
1364 | }
1365 | StringBuilder html = new StringBuilder(raw.length() + 32);
1366 | int offset = 0;
1367 | while (true) {
1368 | int i = -1;
1369 | for (int j = 0; j != style.length; j += 3) {
1370 | if (style[j + 1] == -1) {
1371 | continue;
1372 | }
1373 | if (i == -1 || style[i + 1] > style[j + 1]) {
1374 | i = j;
1375 | }
1376 | }
1377 | int start = ((i != -1) ? style[i + 1] : raw.length());
1378 | for (int j = 0; j != style.length; j += 3) {
1379 | int end = style[j + 2];
1380 | if (end == -1 || end >= start) {
1381 | continue;
1382 | }
1383 | if (offset <= end) {
1384 | html.append(raw, offset, end + 1);
1385 | offset = end + 1;
1386 | }
1387 | style[j + 2] = -1;
1388 | html.append('<');
1389 | html.append('/');
1390 | html.append(getString(style[j]));
1391 | html.append('>');
1392 | }
1393 | if (offset < start) {
1394 | html.append(raw, offset, start);
1395 | offset = start;
1396 | }
1397 | if (i == -1) {
1398 | break;
1399 | }
1400 | html.append('<');
1401 | html.append(getString(style[i]));
1402 | html.append('>');
1403 | style[i + 1] = -1;
1404 | }
1405 | return html.toString();
1406 | }
1407 |
1408 | /**
1409 | * Finds index of the string. Returns -1 if the string was not found.
1410 | */
1411 | public int find(String string) {
1412 | if (string == null) {
1413 | return -1;
1414 | }
1415 | for (int i = 0; i != m_stringOffsets.length; ++i) {
1416 | int offset = m_stringOffsets[i];
1417 | int length = getShort(m_strings, offset);
1418 | if (length != string.length()) {
1419 | continue;
1420 | }
1421 | int j = 0;
1422 | for (; j != length; ++j) {
1423 | offset += 2;
1424 | if (string.charAt(j) != getShort(m_strings, offset)) {
1425 | break;
1426 | }
1427 | }
1428 | if (j == length) {
1429 | return i;
1430 | }
1431 | }
1432 | return -1;
1433 | }
1434 |
1435 | // /////////////////////////////////////////// implementation
1436 |
1437 | private StringBlock() {
1438 | }
1439 |
1440 | /**
1441 | * Returns style information - array of int triplets, where in each triplet:
1442 | * * first int is index of tag name ('b','i', etc.) * second int is tag
1443 | * start index in string * third int is tag end index in string
1444 | */
1445 | private int[] getStyle(int index) {
1446 | if (m_styleOffsets == null || m_styles == null
1447 | || index >= m_styleOffsets.length) {
1448 | return null;
1449 | }
1450 | int offset = m_styleOffsets[index] / 4;
1451 | int style[];
1452 | {
1453 | int count = 0;
1454 | for (int i = offset; i < m_styles.length; ++i) {
1455 | if (m_styles[i] == -1) {
1456 | break;
1457 | }
1458 | count += 1;
1459 | }
1460 | if (count == 0 || (count % 3) != 0) {
1461 | return null;
1462 | }
1463 | style = new int[count];
1464 | }
1465 | for (int i = offset, j = 0; i < m_styles.length;) {
1466 | if (m_styles[i] == -1) {
1467 | break;
1468 | }
1469 | style[j++] = m_styles[i++];
1470 | }
1471 | return style;
1472 | }
1473 |
1474 | private static final int getShort(int[] array, int offset) {
1475 | int value = array[offset / 4];
1476 | if ((offset % 4) / 2 == 0) {
1477 | return (value & 0xFFFF);
1478 | } else {
1479 | return (value >>> 16);
1480 | }
1481 | }
1482 |
1483 | private int[] m_stringOffsets;
1484 | private int[] m_strings;
1485 | private int[] m_styleOffsets;
1486 | private int[] m_styles;
1487 |
1488 | private static final int CHUNK_TYPE = 0x001C0001;
1489 | }
1490 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/verify/PluginNotFoundException.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.verify;
2 |
3 | import android.util.AndroidException;
4 |
5 | /**
6 | * 插件为找到的异常
7 | *
8 | * @author Lody
9 | * @version 1.0
10 | */
11 | public class PluginNotFoundException extends AndroidException {
12 | public PluginNotFoundException(String name) {
13 | super(name);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/verify/PluginOverdueVerifier.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.verify;
2 |
3 | import java.io.File;
4 |
5 | /**
6 | * 插件需要拷贝到私有目录下,
7 | * 如果私有目录下,已经有一个文件存在,那么就需要是否还有必要覆盖原文件.
8 | *
9 | * @author Lody
10 | * @version 1.0
11 | */
12 | public interface PluginOverdueVerifier {
13 |
14 | /**
15 | * 检查已存在的目标插件是否已经过期
16 | *
17 | * @param originPluginFile 原插件文件
18 | * @param targetExistFile 已存在的目标插件文件
19 | * @return 私有目录下已存在的目标插件是否已经过期
20 | */
21 | boolean isOverdue(File originPluginFile, File targetExistFile);
22 | }
23 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/verify/SimpleLengthVerifier.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.verify;
2 | import java.io.File;
3 |
4 | /**
5 | * @author Lody
6 | * @version 1.0
7 | */
8 | public class SimpleLengthVerifier implements PluginOverdueVerifier
9 | {
10 |
11 | @Override
12 | public boolean isOverdue(File originPluginFile, File targetExistFile)
13 | {
14 |
15 | return originPluginFile.length() != targetExistFile.length();
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/widget/LayoutInflaterWrapper.java:
--------------------------------------------------------------------------------
1 | package androidx.pluginmgr.widget;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.util.Log;
6 | import android.view.Gravity;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.FrameLayout;
11 | import android.widget.LinearLayout;
12 |
13 | import org.xmlpull.v1.XmlPullParser;
14 |
15 |
16 | public class LayoutInflaterWrapper extends LayoutInflater {
17 |
18 | private static final String TAG = "LayoutInflaterWrapper";
19 |
20 | private LayoutInflater target;
21 | private final Class> layoutClass;
22 | private final Class> idClass;
23 | @SuppressWarnings("unused")
24 | private final Class> attrClass;
25 | private final int screenTitle;
26 |
27 | public LayoutInflaterWrapper(LayoutInflater target) {
28 | super(target.getContext());
29 | this.target = target;
30 | Class> layoutClass = null;
31 | Class> idClass = null;
32 | Class> attrClass = null;
33 | int screenTitle = 0;
34 | try {
35 | layoutClass = Class.forName("com.android.internal.R$layout");
36 | idClass = Class.forName("com.android.internal.R$id");
37 | attrClass = Class.forName("com.android.internal.R$attr");
38 | screenTitle = layoutClass.getField("screenTitle").getInt(null);
39 | } catch (Exception e) {
40 | e.printStackTrace();
41 | }
42 | this.layoutClass = layoutClass;
43 | this.idClass = idClass;
44 | this.attrClass = attrClass;
45 | this.screenTitle = screenTitle;
46 | }
47 |
48 | @Override
49 | public LayoutInflater cloneInContext(Context newContext) {
50 | return target.cloneInContext(newContext);
51 | }
52 |
53 | @Override
54 | public Context getContext() {
55 | return target.getContext();
56 | }
57 |
58 | @Override
59 | public void setFactory(Factory factory) {
60 | target.setFactory(factory);
61 | }
62 |
63 | @Override
64 | public Filter getFilter() {
65 | return target.getFilter();
66 | }
67 |
68 | @Override
69 | public void setFilter(Filter filter) {
70 | target.setFilter(filter);
71 | }
72 |
73 | @Override
74 | public View inflate(int resource, ViewGroup root) {
75 | Log.i(TAG, "inflate布局( resource=" + resource + ", root=" + root + " )");
76 | if (resource == screenTitle) {
77 | Log.i(TAG, "使用自定义布局");
78 | return createLayoutScreenSimple(resource, root);
79 | }
80 | return target.inflate(resource, root);
81 | }
82 |
83 | private View createLayoutScreenSimple(int resource, ViewGroup root) {
84 | LinearLayout lyt = new LinearLayout(getContext());
85 | lyt.setOrientation(LinearLayout.VERTICAL);
86 | if (android.os.Build.VERSION.SDK_INT >= 14) {
87 | try {
88 | LinearLayout.class.getMethod("setFitsSystemWindows",
89 | boolean.class).invoke(lyt, true);
90 | } catch (Throwable ignored) {
91 | }
92 | }
93 | int viewStubId = 0;
94 | int frameLytId = 0;
95 | int layoutResource = 0;
96 | int inflatedId = 0;
97 | try {
98 | frameLytId = idClass.getField("content").getInt(null);
99 | viewStubId = idClass.getField("action_mode_bar_stub").getInt(null);
100 | inflatedId = idClass.getField("action_mode_bar").getInt(null);
101 | layoutResource = layoutClass.getField("action_mode_bar").getInt(
102 | null);
103 | } catch (Throwable e) {
104 | e.printStackTrace();
105 | }
106 | {
107 | ViewStub viewStub = new ViewStub(
108 | getContext());
109 | viewStub.setId(viewStubId);
110 | if (inflatedId != 0)
111 | viewStub.setInflatedId(inflatedId);
112 | if (layoutResource != 0)
113 | viewStub.setLayoutResource(layoutResource);
114 | lyt.addView(viewStub, new LinearLayout.LayoutParams(
115 | LinearLayout.LayoutParams.MATCH_PARENT,
116 | LinearLayout.LayoutParams.WRAP_CONTENT));
117 | }
118 | FrameLayout flyt_content = new FrameLayout(getContext());
119 | flyt_content.setId(frameLytId);
120 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
121 | LinearLayout.LayoutParams.MATCH_PARENT,
122 | LinearLayout.LayoutParams.MATCH_PARENT);
123 | flyt_content
124 | .setForegroundGravity(Gravity.FILL_HORIZONTAL | Gravity.TOP);
125 | lyt.addView(flyt_content, layoutParams);
126 | return lyt;
127 | }
128 |
129 | @Override
130 | public View inflate(XmlPullParser parser, ViewGroup root) {
131 | return target.inflate(parser, root);
132 | }
133 |
134 | @Override
135 | public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
136 | return target.inflate(resource, root, attachToRoot);
137 | }
138 |
139 | @Override
140 | public View inflate(XmlPullParser parser, ViewGroup root,
141 | boolean attachToRoot) {
142 | return target.inflate(parser, root, attachToRoot);
143 | }
144 |
145 | @Override
146 | protected View onCreateView(String name, AttributeSet attrs)
147 | throws ClassNotFoundException {
148 | try {
149 | return (View) LayoutInflater.class.getDeclaredMethod(
150 | "onCreateView", String.class, AttributeSet.class).invoke(
151 | target, name, attrs);
152 | } catch (Exception e) {
153 | e.printStackTrace();
154 | return super.onCreateView(name, attrs);
155 | }
156 | }
157 |
158 | protected View onCreateView(View parent, String name, AttributeSet attrs)
159 | throws ClassNotFoundException {
160 | try {
161 | return (View) LayoutInflater.class.getDeclaredMethod(
162 | "onCreateView", View.class, String.class,
163 | AttributeSet.class).invoke(target, parent, name, attrs);
164 | } catch (Exception e) {
165 | e.printStackTrace();
166 | return super.onCreateView(name, attrs);
167 | }
168 | }
169 |
170 | }
171 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/java/androidx/pluginmgr/widget/ViewStub.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2008 The Android Open Source Project
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 androidx.pluginmgr.widget;
18 |
19 | import android.annotation.SuppressLint;
20 | import android.content.Context;
21 | import android.content.res.TypedArray;
22 | import android.graphics.Canvas;
23 | import android.util.AttributeSet;
24 | import android.view.LayoutInflater;
25 | import android.view.View;
26 | import android.view.ViewGroup;
27 | import android.view.ViewParent;
28 | import android.widget.RemoteViews.RemoteView;
29 |
30 | import java.lang.ref.WeakReference;
31 |
32 | /**
33 | * A ViewStub is an invisible, zero-sized View that can be used to lazily
34 | * inflate layout resources at runtime.
35 | *
36 | * When a ViewStub is made visible, or when {@link #inflate()} is invoked, the
37 | * layout resource is inflated. The ViewStub then replaces itself in its parent
38 | * with the inflated View or Views. Therefore, the ViewStub exists in the view
39 | * hierarchy until {@link #setVisibility(int)} or {@link #inflate()} is invoked.
40 | *
41 | * The inflated View is added to the ViewStub's parent with the ViewStub's
42 | * layout parameters. Similarly, you can define/override the inflate View's id
43 | * by using the ViewStub's inflatedId property. For instance:
44 | *
45 | *
46 | * <ViewStub android:id="@+id/stub"
47 | * android:inflatedId="@+id/subTree"
48 | * android:layout="@layout/mySubTree"
49 | * android:layout_width="120dip"
50 | * android:layout_height="40dip" />
51 | *
52 | *
53 | * The ViewStub thus defined can be found using the id "stub." After inflation
54 | * of the layout resource "mySubTree," the ViewStub is removed from its parent.
55 | * The View created by inflating the layout resource "mySubTree" can be found
56 | * using the id "subTree," specified by the inflatedId property. The inflated
57 | * View is finally assigned a width of 120dip and a height of 40dip.
58 | *
59 | * The preferred way to perform the inflation of the layout resource is the
60 | * following:
61 | *
62 | *
63 | * ViewStub stub = (ViewStub) findViewById(R.id.stub);
64 | * View inflated = stub.inflate();
65 | *
66 | *
67 | * When {@link #inflate()} is invoked, the ViewStub is replaced by the inflated
68 | * View and the inflated View is returned. This lets applications get a
69 | * reference to the inflated View without executing an extra findViewById().
70 | *
71 | * @attr ref android.R.styleable#ViewStub_inflatedId
72 | * @attr ref android.R.styleable#ViewStub_layout
73 | */
74 | @RemoteView
75 | public final class ViewStub extends View {
76 | private int mLayoutResource = 0;
77 | private int mInflatedId;
78 |
79 | private WeakReference mInflatedViewRef;
80 |
81 | private LayoutInflater mInflater;
82 | private OnInflateListener mInflateListener;
83 |
84 | public ViewStub(Context context) {
85 | super(context);
86 | initialize(context);
87 | }
88 |
89 | /**
90 | * Creates a new ViewStub with the specified layout resource.
91 | *
92 | * @param context The application's environment.
93 | * @param layoutResource The reference to a layout resource that will be inflated.
94 | */
95 | public ViewStub(Context context, int layoutResource) {
96 | super(context);
97 | mLayoutResource = layoutResource;
98 | initialize(context);
99 | }
100 |
101 | public ViewStub(Context context, AttributeSet attrs) {
102 | this(context, attrs, 0);
103 | }
104 |
105 | public ViewStub(Context context, AttributeSet attrs, int defStyle) {
106 | super(context);
107 | int[] attrsArr = null;
108 | int index = 0;
109 | int layoutId = 0;
110 | int[] attrViewArr = null;
111 | int viewId = 0;
112 | try {
113 | Class> styleableClass = Class
114 | .forName("com.android.internal.R$styleable");
115 | attrsArr = (int[]) styleableClass.getField("ViewStub").get(null);
116 | index = styleableClass.getField("ViewStub_inflatedId").getInt(null);
117 | layoutId = styleableClass.getField("ViewStub_layout").getInt(null);
118 | attrViewArr = (int[]) styleableClass.getField("View").get(null);
119 | viewId = styleableClass.getField("View_id").getInt(null);
120 | } catch (Exception e) {
121 | e.printStackTrace();
122 | }
123 |
124 | TypedArray a = context.obtainStyledAttributes(attrs, attrsArr,
125 | defStyle, 0);
126 |
127 | mInflatedId = a.getResourceId(index, NO_ID);
128 | mLayoutResource = a.getResourceId(layoutId, 0);
129 |
130 | a.recycle();
131 | a = context.obtainStyledAttributes(attrs, attrViewArr, defStyle, 0);
132 | setId(a.getResourceId(viewId, NO_ID));
133 | a.recycle();
134 |
135 | initialize(context);
136 | }
137 |
138 | private void initialize(Context context) {
139 | setVisibility(GONE);
140 | setWillNotDraw(true);
141 | }
142 |
143 | /**
144 | * Returns the id taken by the inflated view. If the inflated id is
145 | * {@link View#NO_ID}, the inflated view keeps its original id.
146 | *
147 | * @return A positive integer used to identify the inflated view or
148 | * {@link #NO_ID} if the inflated view should keep its id.
149 | * @attr ref android.R.styleable#ViewStub_inflatedId
150 | * @see #setInflatedId(int)
151 | */
152 | public int getInflatedId() {
153 | return mInflatedId;
154 | }
155 |
156 | /**
157 | * Defines the id taken by the inflated view. If the inflated id is
158 | * {@link View#NO_ID}, the inflated view keeps its original id.
159 | *
160 | * @param inflatedId A positive integer used to identify the inflated view or
161 | * {@link #NO_ID} if the inflated view should keep its id.
162 | * @attr ref android.R.styleable#ViewStub_inflatedId
163 | * @see #getInflatedId()
164 | */
165 | // @android.view.RemotableViewMethod
166 | public void setInflatedId(int inflatedId) {
167 | mInflatedId = inflatedId;
168 | }
169 |
170 | /**
171 | * Returns the layout resource that will be used by
172 | * {@link #setVisibility(int)} or {@link #inflate()} to replace this
173 | * StubbedView in its parent by another view.
174 | *
175 | * @return The layout resource identifier used to inflate the new View.
176 | * @attr ref android.R.styleable#ViewStub_layout
177 | * @see #setLayoutResource(int)
178 | * @see #setVisibility(int)
179 | * @see #inflate()
180 | */
181 | public int getLayoutResource() {
182 | return mLayoutResource;
183 | }
184 |
185 | /**
186 | * Specifies the layout resource to inflate when this StubbedView becomes
187 | * visible or invisible or when {@link #inflate()} is invoked. The View
188 | * created by inflating the layout resource is used to replace this
189 | * StubbedView in its parent.
190 | *
191 | * @param layoutResource A valid layout resource identifier (different from 0.)
192 | * @attr ref android.R.styleable#ViewStub_layout
193 | * @see #getLayoutResource()
194 | * @see #setVisibility(int)
195 | * @see #inflate()
196 | */
197 | // @android.view.RemotableViewMethod
198 | public void setLayoutResource(int layoutResource) {
199 | mLayoutResource = layoutResource;
200 | }
201 |
202 | /**
203 | * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
204 | * to use the default.
205 | */
206 | public void setLayoutInflater(LayoutInflater inflater) {
207 | mInflater = inflater;
208 | }
209 |
210 | /**
211 | * Get current {@link LayoutInflater} used in {@link #inflate()}.
212 | */
213 | public LayoutInflater getLayoutInflater() {
214 | return mInflater;
215 | }
216 |
217 | @Override
218 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
219 | setMeasuredDimension(0, 0);
220 | }
221 |
222 | @SuppressLint("MissingSuperCall")
223 | @Override
224 | public void draw(Canvas canvas) {
225 | }
226 |
227 | @Override
228 | protected void dispatchDraw(Canvas canvas) {
229 | }
230 |
231 | /**
232 | * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
233 | * {@link #inflate()} is invoked and this StubbedView is replaced in its
234 | * parent by the inflated layout resource. After that calls to this function
235 | * are passed through to the inflated view.
236 | *
237 | * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
238 | * @see #inflate()
239 | */
240 | @Override
241 | // @android.view.RemotableViewMethod
242 | public void setVisibility(int visibility) {
243 | if (mInflatedViewRef != null) {
244 | View view = mInflatedViewRef.get();
245 | if (view != null) {
246 | view.setVisibility(visibility);
247 | } else {
248 | throw new IllegalStateException(
249 | "setVisibility called on un-referenced view");
250 | }
251 | } else {
252 | super.setVisibility(visibility);
253 | if (visibility == VISIBLE || visibility == INVISIBLE) {
254 | inflate();
255 | }
256 | }
257 | }
258 |
259 | /**
260 | * Inflates the layout resource identified by {@link #getLayoutResource()}
261 | * and replaces this StubbedView in its parent by the inflated layout
262 | * resource.
263 | *
264 | * @return The inflated layout resource.
265 | */
266 | public View inflate() {
267 | final ViewParent viewParent = getParent();
268 |
269 | if (viewParent != null && viewParent instanceof ViewGroup) {
270 | if (mLayoutResource != 0) {
271 | final ViewGroup parent = (ViewGroup) viewParent;
272 | final LayoutInflater factory;
273 | if (mInflater != null) {
274 | factory = mInflater;
275 | } else {
276 | factory = LayoutInflater.from(getContext());
277 | }
278 | final View view = factory.inflate(mLayoutResource, parent,
279 | false);
280 |
281 | if (mInflatedId != NO_ID) {
282 | view.setId(mInflatedId);
283 | }
284 |
285 | final int index = parent.indexOfChild(this);
286 | parent.removeViewInLayout(this);
287 |
288 | final ViewGroup.LayoutParams layoutParams = getLayoutParams();
289 | if (layoutParams != null) {
290 | parent.addView(view, index, layoutParams);
291 | } else {
292 | parent.addView(view, index);
293 | }
294 |
295 | mInflatedViewRef = new WeakReference(view);
296 |
297 | if (mInflateListener != null) {
298 | mInflateListener.onInflate(this, view);
299 | }
300 |
301 | return view;
302 | } else {
303 | throw new IllegalArgumentException(
304 | "ViewStub must have a valid layoutResource");
305 | }
306 | } else {
307 | throw new IllegalStateException(
308 | "ViewStub must have a non-null ViewGroup viewParent");
309 | }
310 | }
311 |
312 | /**
313 | * Specifies the inflate listener to be notified after this ViewStub
314 | * successfully inflated its layout resource.
315 | *
316 | * @param inflateListener The OnInflateListener to notify of successful inflation.
317 | * @see android.view.ViewStub.OnInflateListener
318 | */
319 | public void setOnInflateListener(OnInflateListener inflateListener) {
320 | mInflateListener = inflateListener;
321 | }
322 |
323 | /**
324 | * Listener used to receive a notification after a ViewStub has successfully
325 | * inflated its layout resource.
326 | *
327 | * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
328 | */
329 | public static interface OnInflateListener {
330 | /**
331 | * Invoked after a ViewStub successfully inflated its layout resource.
332 | * This method is invoked after the inflated view was added to the
333 | * hierarchy but before the layout pass.
334 | *
335 | * @param stub The ViewStub that initiated the inflation.
336 | * @param inflated The inflated View.
337 | */
338 | void onInflate(ViewStub stub, View inflated);
339 | }
340 | }
341 |
--------------------------------------------------------------------------------
/android-pluginmgr/src/main/main.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/android-pluginmgr/target/.gitignore:
--------------------------------------------------------------------------------
1 | /classes/
2 |
--------------------------------------------------------------------------------