feat(android): add zoom slider
fix(android): gray screen bug
This commit is contained in:
parent
6f4cc62530
commit
eaf3f61ebd
24 changed files with 456 additions and 372 deletions
135
apps/tauri/src-tauri/src/accessibility_backend.rs
Normal file
135
apps/tauri/src-tauri/src/accessibility_backend.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#[cfg(not(target_os = "android"))]
|
||||
const DEFAULT_INITIAL_SCALE: i32 = 290;
|
||||
const MIN_INITIAL_SCALE: i32 = 210;
|
||||
const MAX_INITIAL_SCALE: i32 = 500;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn accessibility_get_initial_scale() -> Result<i32, String> {
|
||||
android_get_initial_scale()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn accessibility_set_initial_scale(initial_scale: i32) -> Result<(), String> {
|
||||
if !(MIN_INITIAL_SCALE..=MAX_INITIAL_SCALE).contains(&initial_scale) {
|
||||
return Err(format!(
|
||||
"Initial scale must be between {MIN_INITIAL_SCALE} and {MAX_INITIAL_SCALE}"
|
||||
));
|
||||
}
|
||||
|
||||
android_set_initial_scale(initial_scale)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn android_get_initial_scale() -> Result<i32, String> {
|
||||
Ok(DEFAULT_INITIAL_SCALE)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn android_set_initial_scale(_: i32) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
mod android {
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use jni::{
|
||||
jni_sig, jni_str,
|
||||
objects::{Global, JClass, JObject, JValue},
|
||||
Env, EnvUnowned, JavaVM,
|
||||
};
|
||||
|
||||
struct Host {
|
||||
vm: JavaVM,
|
||||
context: Global<JObject<'static>>,
|
||||
bridge: Global<JObject<'static>>,
|
||||
}
|
||||
|
||||
static HOST: OnceLock<Host> = OnceLock::new();
|
||||
|
||||
fn attach(env: &mut Env, context: JObject) -> Result<(), String> {
|
||||
if HOST.get().is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let class = env
|
||||
.find_class(jni_str!("net/tensamin/client/NativeAccessibilityBridge"))
|
||||
.map_err(|error| error.to_string())?;
|
||||
let bridge = env
|
||||
.get_static_field(
|
||||
class,
|
||||
jni_str!("INSTANCE"),
|
||||
jni_sig!("Lnet/tensamin/client/NativeAccessibilityBridge;"),
|
||||
)
|
||||
.and_then(|value| value.l())
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
HOST.set(Host {
|
||||
vm: env.get_java_vm().map_err(|error| error.to_string())?,
|
||||
context: env
|
||||
.new_global_ref(context)
|
||||
.map_err(|error| error.to_string())?,
|
||||
bridge: env
|
||||
.new_global_ref(bridge)
|
||||
.map_err(|error| error.to_string())?,
|
||||
})
|
||||
.map_err(|_| "Android accessibility host is already attached".to_string())
|
||||
}
|
||||
|
||||
fn with_env<T>(call: impl FnOnce(&mut Env, &Host) -> Result<T, String>) -> Result<T, String> {
|
||||
let host = HOST
|
||||
.get()
|
||||
.ok_or("Android accessibility host is not attached")?;
|
||||
host.vm
|
||||
.attach_current_thread(|env| Ok::<_, jni::errors::Error>(call(env, host)))
|
||||
.map_err(|error| error.to_string())?
|
||||
}
|
||||
|
||||
pub fn get_initial_scale() -> Result<i32, String> {
|
||||
with_env(|env, host| {
|
||||
env.call_method(
|
||||
host.bridge.as_obj(),
|
||||
jni_str!("getInitialScale"),
|
||||
jni_sig!("(Landroid/content/Context;)I"),
|
||||
&[JValue::Object(host.context.as_obj())],
|
||||
)
|
||||
.and_then(|value| value.i())
|
||||
.map_err(|error| error.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_initial_scale(initial_scale: i32) -> Result<(), String> {
|
||||
with_env(|env, host| {
|
||||
env.call_method(
|
||||
host.bridge.as_obj(),
|
||||
jni_str!("setInitialScale"),
|
||||
jni_sig!("(Landroid/content/Context;I)V"),
|
||||
&[
|
||||
JValue::Object(host.context.as_obj()),
|
||||
JValue::Int(initial_scale),
|
||||
],
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_net_tensamin_client_NativeAccessibilityBridge_nativeAttach<
|
||||
'caller,
|
||||
>(
|
||||
mut env: EnvUnowned<'caller>,
|
||||
_class: JClass,
|
||||
context: JObject<'caller>,
|
||||
) {
|
||||
let _ = env.with_env(|env| {
|
||||
let _ = attach(env, context);
|
||||
Ok::<_, jni::errors::Error>(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use android::{
|
||||
get_initial_scale as android_get_initial_scale, set_initial_scale as android_set_initial_scale,
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
mod accessibility_backend;
|
||||
mod mtp_backend;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
|
|
@ -19,6 +20,8 @@ pub fn run() {
|
|||
|
||||
let app = builder
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
accessibility_backend::accessibility_get_initial_scale,
|
||||
accessibility_backend::accessibility_set_initial_scale,
|
||||
mtp_backend::mtp_request,
|
||||
mtp_backend::mtp_status,
|
||||
mtp_backend::mtp_store_credentials,
|
||||
|
|
|
|||
|
|
@ -372,9 +372,13 @@ async fn connect(config: &MtpConfig) -> Result<(MTPConnection, Value), String> {
|
|||
.with_max_missed_pings(3);
|
||||
#[cfg(target_os = "android")]
|
||||
let client_config = client_config.with_pinned_pem(android_root_certificates().clone());
|
||||
let connection = MTPClient::auth_connect(client_config, &keyring, &host_key)
|
||||
.await
|
||||
.map_err(|error| format!("transport authentication failed: {error}"))?;
|
||||
let connection = tokio::time::timeout(
|
||||
Duration::from_secs(30),
|
||||
MTPClient::auth_connect(client_config, &keyring, &host_key),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| "transport authentication timed out".to_string())?
|
||||
.map_err(|error| format!("transport authentication failed: {error}"))?;
|
||||
manager().log(2, "Native MTP authentication completed", None);
|
||||
|
||||
let connected = CommunicationValue::new(CommunicationType::ClientConnected)
|
||||
|
|
@ -385,10 +389,13 @@ async fn connect(config: &MtpConfig) -> Result<(MTPConnection, Value), String> {
|
|||
.add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(0))
|
||||
.add_typed_default(DataType::CacheValid, DataValue::BoolFalse)
|
||||
.add_typed_default(DataType::CacheSchemaVersion, DataValue::UnsignedNumber(0));
|
||||
let state = connection
|
||||
.request(&connected, None)
|
||||
.await
|
||||
.map_err(|error| format!("initial state synchronization failed: {error}"))?;
|
||||
let state = tokio::time::timeout(
|
||||
Duration::from_secs(30),
|
||||
connection.request(&connected, None),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| "initial state synchronization timed out".to_string())?
|
||||
.map_err(|error| format!("initial state synchronization failed: {error}"))?;
|
||||
if !state.is_type(CommunicationType::ClientStateSync) {
|
||||
return Err(format!(
|
||||
"expected ClientStateSync, received {}",
|
||||
|
|
@ -406,9 +413,9 @@ async fn connect(config: &MtpConfig) -> Result<(MTPConnection, Value), String> {
|
|||
let ack = CommunicationValue::new(CommunicationType::ClientStateAck)
|
||||
.add_typed_default(DataType::SessionId, number_to_data(session_id))
|
||||
.add_typed_default(DataType::VersionNumber, number_to_data(version));
|
||||
let response = connection
|
||||
.request(&ack, None)
|
||||
let response = tokio::time::timeout(Duration::from_secs(30), connection.request(&ack, None))
|
||||
.await
|
||||
.map_err(|_| "state acknowledgement timed out".to_string())?
|
||||
.map_err(|error| format!("state acknowledgement failed: {error}"))?;
|
||||
if response
|
||||
.get_type_name()
|
||||
|
|
@ -433,7 +440,19 @@ async fn resolve_endpoint(config: &MtpConfig) -> Result<(String, String), String
|
|||
public_key: String,
|
||||
}
|
||||
let root = config.omega_url.trim_end_matches('/');
|
||||
let response = reqwest::get(format!("{root}/api/get/omikron/{}", config.user_id))
|
||||
let client = reqwest::Client::builder()
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(20));
|
||||
#[cfg(target_os = "android")]
|
||||
let client = client.tls_certs_only(
|
||||
reqwest::Certificate::from_pem_bundle(android_root_certificates())
|
||||
.map_err(|error| format!("invalid bundled root certificates: {error}"))?,
|
||||
);
|
||||
let response = client
|
||||
.build()
|
||||
.map_err(|error| error.to_string())?
|
||||
.get(format!("{root}/api/get/omikron/{}", config.user_id))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
if !response.status().is_success() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue