Skip to content

Commit

Permalink
Rename Intent to IntentBuilder and Inner to Intent
Browse files Browse the repository at this point in the history
To prepare for upcoming getters to use this crate to *read* incoming
`Intent`s from e.g. `Activity.getIntent()`, the "default" builder
pattern on `Intent` (storing a `Result` inside) is no longer applicable.
  • Loading branch information
MarijnS95 committed Jan 29, 2025
1 parent c86df55 commit b8dc3a5
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 127 deletions.
4 changes: 2 additions & 2 deletions example/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use android_activity::AndroidApp;
use android_intent::{with_current_env, Action, Extra, Intent};
use android_intent::{with_current_env, Action, Extra, IntentBuilder};

#[no_mangle]
fn android_main(_android_app: AndroidApp) {
with_current_env(|env| {
Intent::new(env, Action::Send)
IntentBuilder::new(env, Action::Send)
.with_type("text/plain")
.with_extra(Extra::Text, "Hello World!")
.into_chooser()
Expand Down
320 changes: 196 additions & 124 deletions src/intent.rs
Original file line number Diff line number Diff line change
@@ -1,68 +1,56 @@
use jni::{errors::Error, objects::JObject, JNIEnv};

struct Inner<'env> {
env: JNIEnv<'env>,
object: JObject<'env>,
}
use jni::{errors::Result, objects::JObject, JNIEnv};

/// A messaging object you can use to request an action from another android app component.
#[must_use]
pub struct Intent<'env> {
inner: Result<Inner<'env>, Error>,
env: JNIEnv<'env>,
object: JObject<'env>,
}

impl<'env> Intent<'env> {
pub fn from_object(env: JNIEnv<'env>, object: JObject<'env>) -> Self {
Self {
inner: Ok(Inner { env, object }),
}
Self { env, object }
}

fn from_fn(f: impl FnOnce() -> Result<Inner<'env>, Error>) -> Self {
let inner = f();
Self { inner }
}
pub fn new(env: JNIEnv<'env>, action: impl AsRef<str>) -> Result<Self> {
let intent_class = env.find_class("android/content/Intent")?;
let action_view =
env.get_static_field(intent_class, action.as_ref(), "Ljava/lang/String;")?;

pub fn new(env: JNIEnv<'env>, action: impl AsRef<str>) -> 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])?;

let intent = env.new_object(intent_class, "(Ljava/lang/String;)V", &[action_view])?;

Ok(Inner {
env,
object: intent,
})
Ok(Self {
env,
object: intent,
})
}

pub fn new_with_uri(env: JNIEnv<'env>, action: impl AsRef<str>, uri: impl AsRef<str>) -> 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;",
&[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, uri],
)?;

Ok(Inner {
env,
object: intent,
})
pub fn new_with_uri(
env: JNIEnv<'env>,
action: impl AsRef<str>,
uri: impl AsRef<str>,
) -> Result<Self> {
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;",
&[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, uri],
)?;

Ok(Self {
env,
object: intent,
})
}

Expand All @@ -72,27 +60,25 @@ impl<'env> Intent<'env> {
///
/// # android_intent::with_current_env(|env| {
/// let intent = Intent::new(env, Action::Send)
/// .set_class_name("com.excample", "IntentTarget");
/// .with_class_name("com.excample", "IntentTarget");
/// # })
/// ```
pub fn set_class_name(
pub fn with_class_name(
self,
package_name: impl AsRef<str>,
class_name: impl AsRef<str>,
) -> Self {
self.and_then(|inner| {
let package_name = inner.env.new_string(package_name)?;
let class_name = inner.env.new_string(class_name)?;

inner.env.call_method(
inner.object,
"setClassName",
"(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
&[package_name.into(), class_name.into()],
)?;

Ok(inner)
})
) -> Result<Self> {
let package_name = self.env.new_string(package_name)?;
let class_name = self.env.new_string(class_name)?;

self.env.call_method(
self.object,
"setClassName",
"(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
&[package_name.into(), class_name.into()],
)?;

Ok(self)
}

/// Add extended data to the intent.
Expand All @@ -104,20 +90,18 @@ impl<'env> Intent<'env> {
/// .with_extra(Extra::Text, "Hello World!");
/// # })
/// ```
pub fn with_extra(self, key: impl AsRef<str>, value: impl AsRef<str>) -> 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)
})
pub fn with_extra(self, key: impl AsRef<str>, value: impl AsRef<str>) -> Result<Self> {
let key = self.env.new_string(key)?;
let value = self.env.new_string(value)?;

self.env.call_method(
self.object,
"putExtra",
"(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
&[key.into(), value.into()],
)?;

Ok(self)
}

/// Builds a new [`super::Action::Chooser`] Intent that wraps the given target intent.
Expand All @@ -129,30 +113,30 @@ impl<'env> Intent<'env> {
/// .into_chooser();
/// # })
/// ```
pub fn into_chooser(self) -> Self {
// TODO: Rename to with_?
pub fn into_chooser(self) -> Result<Self> {
self.into_chooser_with_title(None::<&str>)
}

pub fn into_chooser_with_title(self, title: Option<impl AsRef<str>>) -> 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)
})
// TODO: Rename to with_?
pub fn into_chooser_with_title(mut self, title: Option<impl AsRef<str>>) -> Result<Self> {
let title_value = if let Some(title) = title {
let s = self.env.new_string(title)?;
s.into()
} else {
JObject::null().into()
};

let intent_class = self.env.find_class("android/content/Intent")?;
let intent = self.env.call_static_method(
intent_class,
"createChooser",
"(Landroid/content/Intent;Ljava/lang/CharSequence;)Landroid/content/Intent;",
&[self.object.into(), title_value],
)?;

self.object = intent.try_into()?;
Ok(self)
}

/// Set an explicit MIME data type.
Expand All @@ -164,38 +148,126 @@ impl<'env> Intent<'env> {
/// .with_type("text/plain");
/// # })
/// ```
pub fn with_type(self, type_name: impl AsRef<str>) -> Self {
self.and_then(|inner| {
let jstring = inner.env.new_string(type_name)?;
pub fn with_type(self, type_name: impl AsRef<str>) -> Result<Self> {
let jstring = self.env.new_string(type_name)?;

inner.env.call_method(
inner.object,
"setType",
"(Ljava/lang/String;)Landroid/content/Intent;",
&[jstring.into()],
)?;
self.env.call_method(
self.object,
"setType",
"(Ljava/lang/String;)Landroid/content/Intent;",
&[jstring.into()],
)?;

Ok(inner)
})
Ok(self)
}

pub fn start_activity(self) -> Result<(), Error> {
pub fn start_activity(self) -> Result<()> {
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()],
)?;
self.env.call_method(
activity,
"startActivity",
"(Landroid/content/Intent;)V",
&[self.object.into()],
)?;

Ok(())
})
Ok(())
}
}

