Skip to content

Commit 1413074

Browse files
committed
We now execute Python code!
Finally! It's not perfect, but it works. If anyone has any advice on how to improve the current startup and communication system, please head over to Abestanis/APython#9.
1 parent 13e358d commit 1413074

17 files changed

+275
-164
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@
77
/captures
88
Thumbs.db
99
.idea
10+
/app/src/main/libs
11+

app/app.iml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
5454
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
5555
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
56-
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
5756
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
5857
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
5958
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />

app/build.gradle

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import java.security.KeyException
2+
13
apply plugin: 'com.android.application'
24

35
android {
@@ -17,6 +19,32 @@ android {
1719
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
1820
}
1921
}
22+
23+
sourceSets.main{
24+
jniLibs.srcDirs = ['src/main/libs']
25+
jni.srcDirs = [] //disable automatic ndk-build
26+
}
27+
28+
tasks.whenTaskAdded {
29+
task ->
30+
if (task.name == 'compileDebugJava') {
31+
//noinspection GroovyAssignabilityCheck
32+
task.dependsOn ndkBuild
33+
}
34+
}
35+
36+
Properties properties = new Properties()
37+
properties.load(project.rootProject.file('local.properties').newDataInputStream())
38+
def ndkDir = properties.getProperty('ndk.dir')
39+
//noinspection GroovyAssignabilityCheck
40+
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK.') {
41+
workingDir file('src/main')
42+
commandLine 'cmd', '/c', "$ndkDir\\ndk-build", 'NDK_OUT=' + getBuildDir().absolutePath + '\\intermediates\\ndk\\obj'
43+
}
44+
ndkBuild.onlyIf {
45+
if (ndkDir == null) { throw new KeyException('NDK location not found! Define the location with ndk.dir in the local.properties file.')}
46+
true
47+
}
2048
}
2149

