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
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,14 @@ object OpenLessAndroidPreferences {

private fun preferenceFiles(context: Context): List<File> {
val files = mutableListOf<File>()
// Prefer app-private filesDir (Rust data_dir); never rely on /data/local/tmp.
files += File(File(context.filesDir, APP_DIR), PREFERENCES_FILE)
val envDir = System.getenv("TAURI_ANDROID_APP_DATA_DIR")
if (!envDir.isNullOrBlank()) {
files += File(File(envDir), APP_DIR).resolve(PREFERENCES_FILE)
}
// Legacy probe only — some older builds may have written under cacheDir.
files += File(File(context.cacheDir, APP_DIR), PREFERENCES_FILE)
files += File(File(context.filesDir, APP_DIR), PREFERENCES_FILE)
return files
}
}
37 changes: 37 additions & 0 deletions openless-all/app/android/kotlin/OpenLessContentWriter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.openless.app

import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.annotation.Keep

/**
* Writes bytes to a SAF content:// URI via ContentResolver.
*
* Prefer this over tauri-plugin-fs for exports: fs detaches the FD early and
* some providers finalize a 0-byte file before Rust finishes writing.
*/
@Keep
object OpenLessContentWriter {
private const val TAG = "OpenLessContentWriter"

@Keep
@JvmStatic
fun writeBytes(context: Context, uriString: String, bytes: ByteArray): Boolean {
return try {
val uri = Uri.parse(uriString)
context.contentResolver.openOutputStream(uri)?.use { output ->
output.write(bytes)
output.flush()
} ?: run {
Log.w(TAG, "openOutputStream returned null for $uriString")
return false
}
Log.i(TAG, "wrote ${bytes.size} bytes to $uriString")
true
} catch (error: Throwable) {
Log.e(TAG, "failed to write $uriString", error)
false
}
}
}
34 changes: 4 additions & 30 deletions openless-all/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion openless-all/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@tailwindcss/vite": "^4.3.2",
"@tauri-apps/api": "^2.1.1",
"@tauri-apps/plugin-autostart": "^2.5.1",
"@tauri-apps/plugin-dialog": "^2.7.1",
"@tauri-apps/plugin-dialog": "^2.7.2",
"@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-updater": "^2.10.1",
"@types/dompurify": "^3.0.5",
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/scripts/copy-android-scaffolding.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const KOTLIN_FILES = [
'OpenLessAccessibilityCommandReceiver.kt',
'OverlayPermissionActivity.kt',
'OpenLessUpdateInstaller.kt',
'OpenLessContentWriter.kt',
];

const KOTLIN_TEST_FILES = ['OpenLessCredentialCipherTest.kt'];
Expand Down
4 changes: 2 additions & 2 deletions openless-all/app/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion openless-all/app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ cc = "1.1"
tauri = { version = "~2.11", features = ["macos-private-api"] }
# 锁 >=2.3.5 修复 GHSA-c9pr-q8gx-3mgp:tauri-plugin-shell `open` 端点作用域校验绕过(CRITICAL,issue #668)。
tauri-plugin-shell = "2.3.5"
tauri-plugin-dialog = "2"
tauri-plugin-dialog = "2.7.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# OpenRouter ASR 把音频以标准 base64(带 padding)放进 JSON body(issue #582)。
Expand Down
32 changes: 32 additions & 0 deletions openless-all/app/src-tauri/src/android/jni.rs
Original file line number Diff line number Diff line change
Expand Up @@ -925,4 +925,36 @@ pub mod android {
&[JValue::Object(context), JValue::Object(path_obj)],
)
}

/// Write `bytes` to a SAF `content://` URI via Kotlin ContentResolver.
pub fn write_content_uri(uri: &str, bytes: &[u8]) -> Result<(), String> {
with_android_env(|env, context| {
let class = load_context_class(env, context, "com.openless.app.OpenLessContentWriter")?;
let uri_obj = jobject_str(env, uri)?;
let bytes_array = env
.byte_array_from_slice(bytes)
.map_err(|error| format!("create byte array for content URI write: {error}"))?;
let bytes_obj = JObject::from(bytes_array);
let ok = env
.call_static_method(
class,
"writeBytes",
"(Landroid/content/Context;Ljava/lang/String;[B)Z",
&[
JValue::Object(context),
JValue::Object(&uri_obj),
JValue::Object(&bytes_obj),
],
)
.and_then(|value| value.z())
.map_err(|error| {
format!("call OpenLessContentWriter.writeBytes: {error}")
})?;
if ok {
Ok(())
} else {
Err(format!("写入 content URI 失败:{uri}"))
}
})
}
}
Loading
Loading