diff --git a/package-lock.json b/package-lock.json
index 80400ab01..0fa28bd8f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10149,126 +10149,6 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
- "node_modules/ts-loader": {
- "version": "9.5.2",
- "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz",
- "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "^4.1.0",
- "enhanced-resolve": "^5.0.0",
- "micromatch": "^4.0.0",
- "semver": "^7.3.4",
- "source-map": "^0.7.4"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "typescript": "*",
- "webpack": "^5.0.0"
- }
- },
- "node_modules/ts-loader/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/ts-loader/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/ts-loader/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/ts-loader/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/ts-loader/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ts-loader/node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/ts-loader/node_modules/source-map": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
- "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/ts-loader/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/tslib": {
"version": "1.14.1",
"license": "0BSD"
@@ -10324,6 +10204,8 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/package.json b/package.json
index f29ca0c5c..cb7df5654 100644
--- a/package.json
+++ b/package.json
@@ -129,4 +129,4 @@
"yargs": "^17.7.2"
},
"browserslist": "cover 100%,not android < 5"
-}
+}
\ No newline at end of file
diff --git a/src/plugins/terminal/plugin.xml b/src/plugins/terminal/plugin.xml
index 11c8fb704..418212871 100644
--- a/src/plugins/terminal/plugin.xml
+++ b/src/plugins/terminal/plugin.xml
@@ -18,15 +18,28 @@
-
+
+
-
+
-
+
+
+
+
+
+
+
diff --git a/src/plugins/terminal/scripts/init-sandbox.sh b/src/plugins/terminal/scripts/init-sandbox.sh
index e3844cc38..538e02738 100644
--- a/src/plugins/terminal/scripts/init-sandbox.sh
+++ b/src/plugins/terminal/scripts/init-sandbox.sh
@@ -25,4 +25,4 @@ else
fi
-$PROOT --link2symlink -L --sysvipc --kill-on-exit -b $PREFIX:$PREFIX -b /data:/data -b /system:/system -b /vendor:/vendor -b /sdcard:/sdcard -b /storage:/storage -S $PREFIX/alpine /bin/sh $PREFIX/init-alpine.sh "$@"
+$PROOT --link2symlink -L --sysvipc --kill-on-exit -b $PREFIX:$PREFIX -b /data:/data -b /system:/system -b /vendor:/vendor -b /sdcard:/sdcard -b /storage:/storage -b $PREFIX/public:/public -S $PREFIX/alpine /bin/sh $PREFIX/init-alpine.sh "$@"
diff --git a/src/plugins/terminal/src/android/AlpineDocumentProvider.java b/src/plugins/terminal/src/android/AlpineDocumentProvider.java
new file mode 100644
index 000000000..6e2d3cdf3
--- /dev/null
+++ b/src/plugins/terminal/src/android/AlpineDocumentProvider.java
@@ -0,0 +1,336 @@
+package com.foxdebug.acode.rk.exec.terminal;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Point;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Locale;
+import com.foxdebug.acode.R;
+
+public class AlpineDocumentProvider extends DocumentsProvider {
+
+ private static final String ALL_MIME_TYPES = "*/*";
+ private static final File BASE_DIR = new File("/data/data/com.foxdebug.acode/files/public");
+
+ public AlpineDocumentProvider(){
+ if (!BASE_DIR.exists()) {
+ BASE_DIR.mkdirs();
+ }
+ }
+
+ // The default columns to return information about a root if no specific
+ // columns are requested in a query.
+ private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
+ DocumentsContract.Root.COLUMN_ROOT_ID,
+ DocumentsContract.Root.COLUMN_MIME_TYPES,
+ DocumentsContract.Root.COLUMN_FLAGS,
+ DocumentsContract.Root.COLUMN_ICON,
+ DocumentsContract.Root.COLUMN_TITLE,
+ DocumentsContract.Root.COLUMN_SUMMARY,
+ DocumentsContract.Root.COLUMN_DOCUMENT_ID,
+ DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
+ };
+
+ // The default columns to return information about a document if no specific
+ // columns are requested in a query.
+ private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
+ DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+ DocumentsContract.Document.COLUMN_MIME_TYPE,
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME,
+ DocumentsContract.Document.COLUMN_LAST_MODIFIED,
+ DocumentsContract.Document.COLUMN_FLAGS,
+ DocumentsContract.Document.COLUMN_SIZE
+ };
+
+ @Override
+ public Cursor queryRoots(String[] projection) {
+ MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_ROOT_PROJECTION
+ );
+ String applicationName = "Acode";
+
+ MatrixCursor.RowBuilder row = result.newRow();
+ row.add(DocumentsContract.Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
+ row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
+ row.add(DocumentsContract.Root.COLUMN_SUMMARY, null);
+ row.add(
+ DocumentsContract.Root.COLUMN_FLAGS,
+ DocumentsContract.Root.FLAG_SUPPORTS_CREATE |
+ DocumentsContract.Root.FLAG_SUPPORTS_SEARCH |
+ DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
+ );
+ row.add(DocumentsContract.Root.COLUMN_TITLE, applicationName);
+ row.add(DocumentsContract.Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
+ row.add(DocumentsContract.Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
+ row.add(DocumentsContract.Root.COLUMN_ICON, R.mipmap.ic_launcher);
+ return result;
+ }
+
+ @Override
+ public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
+ MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION
+ );
+ includeFile(result, documentId, null);
+ return result;
+ }
+
+ @Override
+ public Cursor queryChildDocuments(
+ String parentDocumentId,
+ String[] projection,
+ String sortOrder
+ ) throws FileNotFoundException {
+ MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION
+ );
+ File parent = getFileForDocId(parentDocumentId);
+ File[] files = parent.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ includeFile(result, null, file);
+ }
+ } else {
+ Log.e("DocumentsProvider", "Unable to list files in " + parentDocumentId);
+ }
+ return result;
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(
+ String documentId,
+ String mode,
+ CancellationSignal signal
+ ) throws FileNotFoundException {
+ File file = getFileForDocId(documentId);
+ int accessMode = ParcelFileDescriptor.parseMode(mode);
+ return ParcelFileDescriptor.open(file, accessMode);
+ }
+
+ @Override
+ public AssetFileDescriptor openDocumentThumbnail(
+ String documentId,
+ Point sizeHint,
+ CancellationSignal signal
+ ) throws FileNotFoundException {
+ File file = getFileForDocId(documentId);
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ return new AssetFileDescriptor(pfd, 0, file.length());
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public String createDocument(
+ String parentDocumentId,
+ String mimeType,
+ String displayName
+ ) throws FileNotFoundException {
+ File parent = getFileForDocId(parentDocumentId);
+ File newFile = new File(parent, displayName);
+ int noConflictId = 2;
+ while (newFile.exists()) {
+ newFile = new File(parent, displayName + " (" + noConflictId++ + ")");
+ }
+ try {
+ boolean succeeded;
+ if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
+ succeeded = newFile.mkdir();
+ } else {
+ succeeded = newFile.createNewFile();
+ }
+ if (!succeeded) {
+ throw new FileNotFoundException("Failed to create document with id " + newFile.getAbsolutePath());
+ }
+ } catch (IOException e) {
+ throw new FileNotFoundException("Failed to create document with id " + newFile.getAbsolutePath());
+ }
+ return getDocIdForFile(newFile);
+ }
+
+ @Override
+ public void deleteDocument(String documentId) throws FileNotFoundException {
+ File file = getFileForDocId(documentId);
+ if (!file.delete()) {
+ throw new FileNotFoundException("Failed to delete document with id " + documentId);
+ }
+ }
+
+ @Override
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ File file = getFileForDocId(documentId);
+ return getMimeType(file);
+ }
+
+ @Override
+ public Cursor querySearchDocuments(
+ String rootId,
+ String query,
+ String[] projection
+ ) throws FileNotFoundException {
+ MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION
+ );
+ File parent = getFileForDocId(rootId);
+
+ // This example implementation searches file names for the query and doesn't rank search
+ // results, so we can stop as soon as we find a sufficient number of matches. Other
+ // implementations might rank results and use other data about files, rather than the file
+ // name, to produce a match.
+ LinkedList pending = new LinkedList<>();
+ pending.add(parent);
+
+ final int MAX_SEARCH_RESULTS = 50;
+ while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
+ File file = pending.removeFirst();
+ // Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g. search
+ // through the whole SD card).
+ boolean isInsideHome;
+ try {
+ isInsideHome = file.getCanonicalPath().startsWith(BASE_DIR.getCanonicalPath());
+ } catch (IOException e) {
+ isInsideHome = true;
+ }
+ if (isInsideHome) {
+ if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ Collections.addAll(pending, files);
+ }
+ } else {
+ if (file.getName().toLowerCase(Locale.getDefault()).contains(query)) {
+ includeFile(result, null, file);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ return documentId.startsWith(parentDocumentId);
+ }
+
+ /**
+ * Add a representation of a file to a cursor.
+ *
+ * @param result the cursor to modify
+ * @param docId the document ID representing the desired file (may be null if given file)
+ * @param file the File object representing the desired file (may be null if given docID)
+ */
+ private void includeFile(MatrixCursor result, String docId, File file) throws FileNotFoundException {
+ if (docId == null) {
+ docId = getDocIdForFile(file);
+ } else {
+ file = getFileForDocId(docId);
+ }
+
+ int flags = 0;
+ if (file.isDirectory()) {
+ if (file.canWrite()) {
+ flags = flags | DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE;
+ }
+ } else if (file.canWrite()) {
+ flags = flags | DocumentsContract.Document.FLAG_SUPPORTS_WRITE;
+ }
+ File parentFile = file.getParentFile();
+ if (parentFile != null && parentFile.canWrite()) {
+ flags = flags | DocumentsContract.Document.FLAG_SUPPORTS_DELETE;
+ }
+
+ String displayName = file.getName();
+ String mimeType = getMimeType(file);
+ if (mimeType.startsWith("image/")) {
+ flags = flags | DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL;
+ }
+
+ MatrixCursor.RowBuilder row = result.newRow();
+ row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, docId);
+ row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, displayName);
+ row.add(DocumentsContract.Document.COLUMN_SIZE, file.length());
+ row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, mimeType);
+ row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, file.lastModified());
+ row.add(DocumentsContract.Document.COLUMN_FLAGS, flags);
+ row.add(DocumentsContract.Document.COLUMN_ICON, R.mipmap.ic_launcher);
+ }
+
+ public static boolean isDocumentProviderEnabled(Context context) {
+ ComponentName componentName = new ComponentName(context, AlpineDocumentProvider.class);
+ int state = context.getPackageManager().getComponentEnabledSetting(componentName);
+ return state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED ||
+ state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+ }
+
+ public static void setDocumentProviderEnabled(Context context, boolean enabled) {
+ if (isDocumentProviderEnabled(context) == enabled) {
+ return;
+ }
+ ComponentName componentName = new ComponentName(context, AlpineDocumentProvider.class);
+ int newState = enabled ?
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+
+ context.getPackageManager().setComponentEnabledSetting(
+ componentName,
+ newState,
+ PackageManager.DONT_KILL_APP
+ );
+ }
+
+ /**
+ * Get the document id given a file. This document id must be consistent across time as other
+ * applications may save the ID and use it to reference documents later.
+ *
+ * The reverse of {@link #getFileForDocId}.
+ */
+ private static String getDocIdForFile(File file) {
+ return file.getAbsolutePath();
+ }
+
+ /**
+ * Get the file given a document id (the reverse of {@link #getDocIdForFile}).
+ */
+ private static File getFileForDocId(String docId) throws FileNotFoundException {
+ File f = new File(docId);
+ if (!f.exists()) {
+ throw new FileNotFoundException(f.getAbsolutePath() + " not found");
+ }
+ return f;
+ }
+
+ private static String getMimeType(File file) {
+ if (file.isDirectory()) {
+ return DocumentsContract.Document.MIME_TYPE_DIR;
+ } else {
+ String name = file.getName();
+ int lastDot = name.lastIndexOf('.');
+ if (lastDot >= 0) {
+ String extension = name.substring(lastDot + 1).toLowerCase(Locale.getDefault());
+ String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mime != null) {
+ return mime;
+ }
+ }
+ return "application/octet-stream";
+ }
+ }
+}
\ No newline at end of file
diff --git a/www/index.html b/www/index.html
index e4c0e5ec3..ae9aa7f4d 100644
--- a/www/index.html
+++ b/www/index.html
@@ -165,17 +165,17 @@
Acode
-
-
-
-
-
+
+
+
+
+