Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- <uses-permission android:name="com.termux.permission.RUN_COMMAND" /> -->
</config-file>

<hook type="before_prepare" src="hooks/modify-java-files.js" />
Expand Down
1 change: 1 addition & 0 deletions src/lang/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
"animation": "Animation",
"backup": "Backup",
"restore": "Restore",
"allFileAccess": "All file access",
"backup successful": "Backup successful",
"invalid backup file": "Invalid backup file",
"add path": "Add path",
Expand Down
288 changes: 179 additions & 109 deletions src/plugins/system/android/com/foxdebug/system/System.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@
import java.io.OutputStream;
import java.io.IOException;

import android.os.Build;
import android.os.Environment;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.Settings;

import androidx.core.content.ContextCompat;



public class System extends CordovaPlugin {

Expand Down Expand Up @@ -240,6 +253,65 @@ public void run() {

callbackContext.success(arch);
return true;

case "requestStorageManager":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:" + context.getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
callbackContext.success("true");
} catch (Exception e) {
// Fallback to general settings if specific one fails
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
callbackContext.success("true");
}
} else {
callbackContext.success("false"); // Not needed on Android < 11
}
return true;


case "hasGrantedStorageManager":
boolean granted;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
granted = Environment.isExternalStorageManager();
} else {
// Fallback for Android 10 and below
granted = ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED;
}
callbackContext.success(String.valueOf(granted));
return true;

case "isManageExternalStorageDeclared":
PackageManager pm = context.getPackageManager();
try {
PackageInfo info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
String[] permissions = info.requestedPermissions;
String isDeclared = "false";

if (permissions != null) {
for (String perm: permissions) {
if (perm.equals("android.permission.MANAGE_EXTERNAL_STORAGE")) {
isDeclared = "true";
break;
}
}
}
callbackContext.success(isDeclared);

} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
callbackContext.error(e.toString());
}