2250
dependencies {

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@
99
android:theme="@style/AppTheme" >
1010
<activity
1111
android:name=".MainActivity"
12-
android:label="@string/app_name" >
12+
android:label="@string/app_name"
13+
android:theme="@android:style/Theme.NoDisplay">
1314
<intent-filter>
1415
<action android:name="android.intent.action.MAIN" />
1516
<category android:name="android.intent.category.LAUNCHER" />
1617
</intent-filter>
1718
</activity>
18-
<service android:name=".PythonHostCommunicationService"
19-
android:exported="true"
20-
android:permission="com.python.permission.PYTHONHOST"/>
19+
<activity
20+
android:name=".PythonExecuteActivity"
21+
android:permission="com.python.permission.PYTHONHOST"
22+
android:exported="true" />
2123
</application>
2224

2325
</manifest>

app/src/main/java/com/apython/python/apython_pyapp/MainActivity.java

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
package com.apython.python.apython_pyapp;
22

33
import android.app.Activity;
4-
import android.content.Intent;
4+
import android.content.Context;
55
import android.os.Bundle;
6+
import android.util.Log;
7+
8+
import java.io.BufferedInputStream;
9+
import java.io.File;
10+
import java.io.FileOutputStream;
11+
import java.io.IOException;
12+
import java.util.zip.ZipEntry;
13+
import java.util.zip.ZipInputStream;
614

715

816
public class MainActivity extends Activity {
@@ -16,7 +24,23 @@ public class MainActivity extends Activity {
1624
@Override
1725
protected void onCreate(Bundle savedInstanceState) {
1826
super.onCreate(savedInstanceState);
19-
//setContentView(R.layout.activity_main);
27+
File codeDir = getCodeDir(this.getApplicationContext());
28+
String[] children = codeDir.list();
29+
if (codeDir.exists()) { // TODO: Make a versioning system
30+
for (String child : children) {
31+
new File(codeDir, child).delete();
32+
}
33+
codeDir.delete();
34+
}
35+
if (!codeDir.isDirectory()) {
36+
if (!(codeDir.mkdirs() || codeDir.isDirectory())) {
37+
Log.e(MainActivity.TAG, "Failed to create the 'code' directory!");
38+
// TODO: Handle this.
39+
finish();
40+
return;
41+
}
42+
this.extractPythonCode(codeDir);
43+
}
2044
this.connectionManager = new PythonHostConnectionManager();
2145
}
2246

@@ -26,8 +50,49 @@ protected void onStart() {
2650
connectionManager.connectToPythonHost(this);
2751
}
2852

29-
@Override
30-
public void onActivityResult(int requestCode, int resultCode, Intent result) {
31-
this.connectionManager.handleActivityResult(this, requestCode, resultCode, result);
53+
public static File getCodeDir(Context context) {
54+
return new File(context.getFilesDir(), "code");
55+
}
56+
57+
protected boolean extractPythonCode(File destination) {
58+
Log.d(TAG, "Extracting code to " + destination); // TODO: Maybe also leave it in the zip format.
59+
ZipInputStream archive;
60+
try {
61+
String filename;
62+
archive = new ZipInputStream(new BufferedInputStream(getResources().openRawResource(R.raw.code)));
63+
ZipEntry zipEntry;
64+
byte[] buffer = new byte[1024];
65+
int count;
66+
67+
while ((zipEntry = archive.getNextEntry()) != null) {
68+
filename = zipEntry.getName();
69+
if (zipEntry.isDirectory()) {
70+
// Create the directory if not exists
71+
File directory = new File(destination, filename);
72+
if (!(directory.mkdirs() || directory.isDirectory())) {
73+
Log.e(TAG, "Could not save directory '" + filename + "' to path '"
74+
+ directory.getAbsolutePath() + "' while trying to install the Python modules!");
75+
archive.close();
76+
return false;
77+
}
78+
} else {
79+
// Save the file
80+
File file = new File(destination, filename);
81+
FileOutputStream outputFile = new FileOutputStream(file);
82+
while ((count = archive.read(buffer)) != -1) {
83+
outputFile.write(buffer, 0, count);
84+
}
85+
outputFile.close();
86+
}
87+
archive.closeEntry();
88+
}
89+
archive.close();
90+
return true;
91+
}
92+
catch(IOException e) {
93+
Log.e(TAG, "Failed to extract the Python modules!");
94+
e.printStackTrace();
95+
return false;
96+
}
3297
}
3398
}

app/src/main/java/com/apython/python/apython_pyapp/MessageIDs.java

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.apython.python.apython_pyapp;
2+
3+
/*
4+
* Execute the python code of this app with the interpreter libraries specified by the Python host.
5+
*
6+
* Created by Sebastian on 27.06.2015.
7+
*/
8+
9+
import android.util.Log;
10+
11+
import java.util.ArrayList;
12+
13+
public class PythonExecute {
14+
15+
public PythonExecute(ArrayList<String> pythonLibs) {
16+
for (String lib : pythonLibs) {
17+
Log.d(MainActivity.TAG, "Loading lib from " + lib);
18+
System.load(lib);
19+
}
20+
}
21+
22+
public native int startApp(String appBaseDir, String pythonHome, String pythonTemp, String appTag);
23+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.apython.python.apython_pyapp;
2+
3+
import android.app.Activity;
4+
import android.content.Intent;
5+
import android.os.Bundle;
6+
7+
import java.util.ArrayList;
8+
9+
/*
10+
* This activity will be called by the Python interpreter.
11+
* The intent that started the activity will provide the Python home path and libs.
12+
* The calling app also needs the permission "com.python.permission.PYTHONHOST".
13+
* This way we don't introduce a security hole because we execute code from a remote location.
14+
*
15+
* Created by Sebastian on 28.06.2015.
16+
*/
17+
18+
public class PythonExecuteActivity extends Activity {
19+
20+
static {
21+
System.loadLibrary("envSet");
22+
}
23+
24+
private PythonExecute pyApp;
25+
26+
@Override
27+
protected void onCreate(Bundle savedInstanceState) {
28+
super.onCreate(savedInstanceState);
29+
Intent result = getIntent();
30+
this.setEnv("JAVA_PYTHON_MAIN_CLASSPATH", PythonExecute.class.getCanonicalName().replace('.', '/'));
31+
ArrayList<String> pythonLibs = result.getStringArrayListExtra("pythonLibs");
32+
this.pyApp = new PythonExecute(pythonLibs);
33+
}
34+
35+
@Override
36+
protected void onStart() {
37+
super.onStart();
38+
this.pyApp.startApp(MainActivity.getCodeDir(getApplicationContext()).getAbsolutePath(),
39+
getIntent().getStringExtra("pythonHome"),
40+
getCacheDir().getAbsolutePath(),
41+
MainActivity.TAG);
42+
finish();
43+
}
44+
45+
private native void setEnv(String key, String value);
46+
}

app/src/main/java/com/apython/python/apython_pyapp/PythonHostCommunicationHandler.java

Lines changed: 0 additions & 49 deletions
This file was deleted.

app/src/main/java/com/apython/python/apython_pyapp/PythonHostCommunicationService.java

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)