/// Builder for intents that allows to capture [`Result`] at the end.
#[must_use]
pub struct IntentBuilder<'env> {
inner: Result<Intent<'env>>,
}

impl<'env> IntentBuilder<'env> {
pub fn from_object(env: JNIEnv<'env>, object: JObject<'env>) -> Self {
Self {
inner: Ok(Intent::from_object(env, object)),
}
}

fn from_fn(f: impl FnOnce() -> Result<Intent<'env>>) -> Self {
let inner = f();
Self { inner }
}

pub fn new(env: JNIEnv<'env>, action: impl AsRef<str>) -> Self {
Self::from_fn(|| Intent::new(env, action))
}

pub fn new_with_uri(env: JNIEnv<'env>, action: impl AsRef<str>, uri: impl AsRef<str>) -> Self {
Self::from_fn(|| Intent::new_with_uri(env, action, uri))
}

/// Set the class name for the intent target.
/// ```no_run
/// use android_intent::{Action, Extra, IntentBuilder};
///
/// # android_intent::with_current_env(|env| {
/// let intent = IntentBuilder::new(env, Action::Send)
/// intent.with_class_name("com.example", "IntentTarget");
/// # })
/// ```
pub fn with_class_name(
self,
package_name: impl AsRef<str>,
class_name: impl AsRef<str>,
) -> Self {
self.and_then(|inner| inner.with_class_name(package_name, class_name))
}

/// Add extended data to the intent.
/// ```no_run
/// use android_intent::{Action, Extra, IntentBuilder};
///
/// # android_intent::with_current_env(|env| {
/// let intent = IntentBuilder::new(env, Action::Send)
/// intent.with_extra(Extra::Text, "Hello World!");
/// # })
/// ```
pub fn with_extra(self, key: impl AsRef<str>, value: impl AsRef<str>) -> Self {
self.and_then(|inner| inner.with_extra(key, value))
}

/// Builds a new [`super::Action::Chooser`] Intent that wraps the given target intent.
/// ```no_run
/// use android_intent::{Action, IntentBuilder};
///
/// # android_intent::with_current_env(|env| {
/// let intent = IntentBuilder::new(env, Action::Send).into_chooser();
/// # })
/// ```
// TODO: Rename to with_?
pub fn into_chooser(self) -> Self {
self.into_chooser_with_title(None::<&str>)
}

// TODO: Rename to with_?
pub fn into_chooser_with_title(self, title: Option<impl AsRef<str>>) -> Self {
self.and_then(|inner| inner.into_chooser_with_title(title))
}

/// Set an explicit MIME data type.
/// ```no_run
/// use android_intent::{Action, IntentBuilder};
///
/// # android_intent::with_current_env(|env| {
/// let intent = IntentBuilder::new(env, Action::Send)
/// .with_type("text/plain");
/// # })
/// ```
pub fn with_type(self, type_name: impl AsRef<str>) -> Self {
self.and_then(|inner| inner.with_type(type_name))
}

pub fn start_activity(self) -> Result<()> {
self.inner.and_then(|inner| inner.start_activity())
}

fn and_then(mut self, f: impl FnOnce(Inner) -> Result<Inner, Error>) -> Self {
fn and_then(mut self, f: impl FnOnce(Intent) -> Result<Intent>) -> Self {
self.inner = self.inner.and_then(f);
self
}
Expand Down
Loading

0 comments on commit b8dc3a5

Please # to comment.