9299176b2d
Addresses #91: the #90 posture (`deny(unsafe_code)` + three scattered `#![allow(unsafe_code)]` across solitaire_data and solitaire_engine) punched unsafe holes into otherwise-pure logic crates. Replace it by *reducing* the unsafe rather than relocating it, then forbidding it everywhere it can be forbidden. Changes: - solitaire_data: new safe `android_jni` bridge owning the cached `JavaVM` and activity `GlobalRef`; exposes `with_env` / `with_activity_env` so keystore/clipboard/safe-area never touch a raw handle. - keystore: drop the `JavaVM::from_raw` init path (now in the app) and replace the three `unsafe { JByteArray::from_raw(x.into_raw()) }` casts with the safe `JByteArray::from(JObject)` conversion jni 0.21 provides. - engine clipboard + safe_area: route through the bridge; remove their `#![allow(unsafe_code)]` and all `from_raw` calls. - solitaire_app: becomes the single owner of FFI unsafe. `android_main` reconstructs the raw `JavaVM` / activity once (it must, as the cdylib that exports `#[unsafe(no_mangle)]`) and registers the safe wrappers. It opts to its own `deny`-level lints with two scoped `#[allow(unsafe_code)]`. - workspace: `unsafe_code` is now `forbid`. Every crate except the app entry point is fully unsafe-free. Net: 7 unsafe sites across three crates collapse to 3 at the OS boundary in one crate. Verified with host `clippy --workspace -- -D warnings` and an `aarch64-linux-android` clippy build of solitaire_app (transitively engine + data); also fixed two latent android-only `collapsible_if` warnings surfaced in the keystore by the cross-target check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
47 lines
1.6 KiB
Rust
47 lines
1.6 KiB
Rust
/// Android clipboard bridge via JNI.
|
|
///
|
|
/// Writes text to the system clipboard by calling into `ClipboardManager`
|
|
/// through the safe [`solitaire_data::android_jni`] bridge. Only compiled and
|
|
/// linked on `target_os = "android"`.
|
|
#[cfg(target_os = "android")]
|
|
pub fn set_text(text: &str) -> Result<(), String> {
|
|
use jni::objects::JValueOwned;
|
|
use solitaire_data::android_jni;
|
|
|
|
android_jni::with_activity_env(|env, activity| {
|
|
// ClipboardManager cm = activity.getSystemService("clipboard")
|
|
let svc_name = JValueOwned::from(env.new_string("clipboard")?);
|
|
let cm = env
|
|
.call_method(
|
|
activity,
|
|
"getSystemService",
|
|
"(Ljava/lang/String;)Ljava/lang/Object;",
|
|
&[svc_name.borrow()],
|
|
)?
|
|
.l()?;
|
|
|
|
// ClipData clip = ClipData.newPlainText("link", text)
|
|
let label = JValueOwned::from(env.new_string("link")?);
|
|
let java_text = JValueOwned::from(env.new_string(text)?);
|
|
let clip_class = env.find_class("android/content/ClipData")?;
|
|
let clip = env
|
|
.call_static_method(
|
|
&clip_class,
|
|
"newPlainText",
|
|
"(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;",
|
|
&[label.borrow(), java_text.borrow()],
|
|
)?
|
|
.l()?;
|
|
|
|
// cm.setPrimaryClip(clip)
|
|
let clip_val = JValueOwned::Object(clip);
|
|
env.call_method(
|
|
&cm,
|
|
"setPrimaryClip",
|
|
"(Landroid/content/ClipData;)V",
|
|
&[clip_val.borrow()],
|
|
)?
|
|
.v()
|
|
})
|
|
}
|