From bd54b7ddfbbbf6a430efc8674d294932d9e768e3 Mon Sep 17 00:00:00 2001 From: mrbesen Date: Tue, 26 Feb 2019 04:24:00 +0100 Subject: [PATCH] initial --- app/build.gradle | 40 ++++ app/proguard-rules.pro | 21 ++ .../runbin/ExampleInstrumentedTest.java | 26 +++ app/src/main/AndroidManifest.xml | 30 +++ .../java/de/mrbesen/runbin/MainActivity.java | 78 +++++++ .../java/de/mrbesen/runbin/RunService.java | 209 ++++++++++++++++++ .../drawable-v24/ic_launcher_foreground.xml | 34 +++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++ app/src/main/res/layout/activity_main.xml | 87 ++++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 11 + .../de/mrbesen/runbin/ExampleUnitTest.java | 17 ++ build.gradle | 26 +++ gradle.properties | 15 ++ gradle/wrapper/gradle-wrapper.properties | 5 + settings.gradle | 1 + 29 files changed, 789 insertions(+) create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/de/mrbesen/runbin/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/de/mrbesen/runbin/MainActivity.java create mode 100644 app/src/main/java/de/mrbesen/runbin/RunService.java create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/java/de/mrbesen/runbin/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 settings.gradle diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..ea85a5d --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,40 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "de.mrbesen.runbin" + minSdkVersion 21 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + /*externalNativeBuild { + cmake { + cppFlags "" + } + }*/ + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + /* + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + }*/ +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + compile group: 'commons-io', name: 'commons-io', version: '2.0.1' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/de/mrbesen/runbin/ExampleInstrumentedTest.java b/app/src/androidTest/java/de/mrbesen/runbin/ExampleInstrumentedTest.java new file mode 100644 index 0000000..a25cc57 --- /dev/null +++ b/app/src/androidTest/java/de/mrbesen/runbin/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package de.mrbesen.runbin; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("de.mrbesen.runbin", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3aac0a3 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/de/mrbesen/runbin/MainActivity.java b/app/src/main/java/de/mrbesen/runbin/MainActivity.java new file mode 100644 index 0000000..e03311a --- /dev/null +++ b/app/src/main/java/de/mrbesen/runbin/MainActivity.java @@ -0,0 +1,78 @@ +package de.mrbesen.runbin; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.text.Html; +import android.util.Log; +import android.view.View; +import android.widget.EditText; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +public class MainActivity extends AppCompatActivity { + + private TextView logView; + private ScrollView logLayout; + + // Used to load the 'native-lib' library on application startup. + /*static { + System.loadLibrary("native-lib"); + }*/ + + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + logView = (TextView) findViewById(R.id.logView); + logLayout = (ScrollView) findViewById(R.id.logScrollView); + } + + public void run(View v) { + logView.setText(""); + Intent serviceIntent = new Intent(this, RunService.class); + serviceIntent.putExtra("binary", ((EditText) findViewById(R.id.binname)).getText().toString()); + serviceIntent.putExtra("param", ((EditText) findViewById(R.id.param)).getText().toString()); + serviceIntent.putExtra("path", ((EditText) findViewById(R.id.binpath)).getText().toString()); + BroadcastReceiver callback = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + LocalBroadcastManager.getInstance(context).unregisterReceiver(this); + //process terminated + } + }; + LocalBroadcastManager.getInstance(this).registerReceiver(callback, new IntentFilter("callback")); + LocalBroadcastManager.getInstance(this).registerReceiver(addLog, new IntentFilter("addLog")); + startService(serviceIntent); + } + + private BroadcastReceiver addLog = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String message = intent.getStringExtra("message"); + final ScrollView scrollView = (ScrollView) logView.getParent(); + final int scrollY = scrollView.getScrollY(); + logView.append(Html.fromHtml(message)); + scrollView.post(new Runnable() { + @Override + public void run() { + scrollView.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + } + }; + + /** + * A native method that is implemented by the 'native-lib' native library, + * which is packaged with this application. + */ + /*public native String stringFromJNI();*/ +} diff --git a/app/src/main/java/de/mrbesen/runbin/RunService.java b/app/src/main/java/de/mrbesen/runbin/RunService.java new file mode 100644 index 0000000..0961e23 --- /dev/null +++ b/app/src/main/java/de/mrbesen/runbin/RunService.java @@ -0,0 +1,209 @@ +package de.mrbesen.runbin; + +import android.app.IntentService; +import android.app.Notification; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.BitmapFactory; +import android.os.PowerManager; +import android.support.v4.app.NotificationCompat; +import android.support.v4.content.LocalBroadcastManager; +import android.text.Html; +import android.util.Log; + +import org.apache.commons.io.FilenameUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Scanner; + +public class RunService extends IntentService { + + private String log; + + private static final String tag = "Service"; + public RunService() { + super("RunService"); + } + + private void addLog(String string) { + String logLine = ""; + String[] text = string.split("\\n"); + for (String line : text) { + String curText = Html.escapeHtml(line); + if (curText.toLowerCase().startsWith("log: ")) { + curText = curText.replaceFirst("(?i)log: ", ""); + } else if (curText.toLowerCase().startsWith("info:")) { + curText = curText.replaceFirst("(?i)info: ", ""); + curText = "" + curText + ""; + } else if (curText.toLowerCase().startsWith("warning: ")) { + curText = curText.replaceFirst("(?i)warning: ", ""); + curText = "" + curText + ""; + } else if (curText.toLowerCase().startsWith("error: ")) { + curText = curText.replaceFirst("(?i)error: ", ""); + curText = "" + curText + ""; + } + + logLine += "
" + curText; + } + log += logLine; + Intent intent = new Intent("addLog"); + intent.putExtra("message", logLine); + LocalBroadcastManager.getInstance(this).sendBroadcast(intent); + } + + @Override + protected void onHandleIntent(Intent intent) { + Log.d(tag, "Starting service..."); + final String binarydl = intent.getStringExtra("binary"); + final File execpath = new File(intent.getStringExtra("path")); + final String param = intent.getStringExtra("param"); + final File binpath = getFilesDir(); + execpath.mkdirs(); + binpath.mkdirs(); + final String binname = FilenameUtils.getName(binarydl); + CharSequence text = "Running..."; + + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0); + Notification notification = new NotificationCompat.Builder(this) + .setSmallIcon(R.mipmap.ic_launcher) + .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) + .setTicker(text) + .setContentTitle(text) + .setContentText(execpath + " " + binarydl) + .setContentIntent(contentIntent) + .build(); + startForeground(1, notification); + + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + final PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); + wakeLock.acquire(); + try { + // Make sure we can execute the binary + //install binary + + Log.d(tag, "Download " + binarydl + " into " + binpath + " called " + binname ); + File bin = new File(binpath, binname); + download(binarydl, bin); + + bin.setExecutable(true); + // Initiate ProcessBuilder with the command at the given location + ProcessBuilder processBuilder = new ProcessBuilder(bin.getAbsolutePath(), param); + processBuilder.directory(execpath.getAbsoluteFile()); + processBuilder.redirectErrorStream(true); + addLog("Info: Process is starting..."); + Log.d(tag, "Starting process. Folder: " + execpath + " with params: " + param); + final Process process = processBuilder.start(); + + // Open STDIN for the inputLine + final OutputStream cuberiteSTDIN = process.getOutputStream(); + + // Logging thread. This thread will check stdout (and stderr), color it and append it to the logView. This thread will wait only for next lines coming. if stdout is closed, this thread will exit + new Thread(new Runnable() { + @Override + public void run() { + Log.d(tag, "Starting logging thread..."); + Scanner processScanner = new Scanner(process.getInputStream()); + while (processScanner.hasNextLine()) { + String line = processScanner.nextLine(); + Log.i("Process", line); + addLog(line); + } + processScanner.close(); + } + }).start(); + + // Communication with the activity + BroadcastReceiver getLog = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Intent sendIntent = new Intent("fullLog"); + sendIntent.putExtra("message", log); + LocalBroadcastManager.getInstance(context).sendBroadcast(sendIntent); + } + }; + BroadcastReceiver kill = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + process.destroy(); + } + }; + BroadcastReceiver executeLine = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String line = intent.getStringExtra("message"); + try { + cuberiteSTDIN.write((line + "\n").getBytes()); + cuberiteSTDIN.flush(); + } catch (Exception e) { + Log.e(tag, "An error occurred when writing " + line + " to the STDIN", e);} + } + }; + LocalBroadcastManager.getInstance(this).registerReceiver(getLog, new IntentFilter("getLog")); + LocalBroadcastManager.getInstance(this).registerReceiver(kill, new IntentFilter("kill")); + LocalBroadcastManager.getInstance(this).registerReceiver(executeLine, new IntentFilter("executeLine")); + + // Wait for the process to end. Logic waits here until process has stopped. Everything after that is cleanup for the next run + process.waitFor(); + + Log.d(tag, "Stopped Process."); + + LocalBroadcastManager.getInstance(this).unregisterReceiver(getLog); + LocalBroadcastManager.getInstance(this).unregisterReceiver(kill); + LocalBroadcastManager.getInstance(this).unregisterReceiver(executeLine); + cuberiteSTDIN.close(); + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("callback")); + stopSelf(); + } catch (Exception e) { + Log.wtf(tag, "An error occurred when starting process", e); + addLog("error: Failed."); + } finally { + wakeLock.release(); + } + } + + private void download(String surl, final File file) { + InputStream input = null; + OutputStream output = null; + HttpURLConnection connection = null; + try { + URL url = new URL(surl); + connection = (HttpURLConnection) url.openConnection(); + connection.connect(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) + return; + + // download the file + input = connection.getInputStream(); + output = new FileOutputStream(file); + + byte data[] = new byte[4096]; + int count; + while ((count = input.read(data)) != -1) { + output.write(data, 0, count); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (output != null) + output.close(); + if (input != null) + input.close(); + } catch (IOException ignored) { + } + + if (connection != null) + connection.disconnect(); + } + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..e55b9bb --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + +