diff --git a/app/build.gradle b/app/build.gradle
index 07f3376c..a8584cff 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -84,6 +84,7 @@ repositories {
dependencies {
implementation 'androidx.browser:browser:1.8.0'
+ implementation "androidx.annotation:annotation:1.8.2"
implementation "androidx.appcompat:appcompat:1.7.0"
implementation 'androidx.core:core:1.13.1'
implementation 'androidx.webkit:webkit:1.11.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a781640f..eebfe31d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -45,16 +45,35 @@
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -70,5 +89,15 @@
+
+
+
+
diff --git a/app/src/main/java/com/dergoogler/core/NativeSuFile.java b/app/src/main/java/com/dergoogler/core/NativeSuFile.java
index 6c656e90..a4729562 100644
--- a/app/src/main/java/com/dergoogler/core/NativeSuFile.java
+++ b/app/src/main/java/com/dergoogler/core/NativeSuFile.java
@@ -1,12 +1,17 @@
package com.dergoogler.core;
-import android.graphics.BitmapFactory;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.OpenableColumns;
import android.util.Base64;
import android.util.Base64OutputStream;
import android.util.Log;
import android.webkit.JavascriptInterface;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.dergoogler.mmrl.MainActivity;
import com.topjohnwu.superuser.io.SuFile;
@@ -16,7 +21,9 @@
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
+import java.io.File;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -61,7 +68,7 @@ public String read(String def) {
return sb.toString();
}
} catch (IOException e) {
- Log.e( TAG + ":read", e.toString());
+ Log.e(TAG + ":read", e.toString());
return def;
}
}
@@ -92,7 +99,7 @@ public String readAsBase64() {
}
}
- private void closeQuietly(Closeable closeable) {
+ private void closeQuietly(Closeable closeable) {
try {
closeable.close();
} catch (IOException e) {
@@ -199,7 +206,6 @@ public String readFile(String path) {
}
}
- @NonNull
@JavascriptInterface
public String listFiles(String path) {
String[] modules = new SuFile(path).list();
@@ -225,4 +231,70 @@ public void deleteRecursive(String path) {
public boolean existFile(String path) {
return new SuFile(path).exists();
}
+
+ @JavascriptInterface
+ public @Nullable String getSharedFile() {
+ Intent intent = ctx.getIntent();
+ Uri uri = intent.getData();
+ if (uri != null) {
+ return createCopyAndReturnRealPath(uri);
+ } else {
+ return null;
+ }
+ }
+
+ public @Nullable String createCopyAndReturnRealPath(Uri uri) {
+ final ContentResolver contentResolver = ctx.getContentResolver();
+ if (contentResolver == null)
+ return null;
+
+ // Create file path inside app's data dir
+ String filePath = ctx.getApplicationInfo().dataDir + File.separator + "cache" + File.separator
+ + getFileName(uri);
+
+ File file = new File(filePath);
+ try {
+ InputStream inputStream = contentResolver.openInputStream(uri);
+ if (inputStream == null)
+ return null;
+
+ OutputStream outputStream = new FileOutputStream(file);
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = inputStream.read(buf)) > 0)
+ outputStream.write(buf, 0, len);
+
+ outputStream.close();
+ inputStream.close();
+ } catch (IOException ignore) {
+ return null;
+ }
+
+ return file.getAbsolutePath();
+ }
+
+ public String getFileName(Uri uri) {
+ String result = null;
+ if ("content".equals(uri.getScheme())) {
+ try (Cursor cursor = ctx.getContentResolver().query(uri, null, null, null, null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ if (index != -1) {
+ result = cursor.getString(index);
+ }
+ }
+ }
+ }
+ if (result == null) {
+ result = uri.getPath();
+ if (result != null) {
+ int cut = result.lastIndexOf('/');
+ if (cut != -1) {
+ result = result.substring(cut + 1);
+ }
+ }
+ }
+ return result;
+ }
+
}
diff --git a/app/src/main/java/com/dergoogler/mmrl/MainActivity.java b/app/src/main/java/com/dergoogler/mmrl/MainActivity.java
index 5601e897..794bef87 100644
--- a/app/src/main/java/com/dergoogler/mmrl/MainActivity.java
+++ b/app/src/main/java/com/dergoogler/mmrl/MainActivity.java
@@ -1,11 +1,17 @@
package com.dergoogler.mmrl;
import android.annotation.SuppressLint;
-import android.graphics.Color;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.StrictMode;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -26,11 +32,14 @@
import com.dergoogler.core.NativeBuildConfig;
import com.dergoogler.core.NativeView;
import com.dergoogler.core.NativeSuZip;
+import com.topjohnwu.superuser.io.SuFile;
import org.apache.cordova.*;
import org.apache.cordova.engine.SystemWebChromeClient;
import org.apache.cordova.engine.SystemWebViewEngine;
+import java.io.File;
+
public class MainActivity extends CordovaActivity {
private WebView wv;
@@ -114,7 +123,6 @@ public void onGlobalLayout() {
wv.addJavascriptInterface(new NativeLog(), "__log__");
wv.addJavascriptInterface(new NativeSuZip(), "__suzip__");
-
wv.setWebChromeClient(new SystemWebChromeClient((SystemWebViewEngine) wve) {
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 00000000..d65fd755
--- /dev/null
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/activitys/MainApplication.tsx b/src/activitys/MainApplication.tsx
index 9651f14c..fbd9309f 100644
--- a/src/activitys/MainApplication.tsx
+++ b/src/activitys/MainApplication.tsx
@@ -25,6 +25,12 @@ import InstallTerminalV2Activity from "./InstallTerminalV2Activity";
import { Chooser } from "@Native/Chooser";
import { useConfirm } from "material-ui-confirm";
import VolunteerActivismIcon from "@mui/icons-material/VolunteerActivism";
+import { SuFile } from "@Native/SuFile";
+import { Log } from "@Native/Log";
+import { SuZip } from "@Native/SuZip";
+import { Properties } from "properties-file";
+
+const TAG = "MainApplication";
const MainApplication = () => {
const { context } = useActivity();
@@ -60,6 +66,41 @@ const MainApplication = () => {
[index]
);
+ React.useEffect(() => {
+ const sharedFile = SuFile.getSharedFile();
+ if (sharedFile) {
+ const file = new SuFile(sharedFile);
+
+ if (file.exist()) {
+ const zipFile = new SuZip(file.getPath(), "module.prop");
+ const props = new Properties(zipFile.read()).toObject();
+
+ if (!props.id) {
+ return;
+ }
+
+ confirm({
+ title: strings("install_module", { name: props.name }),
+ description: strings("install_module_dialog_desc", { name: {props.name} }),
+ confirmationText: strings("yes"),
+ })
+ .then(() => {
+ context.pushPage({
+ component: InstallTerminalV2Activity,
+ key: "InstallTerminalV2Activity",
+ extra: {
+ exploreInstall: false,
+ modSource: [file.getPath()],
+ },
+ });
+ })
+ .catch(() => {});
+ } else {
+ Log.i(TAG, "Unable to find shared file");
+ }
+ }
+ }, []);
+
React.useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const id = urlParams.get("module");
diff --git a/src/components/onsenui/RouterNavigator.tsx b/src/components/onsenui/RouterNavigator.tsx
index f3199ad1..388ca428 100644
--- a/src/components/onsenui/RouterNavigator.tsx
+++ b/src/components/onsenui/RouterNavigator.tsx
@@ -247,7 +247,6 @@ class RouterNavigatorClass extends React.Component {
} = this.props;
const pagesToRender = this.state.internalStack.map((item) => {
- console.log(item);
return (
diff --git a/src/native/SuFile.ts b/src/native/SuFile.ts
index 33c691de..59cae52e 100644
--- a/src/native/SuFile.ts
+++ b/src/native/SuFile.ts
@@ -7,6 +7,7 @@ interface NativeSuFile extends NativeSuFileV2 {
deleteFile(path: string): boolean;
deleteRecursive(path: string): boolean;
existFile(path: string): boolean;
+ getSharedFile(): string | null;
}
interface NativeSuFileV2 {
@@ -68,18 +69,17 @@ class SuFile extends Native {
private _restrictedPaths = [/(\/data\/data\/(.+)\/?|(\/storage\/emulated\/0|\/sdcard)\/Android\/(data|media|obb)(.+)?)\/?/i];
- public constructor(path: string, opt?: SuFileoptions) {
+ public constructor(path?: string, opt?: SuFileoptions) {
super(window.__sufile__);
this._readDefaultValue = opt?.readDefaultValue || "";
+ this._path = path ? String(path) : "";
- if (typeof path !== "string") throw new TypeError("Path name isn't a string");
- if (this._isRestrictedPath(path)) {
+ if (this._isRestrictedPath(this._path)) {
throw new Error(`SuFile tried to access "${path}" which has been blocked due security.`);
}
- this._path = path;
if (this.isAndroid) {
- this._file = this.interface.v2.bind(this.interface)(path);
+ this._file = this.interface.v2.bind(this.interface)(this._path);
}
}
@@ -251,6 +251,13 @@ class SuFile extends Native {
public static create(path: string, type: number = SuFile.NEW_FILE): boolean {
return new SuFile(path).create(type);
}
+
+ public static getSharedFile(): string | undefined {
+ if (this.isAndroid) {
+ return this.static.interface.getSharedFile();
+ }
+ return undefined;
+ }
}
export { SuFile };