DCS/ruiyiweiUX/Assets/Plugins/Android/com/dcx/ruiyiweiux/UsbStorageAccess.java

334 lines
13 KiB
Java
Raw Permalink Normal View History

2026-06-09 13:59:11 +08:00
package com.dcx.ruiyiweiux;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.util.Log;
import android.webkit.MimeTypeMap;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class UsbStorageAccess {
private static final String TAG = "DCXUsbStorageAccess";
private static final String PREFS_NAME = "dcx_usb_storage_access";
private static final String PREF_TREE_URI = "tree_uri";
private static final int REQUEST_OPEN_TREE = 42001;
public static boolean hasPersistedUsbDirectory(Activity activity) {
String treeUri = getPersistedTreeUri(activity);
return treeUri != null && treeUri.length() > 0;
}
public static String getPersistedUsbDirectoryUri(Activity activity) {
String treeUri = getPersistedTreeUri(activity);
return treeUri == null ? "" : treeUri;
}
public static void requestUsbDirectoryPermission(Activity activity) {
Intent intent = new Intent(activity, ActivityBridge.class);
activity.startActivity(intent);
}
public static void clearPersistedUsbDirectory(Activity activity) {
String treeUriText = getPersistedTreeUri(activity);
if (treeUriText != null && treeUriText.length() > 0) {
try {
Uri uri = Uri.parse(treeUriText);
activity.getContentResolver().releasePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} catch (Exception ex) {
Log.w(TAG, "release persisted tree uri failed", ex);
}
}
activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.remove(PREF_TREE_URI)
.apply();
}
public static boolean writeTextFile(Activity activity, String fileName, String content) {
byte[] bytes = content == null ? new byte[0] : content.getBytes(StandardCharsets.UTF_8);
return writeBytesFile(activity, fileName, bytes);
}
public static boolean writeBytesFile(Activity activity, String fileName, byte[] bytes) {
try {
String treeUriText = getPersistedTreeUri(activity);
if (treeUriText == null || treeUriText.length() == 0) {
return false;
}
Uri treeUri = Uri.parse(treeUriText);
Uri exportDir = ensureDirectory(activity, treeUri, "DCX_Export");
if (exportDir == null) {
return false;
}
Uri fileUri = createOrReplaceFile(activity, exportDir, fileName);
if (fileUri == null) {
return false;
}
OutputStream stream = activity.getContentResolver().openOutputStream(fileUri, "wt");
if (stream == null) {
return false;
}
try {
stream.write(bytes == null ? new byte[0] : bytes);
stream.flush();
} finally {
stream.close();
}
return true;
} catch (Exception ex) {
Log.w(TAG, "write usb file failed: " + fileName, ex);
return false;
}
}
private static Uri ensureDirectory(Activity activity, Uri treeUri, String displayName) {
try {
String treeDocumentId = DocumentsContract.getTreeDocumentId(treeUri);
if (isSelectedTreeDirectory(treeDocumentId, displayName)) {
return DocumentsContract.buildDocumentUriUsingTree(treeUri, treeDocumentId);
}
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
treeUri,
treeDocumentId);
android.database.Cursor cursor = activity.getContentResolver().query(
childrenUri,
new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE
},
null,
null,
null);
if (cursor != null) {
try {
while (cursor.moveToNext()) {
String documentId = cursor.getString(0);
String name = cursor.getString(1);
String mimeType = cursor.getString(2);
if (displayName.equals(name) &&
DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId);
}
}
} finally {
cursor.close();
}
}
return DocumentsContract.createDocument(
activity.getContentResolver(),
DocumentsContract.buildDocumentUriUsingTree(
treeUri,
treeDocumentId),
DocumentsContract.Document.MIME_TYPE_DIR,
displayName);
} catch (Exception ex) {
Log.w(TAG, "ensure usb export directory failed", ex);
return null;
}
}
private static boolean isSelectedTreeDirectory(String treeDocumentId, String displayName) {
if (treeDocumentId == null || displayName == null) {
return false;
}
int colonIndex = treeDocumentId.indexOf(':');
String pathPart = colonIndex >= 0 ? treeDocumentId.substring(colonIndex + 1) : treeDocumentId;
while (pathPart.endsWith("/") && pathPart.length() > 0) {
pathPart = pathPart.substring(0, pathPart.length() - 1);
}
int slashIndex = Math.max(pathPart.lastIndexOf('/'), pathPart.lastIndexOf('\\'));
String selectedName = slashIndex >= 0 ? pathPart.substring(slashIndex + 1) : pathPart;
return displayName.equals(selectedName);
}
private static Uri createOrReplaceFile(Activity activity, Uri parentUri, String fileName) {
try {
String safeName = sanitizeFileName(fileName);
String mimeType = getMimeType(safeName);
Uri existingFile = findChild(activity, parentUri, safeName, null);
if (existingFile != null) {
DocumentsContract.deleteDocument(activity.getContentResolver(), existingFile);
}
return DocumentsContract.createDocument(
activity.getContentResolver(),
parentUri,
mimeType,
safeName);
} catch (Exception ex) {
Log.w(TAG, "create usb export file failed: " + fileName, ex);
return null;
}
}
private static Uri findChild(Activity activity, Uri parentUri, String displayName, String requiredMimeType) {
android.database.Cursor cursor = null;
try {
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
parentUri,
DocumentsContract.getDocumentId(parentUri));
cursor = activity.getContentResolver().query(
childrenUri,
new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE
},
null,
null,
null);
if (cursor == null) {
return null;
}
while (cursor.moveToNext()) {
String documentId = cursor.getString(0);
String name = cursor.getString(1);
String mimeType = cursor.getString(2);
if (displayName.equals(name) &&
(requiredMimeType == null || requiredMimeType.equals(mimeType))) {
return DocumentsContract.buildDocumentUriUsingTree(parentUri, documentId);
}
}
} catch (Exception ignored) {
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
private static String getPersistedTreeUri(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
return prefs.getString(PREF_TREE_URI, "");
}
private static void savePersistedTreeUri(Context context, Uri uri) {
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.putString(PREF_TREE_URI, uri.toString())
.apply();
}
private static boolean isLikelyUsbTree(Uri uri) {
try {
String documentId = DocumentsContract.getTreeDocumentId(uri);
if (documentId == null || documentId.length() == 0) {
return false;
}
String lowerDocumentId = documentId.toLowerCase();
return !lowerDocumentId.startsWith("primary:") &&
!lowerDocumentId.startsWith("home:");
} catch (Exception ex) {
Log.w(TAG, "check selected tree uri failed", ex);
return false;
}
}
private static String sanitizeFileName(String fileName) {
if (fileName == null || fileName.trim().length() == 0) {
return "BFI_Export.txt";
}
return fileName.replaceAll("[\\\\/:*?\"<>|]", "_");
}
private static String getMimeType(String fileName) {
String extension = MimeTypeMap.getFileExtensionFromUrl(fileName);
if (extension != null && extension.length() > 0) {
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
if (mimeType != null && mimeType.length() > 0) {
return mimeType;
}
}
if (fileName.endsWith(".csv")) {
return "text/csv";
}
if (fileName.endsWith(".pdf")) {
return "application/pdf";
}
return "text/plain";
}
public static class ActivityBridge extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
startActivityForResult(intent, REQUEST_OPEN_TREE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_OPEN_TREE && resultCode == RESULT_OK && data != null) {
Uri uri = data.getData();
if (uri != null) {
if (!isLikelyUsbTree(uri)) {
Log.w(TAG, "selected tree is internal storage, ignore: " + uri);
finish();
return;
}
int flags = data.getFlags() &
(Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION |
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
boolean persisted = false;
try {
getContentResolver().takePersistableUriPermission(
uri,
flags & (Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
persisted = true;
} catch (Exception ex) {
Log.w(TAG, "persist selected usb tree uri failed", ex);
}
if (persisted) {
savePersistedTreeUri(this, uri);
}
}
}
finish();
}
}
}