return true;
case "mkdirs":
File file = new File(args.getString(0));
if (file.mkdirs()) {
Expand All @@ -260,106 +332,104 @@ public void run() {
public void run() {
switch (action) {
case "copyToUri":
try {
//srcUri is a file
Uri srcUri = Uri.parse(args.getString(0));

//destUri is a directory
Uri destUri = Uri.parse(args.getString(1));

//create a file named this into the dest Directory and copy the srcUri into it
String fileName = args.getString(2);

InputStream in = null;
OutputStream out = null;
try {
// Open input stream from the source URI
if ("file".equalsIgnoreCase(srcUri.getScheme())) {
File file = new File(srcUri.getPath());
in = new FileInputStream(file);
} else {
in = context.getContentResolver().openInputStream(srcUri);
}

// Create the destination file using DocumentFile for better URI handling
DocumentFile destFile = null;

if ("file".equalsIgnoreCase(destUri.getScheme())) {
// Handle file:// scheme using DocumentFile
File destDir = new File(destUri.getPath());
if (!destDir.exists()) {
destDir.mkdirs(); // Create directory if it doesn't exist
}
DocumentFile destDocDir = DocumentFile.fromFile(destDir);

// Check if file already exists and delete it
DocumentFile existingFile = destDocDir.findFile(fileName);
if (existingFile != null && existingFile.exists()) {
existingFile.delete();
}

// Create new file
String mimeType = getMimeTypeFromExtension(fileName);
destFile = destDocDir.createFile(mimeType, fileName);
} else {
// Handle content:// scheme using DocumentFile
DocumentFile destDocDir = DocumentFile.fromTreeUri(context, destUri);

if (destDocDir == null || !destDocDir.exists() || !destDocDir.isDirectory()) {
callbackContext.error("Destination directory does not exist or is not accessible");
return;
}

// Check if file already exists and delete it
DocumentFile existingFile = destDocDir.findFile(fileName);
if (existingFile != null && existingFile.exists()) {
existingFile.delete();
}

// Create new file
String mimeType = getMimeTypeFromExtension(fileName);
destFile = destDocDir.createFile(mimeType, fileName);
}

if (destFile == null || !destFile.exists()) {
callbackContext.error("Failed to create destination file");
return;
}

// Open output stream to the created file
out = context.getContentResolver().openOutputStream(destFile.getUri());

if (in == null || out == null) {
callbackContext.error("uri streams are null");
return;
}

// Copy stream
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}

out.flush();
callbackContext.success();
} catch (IOException e) {
e.printStackTrace();
callbackContext.error(e.toString());
} finally {
try {
if (in != null) in.close();
if (out != null) out.close();
} catch (IOException e) {
e.printStackTrace();
callbackContext.error(e.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
callbackContext.error(e.toString());
}
break;
try {
//srcUri is a file
Uri srcUri = Uri.parse(args.getString(0));

//destUri is a directory
Uri destUri = Uri.parse(args.getString(1));

//create a file named this into the dest Directory and copy the srcUri into it
String fileName = args.getString(2);

InputStream in = null;
OutputStream out = null;
try {
// Open input stream from the source URI
if ("file".equalsIgnoreCase(srcUri.getScheme())) {
File file = new File(srcUri.getPath()); in = new FileInputStream(file);
} else { in = context.getContentResolver().openInputStream(srcUri);
}

// Create the destination file using DocumentFile for better URI handling
DocumentFile destFile = null;

if ("file".equalsIgnoreCase(destUri.getScheme())) {
// Handle file:// scheme using DocumentFile
File destDir = new File(destUri.getPath());
if (!destDir.exists()) {
destDir.mkdirs(); // Create directory if it doesn't exist
}
DocumentFile destDocDir = DocumentFile.fromFile(destDir);

// Check if file already exists and delete it
DocumentFile existingFile = destDocDir.findFile(fileName);
if (existingFile != null && existingFile.exists()) {
existingFile.delete();
}

// Create new file
String mimeType = getMimeTypeFromExtension(fileName);
destFile = destDocDir.createFile(mimeType, fileName);
} else {
// Handle content:// scheme using DocumentFile
DocumentFile destDocDir = DocumentFile.fromTreeUri(context, destUri);

if (destDocDir == null || !destDocDir.exists() || !destDocDir.isDirectory()) {
callbackContext.error("Destination directory does not exist or is not accessible");
return;
}

// Check if file already exists and delete it
DocumentFile existingFile = destDocDir.findFile(fileName);
if (existingFile != null && existingFile.exists()) {
existingFile.delete();
}

// Create new file
String mimeType = getMimeTypeFromExtension(fileName);
destFile = destDocDir.createFile(mimeType, fileName);
}

if (destFile == null || !destFile.exists()) {
callbackContext.error("Failed to create destination file");
return;
}

// Open output stream to the created file
out = context.getContentResolver().openOutputStream(destFile.getUri());

if ( in == null || out == null) {
callbackContext.error("uri streams are null");
return;
}

// Copy stream
byte[] buffer = new byte[8192];
int len;
while ((len = in .read(buffer)) > 0) {
out.write(buffer, 0, len);
}

out.flush();
callbackContext.success();
} catch (IOException e) {
e.printStackTrace();
callbackContext.error(e.toString());
} finally {
try {
if ( in != null) in .close();
if (out != null) out.close();
} catch (IOException e) {
e.printStackTrace();
callbackContext.error(e.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
callbackContext.error(e.toString());
}
break;
case "get-webkit-info":
getWebkitInfo(callbackContext);
break;
Expand Down Expand Up @@ -445,16 +515,16 @@ public void run() {
}

// Helper method to determine MIME type using Android's built-in MimeTypeMap
private String getMimeTypeFromExtension(String fileName) {
String extension = "";
int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
extension = fileName.substring(lastDotIndex + 1).toLowerCase();
private String getMimeTypeFromExtension(String fileName) {
String extension = "";
int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
extension = fileName.substring(lastDotIndex + 1).toLowerCase();
}

String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
return mimeType != null ? mimeType : "application/octet-stream";
}

String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
return mimeType != null ? mimeType : "application/octet-stream";
}

private void getConfiguration(CallbackContext callback) {
try {
Expand Down
9 changes: 9 additions & 0 deletions src/plugins/system/www/plugin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
module.exports = {
isManageExternalStorageDeclared: function (success, error) {
cordova.exec(success, error, 'System', 'isManageExternalStorageDeclared', []);
},
hasGrantedStorageManager: function (success, error) {
cordova.exec(success, error, 'System', 'hasGrantedStorageManager', []);
},
requestStorageManager: function (success, error) {
cordova.exec(success, error, 'System', 'requestStorageManager', []);
},
copyToUri: function (srcUri,destUri,fileName, success, error) {
cordova.exec(success, error, 'System', 'copyToUri', [srcUri,destUri,fileName]);
},
Expand Down
5 changes: 5 additions & 0 deletions src/plugins/terminal/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@

<source-file src="scripts/init-sandbox.sh" target-dir="assets"/>
<source-file src="scripts/init-alpine.sh" target-dir="assets"/>

<config-file target="AndroidManifest.xml" parent="/manifest">

</config-file>


</platform>
</plugin>
19 changes: 19 additions & 0 deletions src/settings/terminalSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export default function terminalSettings() {
const terminalValues = values.terminalSettings;

const items = [
{
key: "all_file_access",
text: strings.allFileAccess.capitalize(),
info: "Enable access of /sdcard and /storage in terminal",
},
{
key: "fontSize",
text: strings["font size"],
Expand Down Expand Up @@ -179,6 +184,20 @@ export default function terminalSettings() {
*/
function callback(key, value) {
switch (key) {
case "all_file_access":
if (ANDROID_SDK_INT >= 30) {
system.isManageExternalStorageDeclared((boolStr) => {
if (boolStr === "true") {
system.requestStorageManager(console.log, console.error);
} else {
alert("This feature is not available.");
}
}, alert);
} else {
alert("This feature is not available.");
}

return;
case "backup":
terminalBackup();
return;
Expand Down
Loading