Skip to content

Commit ab81cee

Browse files
committed
Initial project
0 parents  commit ab81cee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+971
-0
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.iml
2+
.gradle
3+
/local.properties
4+
/.idea
5+
/build
6+
/captures

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License
2+
3+
Copyright (c) 2016 Victor Melnik
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

app/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

app/build.gradle

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
apply plugin: 'com.android.application'
2+
3+
android {
4+
compileSdkVersion 23
5+
buildToolsVersion "23.0.3"
6+
7+
defaultConfig {
8+
applicationId "com.annimon.androidplugindemo"
9+
minSdkVersion 9
10+
targetSdkVersion 23
11+
versionCode 1
12+
versionName "1.0"
13+
}
14+
buildTypes {
15+
release {
16+
minifyEnabled false
17+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18+
}
19+
}
20+
}
21+
22+
dependencies {
23+
compile fileTree(dir: 'libs', include: ['*.jar'])
24+
compile 'com.android.support:appcompat-v7:23.3.0'
25+
}

app/proguard-rules.pro

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Add project specific ProGuard rules here.
2+
# By default, the flags in this file are appended to flags specified
3+
# in D:\Android\android-sdk/tools/proguard/proguard-android.txt
4+
# You can edit the include path and order by changing the proguardFiles
5+
# directive in build.gradle.
6+
#
7+
# For more details, see
8+
# http://developer.android.com/guide/developing/tools/proguard.html
9+
10+
# Add any project specific keep options here:
11+
12+
# If your project uses WebView with JS, uncomment the following
13+
# and specify the fully qualified class name to the JavaScript interface
14+
# class:
15+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16+
# public *;
17+
#}

app/src/main/AndroidManifest.xml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="com.annimon.androidplugindemo">
4+
5+
<application
6+
android:allowBackup="true"
7+
android:icon="@mipmap/ic_launcher"
8+
android:label="@string/app_name"
9+
android:supportsRtl="true"
10+
android:theme="@style/AppTheme">
11+
<activity android:name=".MainActivity">
12+
<intent-filter>
13+
<action android:name="android.intent.action.MAIN" />
14+
15+
<category android:name="android.intent.category.LAUNCHER" />
16+
</intent-filter>
17+
</activity>
18+
</application>
19+
20+
</manifest>

app/src/main/assets/library.apk

