android_intent/
intent.rs

1use jni::{
2    errors::Error,
3    objects::{JObject, JString},
4    JNIEnv,
5};
6
7struct Inner<'env> {
8    env: JNIEnv<'env>,
9    object: JObject<'env>,
10}
11
12/// A messaging object you can use to request an action from another android app component.
13#[must_use]
14pub struct Intent<'env> {
15    inner: Result<Inner<'env>, Error>,
16}
17
18impl<'env> Intent<'env> {
19    pub fn from_object(env: JNIEnv<'env>, object: JObject<'env>) -> Self {
20        Self {
21            inner: Ok(Inner { env, object }),
22        }
23    }
24
25    fn from_fn(f: impl FnOnce() -> Result<Inner<'env>, Error>) -> Self {
26        let inner = f();
27        Self { inner }
28    }
29
30    pub fn new(env: JNIEnv<'env>, action: impl AsRef<str>) -> Self {
31        Self::from_fn(|| {
32            let intent_class = env.find_class("android/content/Intent")?;
33            let action_view =
34                env.get_static_field(intent_class, action.as_ref(), "Ljava/lang/String;")?;
35
36            let intent =
37                env.new_object(intent_class, "(Ljava/lang/String;)V", &[action_view.into()])?;
38
39            Ok(Inner {
40                env,
41                object: intent,
42            })
43        })
44    }
45
46    pub fn new_with_uri(env: JNIEnv<'env>, action: impl AsRef<str>, uri: impl AsRef<str>) -> Self {
47        Self::from_fn(|| {
48            let url_string = env.new_string(uri)?;
49            let uri_class = env.find_class("android/net/Uri")?;
50            let uri = env.call_static_method(
51                uri_class,
52                "parse",
53                "(Ljava/lang/String;)Landroid/net/Uri;",
54                &[JString::from(url_string).into()],
55            )?;
56
57            let intent_class = env.find_class("android/content/Intent")?;
58            let action_view =
59                env.get_static_field(intent_class, action.as_ref(), "Ljava/lang/String;")?;
60
61            let intent = env.new_object(
62                intent_class,
63                "(Ljava/lang/String;Landroid/net/Uri;)V",
64                &[action_view.into(), uri.into()],
65            )?;
66
67            Ok(Inner {
68                env,
69                object: intent,
70            })
71        })
72    }
73
74    /// Add extended data to the intent.
75    /// ```no_run
76    /// use android_intent::{Action, Extra, Intent};
77    ///
78    /// # android_intent::with_current_env(|env| {
79    /// let intent = Intent::new(env, Action::Send);
80    /// intent.push_extra(Extra::Text, "Hello World!")
81    /// # })
82    /// ```
83    pub fn with_extra(self, key: impl AsRef<str>, value: impl AsRef<str>) -> Self {
84        self.and_then(|inner| {
85            let key = inner.env.new_string(key)?;
86            let value = inner.env.new_string(value)?;
87
88            inner.env.call_method(
89                inner.object,
90                "putExtra",
91                "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
92                &[key.into(), value.into()],
93            )?;
94
95            Ok(inner)
96        })
97    }
98
99    /// Builds a new [`Action::Chooser`] Intent that wraps the given target intent.
100    /// ```no_run
101    /// use android_intent::{Action, Intent};
102    ///
103    /// # android_intent::with_current_env(|env| {
104    /// let intent = Intent::new(env, Action::Send).into_chhoser();
105    /// # })
106    /// ```
107    pub fn into_chooser(self) -> Self {
108        self.into_chooser_with_title(None::<&str>)
109    }
110
111    pub fn into_chooser_with_title(self, title: Option<impl AsRef<str>>) -> Self {
112        self.and_then(|mut inner| {
113            let title_value = if let Some(title) = title {
114                let s = inner.env.new_string(title)?;
115                s.into()
116            } else {
117                JObject::null().into()
118            };
119
120            let intent_class = inner.env.find_class("android/content/Intent")?;
121            let intent = inner.env.call_static_method(
122                intent_class,
123                "createChooser",
124                "(Landroid/content/Intent;Ljava/lang/CharSequence;)Landroid/content/Intent;",
125                &[inner.object.into(), title_value],
126            )?;
127
128            inner.object = intent.try_into()?;
129            Ok(inner)
130        })
131    }
132
133    /// Set an explicit MIME data type.
134    /// ```no_run
135    /// use android_intent::{Action, Intent};
136    ///
137    /// # android_intent::with_current_env(|env| {
138    /// let intent = Intent::new(env, Action::Send);
139    /// intent.set_type("text/plain");
140    /// # })
141    /// ```
142    pub fn with_type(self, type_name: impl AsRef<str>) -> Self {
143        self.and_then(|inner| {
144            let jstring = inner.env.new_string(type_name)?;
145
146            inner.env.call_method(
147                inner.object,
148                "setType",
149                "(Ljava/lang/String;)Landroid/content/Intent;",
150                &[jstring.into()],
151            )?;
152
153            Ok(inner)
154        })
155    }
156
157    pub fn start_activity(self) -> Result<(), Error> {
158        let cx = ndk_context::android_context();
159        let activity = unsafe { JObject::from_raw(cx.context() as jni::sys::jobject) };
160
161        self.inner.and_then(|inner| {
162            inner.env.call_method(
163                activity,
164                "startActivity",
165                "(Landroid/content/Intent;)V",
166                &[inner.object.into()],
167            )?;
168
169            Ok(())
170        })
171    }
172
173    fn and_then(mut self, f: impl FnOnce(Inner) -> Result<Inner, Error>) -> Self {
174        self.inner = self.inner.and_then(f);
175        self
176    }
177}