use android_view::jni::{ JNIEnv, JavaVM, objects::{GlobalRef, JObject, JValue}, }; /// `android.os.BatteryManager.BATTERY_PROPERTY_CURRENT_NOW` -- not exposed /// as a constant anywhere reachable without the Android SDK jar, so named /// here with its source rather than left as a bare `2`. const BATTERY_PROPERTY_CURRENT_NOW: i32 = 2; pub struct PlatformHandle { vm: JavaVM, view: GlobalRef, } impl PlatformHandle { pub fn new(vm: JavaVM, view: GlobalRef) -> Self { Self { vm, view } } fn context<'e>(&self, env: &mut JNIEnv<'e>) -> Option> { env.call_method( self.view.as_obj(), "getContext", "()Landroid/content/Context;", &[], ) .ok()? .l() .ok() } fn system_service<'e>( &self, env: &mut JNIEnv<'e>, context: &JObject<'e>, name: &str, ) -> Option> { let jname = env.new_string(name).ok()?; env.call_method( context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", &[JValue::Object(jname.as_ref())], ) .ok()? .l() .ok() } pub fn battery_current_ua(&self) -> Option { let mut guard = self.vm.attach_current_thread().ok()?; let env: &mut JNIEnv = &mut guard; let context = self.context(env)?; let battery_manager = self.system_service(env, &context, "batterymanager")?; let value = env .call_method( &battery_manager, "getIntProperty", "(I)I", &[JValue::Int(BATTERY_PROPERTY_CURRENT_NOW)], ) .ok()? .i() .ok()?; if value == 0 || value == i32::MIN { None } else { Some(value) } } /// Puts `text` on the system clipboard through `ClipboardManager` -- /// `true` only if the whole JNI chain (service lookup, `ClipData`, /// `setPrimaryClip`) succeeded. pub fn copy_to_clipboard(&self, label: &str, text: &str) -> bool { self.try_copy_to_clipboard(label, text).is_some() } fn try_copy_to_clipboard(&self, label: &str, text: &str) -> Option<()> { let mut guard = self.vm.attach_current_thread().ok()?; let env: &mut JNIEnv = &mut guard; let context = self.context(env)?; let clipboard = self.system_service(env, &context, "clipboard")?; let jlabel = env.new_string(label).ok()?; let jtext = env.new_string(text).ok()?; let clip = env .call_static_method( "android/content/ClipData", "newPlainText", "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;", &[ JValue::Object(jlabel.as_ref()), JValue::Object(jtext.as_ref()), ], ) .ok()? .l() .ok()?; env.call_method( &clipboard, "setPrimaryClip", "(Landroid/content/ClipData;)V", &[JValue::Object(&clip)], ) .ok()?; Some(()) } pub fn refresh_rate_hz(&self) -> Option { let mut guard = self.vm.attach_current_thread().ok()?; let env: &mut JNIEnv = &mut guard; let display = env .call_method( self.view.as_obj(), "getDisplay", "()Landroid/view/Display;", &[], ) .ok()? .l() .ok()?; if display.is_null() { return None; } let rate = env .call_method(&display, "getRefreshRate", "()F", &[]) .ok()? .f() .ok()?; if rate > 0.0 { Some(rate) } else { None } } pub fn show_ime(&self) -> bool { self.try_toggle_ime(true).unwrap_or(false) } pub fn hide_ime(&self) -> bool { self.try_toggle_ime(false).unwrap_or(false) } fn try_toggle_ime(&self, show: bool) -> Option { let mut guard = self.vm.attach_current_thread().ok()?; let env: &mut JNIEnv = &mut guard; let context = self.context(env)?; let imm = self.system_service(env, &context, "input_method")?; if show { env.call_method( &imm, "showSoftInput", "(Landroid/view/View;I)Z", &[JValue::Object(self.view.as_obj()), JValue::Int(0)], ) .ok()? .z() .ok() } else { let token = env .call_method( self.view.as_obj(), "getWindowToken", "()Landroid/os/IBinder;", &[], ) .ok()? .l() .ok()?; env.call_method( &imm, "hideSoftInputFromWindow", "(Landroid/os/IBinder;I)Z", &[JValue::Object(&token), JValue::Int(0)], ) .ok()? .z() .ok() } } }