2.34 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.annimon.androidplugindemo;
2+
3+
import android.content.Context;
4+
import android.support.annotation.Nullable;
5+
import java.io.File;
6+
import java.io.FileOutputStream;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.io.OutputStream;
10+
11+
public class IOUtil {
12+
13+
@Nullable
14+
public static String extractAssetToInternalStorage(Context context, String fileName) {
15+
try {
16+
final InputStream is = context.getAssets().open(fileName);
17+
final File file = new File(context.getFilesDir(), fileName);
18+
final OutputStream os = new FileOutputStream(file);
19+
final int length = 1024;
20+
final byte[] buffer = new byte[length];
21+
int readed;
22+
while ((readed = is.read(buffer)) != -1) {
23+
os.write(buffer, 0, readed);
24+
}
25+
os.flush();
26+
os.close();
27+
is.close();
28+
return file.getAbsolutePath();
29+
} catch (IOException e) {
30+
return null;
31+
}
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package com.annimon.androidplugindemo;
2+
3+
import android.content.Context;
4+
import android.content.Intent;
5+
import android.content.pm.ApplicationInfo;
6+
import android.content.pm.PackageManager;
7+
import android.content.res.Resources;
8+
import android.os.Bundle;
9+
import android.support.v7.app.AppCompatActivity;
10+
import android.support.v7.widget.Toolbar;
11+
import android.view.View;
12+
import android.widget.TextView;
13+
import java.lang.reflect.Constructor;
14+
import java.lang.reflect.Field;
15+
import java.lang.reflect.Method;
16+
import java.util.List;
17+
import dalvik.system.DexClassLoader;
18+
19+
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
20+
21+
private static final String CATEGORY_PLUGIN = "com.annimon.plugin.PLUGIN_APPLICATION";
22+
private static final String PLUGIN_PACKAGE = "com.annimon.plugin";
23+
24+
private Toolbar toolbar;
25+
private TextView infoTextView;
26+
27+
@Override
28+
protected void onCreate(Bundle savedInstanceState) {
29+
super.onCreate(savedInstanceState);
30+
setContentView(R.layout.activity_main);
31+
32+
infoTextView = (TextView) findViewById(R.id.info);
33+
toolbar = (Toolbar) findViewById(R.id.toolbar);
34+
setSupportActionBar(toolbar);
35+
36+
int[] ids = {R.id.launch_plugin_activity, R.id.invoke_code, R.id.invoke_library};
37+
for (int id : ids) {
38+
findViewById(id).setOnClickListener(this);
39+
}
40+
}
41+
42+
@Override
43+
public void onClick(View v) {
44+
switch (v.getId()) {
45+
case R.id.launch_plugin_activity:
46+
launchPluginActivity();
47+
break;
48+
case R.id.invoke_code:
49+
invokeCode();
50+
break;
51+
case R.id.invoke_library:
52+
invokeLibrary();
53+
break;
54+
}
55+
}
56+
57+
/*
58+
* Checks if plugin application is installed.
59+
*/
60+
private boolean isPluginInstalled() {
61+
if (!PackageUtil.isApplicationInstalled(this, PLUGIN_PACKAGE)) {
62+
infoTextView.setText(R.string.plugin_not_installed);
63+
return false;
64+
}
65+
return true;
66+
}
67+
68+
/*
69+
* Gets information about plugin application (label, icon, resources), then launch activity.
70+
*/
71+
private void launchPluginActivity() {
72+
PackageManager pm = getApplicationContext().getPackageManager();
73+
74+
// Search plugin applications
75+
Intent queryIntent = new Intent(Intent.ACTION_MAIN);
76+
queryIntent.addCategory(CATEGORY_PLUGIN);
77+
final List<ApplicationInfo> infos = PackageUtil.filterApplicationsByIntent(pm, queryIntent);
78+
if (infos.isEmpty()) {
79+
infoTextView.setText(R.string.plugin_not_installed);
80+
return;
81+
}
82+
// Get first app
83+
ApplicationInfo appInfo = infos.get(0);
84+
85+
// Get simple information from ApplicationInfo
86+
StringBuilder info = new StringBuilder();
87+
info.append("Label: ").append(appInfo.loadLabel(pm)).append("\n");
88+
info.append("Package name: ").append(appInfo.packageName).append("\n");
89+
info.append("Source dir: ").append(appInfo.sourceDir).append("\n");
90+
info.append("Target SDK Version: ").append(appInfo.targetSdkVersion).append("\n");
91+
92+
toolbar.setLogo(appInfo.loadIcon(pm));
93+
94+
// Get resources
95+
Resources pluginResources = PackageUtil.getResources(pm, appInfo);
96+
if (pluginResources != null) {
97+
// Get string resource
98+
int plugin_text = pluginResources.getIdentifier("plugin_text", "string", appInfo.packageName);
99+
if (plugin_text > 0) {
100+
info.append("Plugin text: ").append(pluginResources.getString(plugin_text)).append("\n");
101+
}
102+
103+
// Get color resource
104+
int colorPrimaryDark = pluginResources.getIdentifier("colorPrimaryDark", "color", appInfo.packageName);
105+
if (colorPrimaryDark > 0) {
106+
toolbar.setBackgroundColor(pluginResources.getColor(colorPrimaryDark));
107+
}
108+
}
109+
infoTextView.setText(info);
110+
111+
// Start Activity
112+
Intent activityIntent = pm.getLaunchIntentForPackage(appInfo.packageName);
113+
startActivity(activityIntent);
114+
}
115+
116+
/**
117+
* Invokes code from plugin application in separate ClassLoader.
118+
*/
119+
private void invokeCode() {
120+
if (!isPluginInstalled()) return;
121+
122+
Context pluginContext = PackageUtil.getPackageContext(this, PLUGIN_PACKAGE);
123+
if (pluginContext == null) return;
124+
ClassLoader classLoader = pluginContext.getClassLoader();
125+
126+
StringBuilder info = new StringBuilder();
127+
try {
128+
// Invoke class via Reflection API
129+
Class<?> pluginClass = classLoader.loadClass(PLUGIN_PACKAGE + ".Plugin");
130+
131+
Field goldenRatio = pluginClass.getDeclaredField("GOLDEN_RATIO");
132+
info.append("GOLDEN_RATIO: ").append(goldenRatio.getDouble(null)).append("\n");
133+
134+
Method sum = pluginClass.getDeclaredMethod("sum", int.class, int.class);
135+
info.append("sum(42, 280): ").append(sum.invoke(null, 42, 280)).append("\n");
136+
137+
Method reverse = pluginClass.getDeclaredMethod("reverse", String.class);
138+
info.append("reverse(\"abcdefgh\"): ").append(reverse.invoke(null, "abcdefgh"));
139+
} catch (Exception e) {
140+
info.append("Unable to retrieve data, ").append(e.toString());
141+
}
142+
infoTextView.setText(info);
143+
}
144+
145+
/**
146+
* Extracts library.apk to internal storage, then invoke code.
147+
*/
148+
private void invokeLibrary() {
149+
final String filename = "library.apk";
150+
String path = IOUtil.extractAssetToInternalStorage(this, filename);
151+
if (path == null) {
152+
infoTextView.setText("Unable to extract " + filename + " to internal storage");
153+
return;
154+
}
155+
156+
StringBuilder info = new StringBuilder();
157+
try {
158+
DexClassLoader dexClassLoader = new DexClassLoader(
159+
path, getFilesDir().getAbsolutePath(), null,
160+
getClassLoader());
161+
final Class<?> infoClass = dexClassLoader.loadClass("plugin.Info");
162+
163+
// Invoke default constructor
164+
Object defaultObject = infoClass.newInstance();
165+
Method getInfo = infoClass.getDeclaredMethod("getInfo");
166+
String defaultInfo = (String) getInfo.invoke(defaultObject);
167+
info.append("new Info().getInfo(): ").append(defaultInfo).append("\n");
168+
169+
// Invoke constructor with argument
170+
Constructor constructor = infoClass.getDeclaredConstructor(String.class);
171+
Object testObject = constructor.newInstance("Test");
172+
String testInfo = (String) getInfo.invoke(testObject);
173+
info.append("new Info(\"Test\").getInfo(): ").append(testInfo);
174+
} catch (Exception e) {
175+
info.append("Unable to retrieve data, ").append(e.toString());
176+
}
177+
infoTextView.setText(info);
178+
}
179+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.annimon.androidplugindemo;
2+
3+
import android.content.Context;
4+
import android.content.Intent;
5+
import android.content.pm.ApplicationInfo;
6+
import android.content.pm.PackageManager;
7+
import android.content.pm.ResolveInfo;
8+
import android.content.res.Resources;
9+
import android.support.annotation.NonNull;
10+
import android.support.annotation.Nullable;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
14+
public class PackageUtil {
15+
16+
public static boolean isApplicationInstalled(Context context, String packageName) {
17+
return (getApplicationInfo(context, packageName) != null);
18+
}
19+
20+
@Nullable
21+
public static ApplicationInfo getApplicationInfo(Context context, String packageName) {
22+
PackageManager pacman = context.getApplicationContext().getPackageManager();
23+
return getApplicationInfo(pacman, packageName);
24+
}
25+
26+
@Nullable
27+
public static ApplicationInfo getApplicationInfo(PackageManager pacman, String packageName) {
28+
try {
29+
return pacman.getApplicationInfo(packageName, 0);
30+
} catch (PackageManager.NameNotFoundException e) {
31+
return null;
32+
}
33+
}
34+
35+
@Nullable
36+
public static Resources getResources(PackageManager pacman, ApplicationInfo appInfo) {
37+
try {
38+
return pacman.getResourcesForApplication(appInfo);
39+
} catch (PackageManager.NameNotFoundException e) {
40+
return null;
41+
}
42+
}
43+
44+
@NonNull
45+
public static List<ApplicationInfo> filterApplicationsByIntent(PackageManager pm, Intent intent) {
46+
final List<ApplicationInfo> result = new ArrayList<>();
47+
List<ResolveInfo> infos = pm.queryIntentActivities(intent, 0);
48+
for (ResolveInfo resolveInfo : infos) {
49+
if (resolveInfo.activityInfo != null) {
50+
result.add(resolveInfo.activityInfo.applicationInfo);
51+
}
52+
}
53+
return result;
54+
}
55+
56+
@Nullable
57+
public static Context getPackageContext(Context context, String packageName) {
58+
try {
59+
return context.getApplicationContext().createPackageContext(packageName,
60+
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
61+
} catch (PackageManager.NameNotFoundException e) {
62+
return null;
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)