diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1cbb394 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,354 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "android-activity" +version = "0.4.0" +dependencies = [ + "android-properties", + "bitflags", + "cc", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nom8" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" +dependencies = [ + "memchr", +] + +[[package]] +name = "num_enum" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "proc-macro-crate" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" +dependencies = [ + "cty", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml_datetime" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" + +[[package]] +name = "toml_edit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +dependencies = [ + "indexmap", + "nom8", + "toml_datetime", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/android-activity/Cargo.toml b/android-activity/Cargo.toml index 61f7af0..566bb2e 100644 --- a/android-activity/Cargo.toml +++ b/android-activity/Cargo.toml @@ -23,6 +23,7 @@ native-activity = [] [dependencies] log = "0.4" +jni = "0.20.0" jni-sys = "0.3" ndk = "0.7" ndk-sys = "0.4" diff --git a/android-activity/src/intent/action.rs b/android-activity/src/intent/action.rs new file mode 100644 index 0000000..5481b06 --- /dev/null +++ b/android-activity/src/intent/action.rs @@ -0,0 +1,16 @@ +/// Action to invoke with an intent +pub enum Action { + Send, + Edit, + Chooser, +} + +impl AsRef for Action { + fn as_ref(&self) -> &str { + match self { + Self::Send => "ACTION_SEND", + Self::Edit => "ACTION_EDIT", + Self::Chooser => "ACTION_CHOOSER", + } + } +} diff --git a/android-activity/src/intent/extra.rs b/android-activity/src/intent/extra.rs new file mode 100644 index 0000000..6ec157e --- /dev/null +++ b/android-activity/src/intent/extra.rs @@ -0,0 +1,12 @@ +/// Extra data to include with an intent +pub enum Extra { + Text, +} + +impl AsRef for Extra { + fn as_ref(&self) -> &str { + match self { + Self::Text => "android.intent.extra.TEXT", + } + } +} diff --git a/android-activity/src/intent/mod.rs b/android-activity/src/intent/mod.rs new file mode 100644 index 0000000..005bcc0 --- /dev/null +++ b/android-activity/src/intent/mod.rs @@ -0,0 +1,183 @@ +use jni::{ + errors::Error, + objects::{JObject, JString}, + JNIEnv, +}; + +mod action; +pub use action::Action; + +mod extra; +pub use extra::Extra; + +struct Inner<'env> { + env: JNIEnv<'env>, + object: JObject<'env>, +} + +/// A messaging object you can use to request an action from another android app component. +#[must_use] +pub struct Intent<'env> { + inner: Result, Error>, +} + +impl<'env> Intent<'env> { + pub fn from_object(env: JNIEnv<'env>, object: JObject<'env>) -> Self { + Self { + inner: Ok(Inner { env, object }), + } + } + + fn from_fn(f: impl FnOnce() -> Result, Error>) -> Self { + let inner = f(); + Self { inner } + } + + pub fn new(env: JNIEnv<'env>, action: impl AsRef) -> Self { + Self::from_fn(|| { + let intent_class = env.find_class("android/content/Intent")?; + let action_view = + env.get_static_field(intent_class, action.as_ref(), "Ljava/lang/String;")?; + + let intent = + env.new_object(intent_class, "(Ljava/lang/String;)V", &[action_view.into()])?; + + Ok(Inner { + env, + object: intent, + }) + }) + } + + pub fn new_with_uri(env: JNIEnv<'env>, action: impl AsRef, uri: impl AsRef) -> Self { + Self::from_fn(|| { + let url_string = env.new_string(uri)?; + let uri_class = env.find_class("android/net/Uri")?; + let uri = env.call_static_method( + uri_class, + "parse", + "(Ljava/lang/String;)Landroid/net/Uri;", + &[JString::from(url_string).into()], + )?; + + let intent_class = env.find_class("android/content/Intent")?; + let action_view = + env.get_static_field(intent_class, action.as_ref(), "Ljava/lang/String;")?; + + let intent = env.new_object( + intent_class, + "(Ljava/lang/String;Landroid/net/Uri;)V", + &[action_view.into(), uri.into()], + )?; + + Ok(Inner { + env, + object: intent, + }) + }) + } + + /// Add extended data to the intent. + /// ```no_run + /// use android_intent::{Action, Extra, Intent}; + /// + /// # android_intent::with_current_env(|env| { + /// let intent = Intent::new(env, Action::Send); + /// intent.push_extra(Extra::Text, "Hello World!") + /// # }) + /// ``` + pub fn with_extra(self, key: impl AsRef, value: impl AsRef) -> Self { + self.and_then(|inner| { + let key = inner.env.new_string(key)?; + let value = inner.env.new_string(value)?; + + inner.env.call_method( + inner.object, + "putExtra", + "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;", + &[key.into(), value.into()], + )?; + + Ok(inner) + }) + } + + /// Builds a new [`Action::Chooser`](Action) Intent that wraps the given target intent. + /// ```no_run + /// use android_intent::{Action, Intent}; + /// + /// # android_intent::with_current_env(|env| { + /// let intent = Intent::new(env, Action::Send).into_chhoser(); + /// # }) + /// ``` + pub fn into_chooser(self) -> Self { + self.into_chooser_with_title(None::<&str>) + } + + pub fn into_chooser_with_title(self, title: Option>) -> Self { + self.and_then(|mut inner| { + let title_value = if let Some(title) = title { + let s = inner.env.new_string(title)?; + s.into() + } else { + JObject::null().into() + }; + + let intent_class = inner.env.find_class("android/content/Intent")?; + let intent = inner.env.call_static_method( + intent_class, + "createChooser", + "(Landroid/content/Intent;Ljava/lang/CharSequence;)Landroid/content/Intent;", + &[inner.object.into(), title_value], + )?; + + inner.object = intent.try_into()?; + Ok(inner) + }) + } + + /// Set an explicit MIME data type. + /// ```no_run + /// use android_intent::{Action, Intent}; + /// + /// # android_intent::with_current_env(|env| { + /// let intent = Intent::new(env, Action::Send); + /// intent.set_type("text/plain"); + /// # }) + /// ``` + pub fn with_type(self, type_name: impl AsRef) -> Self { + self.and_then(|inner| { + let jstring = inner.env.new_string(type_name)?; + + inner.env.call_method( + inner.object, + "setType", + "(Ljava/lang/String;)Landroid/content/Intent;", + &[jstring.into()], + )?; + + Ok(inner) + }) + } + + pub fn start_activity(self) -> Result<(), Error> { + let cx = ndk_context::android_context(); + let activity = unsafe { JObject::from_raw(cx.context() as jni::sys::jobject) }; + + self.inner.and_then(|inner| { + inner.env.call_method( + activity, + "startActivity", + "(Landroid/content/Intent;)V", + &[inner.object.into()], + )?; + + Ok(()) + }) + } + + fn and_then(mut self, f: impl FnOnce(Inner) -> Result) -> Self { + self.inner = self.inner.and_then(f); + self + } +} diff --git a/android-activity/src/lib.rs b/android-activity/src/lib.rs index 6ae6b0c..abe5058 100644 --- a/android-activity/src/lib.rs +++ b/android-activity/src/lib.rs @@ -111,6 +111,8 @@ pub mod input; mod config; pub use config::ConfigurationRef; +pub mod intent; + mod util; /// A rectangle with integer edge coordinates. Used to represent window insets, for example.