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 };