1use std::ffi::c_void;
30use std::sync::OnceLock;
31
32use jni::objects::{GlobalRef, JObject, JObjectArray, JString};
33use jni::{JNIEnv, JavaVM};
34
35#[cfg(feature = "pac-android-native")]
36use super::proxy_info::SelectedProxy;
37use super::proxy_info::{ProxyInfo, mode_from_proxy_info};
38use crate::config::{ProxyConfig, ProxyConfigSource};
39use crate::error::Error;
40use crate::watch::WatchOptions;
41
42mod receiver;
43
44pub(crate) use receiver::Watch;
45
46pub(crate) fn read_config(_options: &WatchOptions) -> Result<ProxyConfig, Error> {
47 let info = read_proxy_info()?;
48 let mode = mode_from_proxy_info(info.as_ref())?;
49 Ok(ProxyConfig::from_source(
50 ProxyConfigSource::ConnectivityManager,
51 mode,
52 ))
53}
54
55fn read_proxy_info() -> Result<Option<ProxyInfo>, Error> {
56 with_framework(READING, read_in)
57}
58
59const READING: &str = "reading ConnectivityManager.getDefaultProxy() through JNI";
60
61static HOST: OnceLock<Host> = OnceLock::new();
65
66struct Host {
67 vm: JavaVM,
68 app: GlobalRef,
69}
70
71const INITIALIZING: &str = "proxy_watch::android::init";
72
73pub unsafe fn init(vm: *mut c_void, context: *mut c_void) -> Result<(), Error> {
92 let raw = vm.cast::<jni::sys::JavaVM>();
93 if let Some(host) = HOST.get() {
94 return same_vm(host, raw);
95 }
96 let vm = unsafe { JavaVM::from_raw(raw) }.map_err(|e| jni_error(INITIALIZING, e))?;
98 let app = call_in(INITIALIZING, &vm, |env| {
99 let given = unsafe { JObject::from_raw(context.cast()) };
101 let app = application_context(env, &given)?;
102 if app.is_null() {
103 return Err(jni::errors::Error::NullPtr(
105 "getApplicationContext() result",
106 ));
107 }
108 env.new_global_ref(app)
109 })?;
110 match HOST.set(Host { vm, app }) {
111 Ok(()) => Ok(()),
112 Err(_) => same_vm(HOST.get().expect("HOST is set"), raw),
114 }
115}
116
117fn same_vm(host: &Host, raw: *mut jni::sys::JavaVM) -> Result<(), Error> {
119 if host.vm.get_java_vm_pointer() == raw {
120 Ok(())
121 } else {
122 Err(jni_error(
123 INITIALIZING,
124 "called again with a different JavaVM than the first call",
125 ))
126 }
127}
128
129fn with_framework<T>(
132 what: &'static str,
133 body: impl FnOnce(&mut JNIEnv, &JObject) -> jni::errors::Result<T>,
134) -> Result<T, Error> {
135 if let Some(host) = HOST.get() {
136 return call_in(what, &host.vm, |env| body(env, host.app.as_obj()));
137 }
138 let context = std::panic::catch_unwind(ndk_context::android_context).map_err(|_| {
142 jni_error(
143 what,
144 "the host app has neither called proxy_watch::android::init nor registered its \
145 JavaVM and Context with ndk_context::initialize_android_context",
146 )
147 })?;
148 let vm = unsafe { JavaVM::from_raw(context.vm().cast()) }.map_err(|e| jni_error(what, e))?;
151 call_in(what, &vm, |env| {
152 let registered = unsafe { JObject::from_raw(context.context().cast()) };
154 let app = application_context(env, ®istered)?;
155 let app = if app.is_null() { registered } else { app };
156 body(env, &app)
157 })
158}
159
160fn application_context<'local>(
164 env: &mut JNIEnv<'local>,
165 context: &JObject,
166) -> jni::errors::Result<JObject<'local>> {
167 env.call_method(
168 context,
169 "getApplicationContext",
170 "()Landroid/content/Context;",
171 &[],
172 )?
173 .l()
174}
175
176fn call_in<T>(
177 what: &'static str,
178 vm: &JavaVM,
179 body: impl FnOnce(&mut JNIEnv) -> jni::errors::Result<T>,
180) -> Result<T, Error> {
181 let mut env = vm.attach_current_thread().map_err(|e| jni_error(what, e))?;
182 let result = env.with_local_frame(16, |env| body(env));
184 result.map_err(|e| match take_exception(&mut env) {
185 Some(thrown) => jni_error(what, format!("{e}: {thrown}")),
186 None => jni_error(what, e),
187 })
188}
189
190fn take_exception(env: &mut JNIEnv) -> Option<String> {
193 if !env.exception_check().unwrap_or(false) {
194 return None;
195 }
196 let thrown = env.exception_occurred();
197 let _ = env.exception_clear();
198 let thrown = thrown.ok()?;
199 let text = env
200 .with_local_frame(4, |env| string_of(env, &thrown, "toString"))
201 .ok()
202 .flatten();
203 if env.exception_check().unwrap_or(false) {
205 let _ = env.exception_clear();
206 }
207 let _ = env.delete_local_ref(thrown);
208 text
209}
210
211fn read_in(env: &mut JNIEnv, app: &JObject) -> jni::errors::Result<Option<ProxyInfo>> {
212 let service = env
213 .get_static_field(
214 "android/content/Context",
215 "CONNECTIVITY_SERVICE",
216 "Ljava/lang/String;",
217 )?
218 .l()?;
219 let manager = env
220 .call_method(
221 app,
222 "getSystemService",
223 "(Ljava/lang/String;)Ljava/lang/Object;",
224 &[(&service).into()],
225 )?
226 .l()?;
227 let info = env
228 .call_method(
229 &manager,
230 "getDefaultProxy",
231 "()Landroid/net/ProxyInfo;",
232 &[],
233 )?
234 .l()?;
235 if info.is_null() {
236 return Ok(None);
237 }
238 let host = string_of(env, &info, "getHost")?;
239 let port = env.call_method(&info, "getPort", "()I", &[])?.i()?;
240 let list = JObjectArray::from(
241 env.call_method(&info, "getExclusionList", "()[Ljava/lang/String;", &[])?
242 .l()?,
243 );
244 let mut exclusions = Vec::new();
245 if !list.is_null() {
246 for index in 0..env.get_array_length(&list)? {
247 let entry = JString::from(env.get_object_array_element(&list, index)?);
248 if !entry.is_null() {
249 exclusions.push(env.get_string(&entry)?.into());
250 }
251 env.delete_local_ref(entry)?;
253 }
254 }
255 let uri = env
256 .call_method(&info, "getPacFileUrl", "()Landroid/net/Uri;", &[])?
257 .l()?;
258 let pac_url = if uri.is_null() {
259 None
260 } else {
261 string_of(env, &uri, "toString")?
262 };
263 Ok(Some(ProxyInfo {
264 host,
265 port,
266 exclusions,
267 pac_url,
268 }))
269}
270
271#[cfg(feature = "pac-android-native")]
279pub(crate) fn select(uri: &str) -> Result<(Option<String>, Option<Vec<SelectedProxy>>), Error> {
280 with_framework(READING, |env, app| {
281 let pac_url = read_in(env, app)?.and_then(|info| info.pac_url);
282 Ok((pac_url, select_in(env, uri)?))
283 })
284}
285
286#[cfg(feature = "pac-android-native")]
287const PAC_SELECTOR: &str = "android.net.PacProxySelector";
288
289#[cfg(feature = "pac-android-native")]
290fn select_in(env: &mut JNIEnv, uri: &str) -> jni::errors::Result<Option<Vec<SelectedProxy>>> {
291 let selector = env
292 .call_static_method(
293 "java/net/ProxySelector",
294 "getDefault",
295 "()Ljava/net/ProxySelector;",
296 &[],
297 )?
298 .l()?;
299 if selector.is_null() {
300 return Ok(None);
301 }
302 let class = env
307 .call_method(&selector, "getClass", "()Ljava/lang/Class;", &[])?
308 .l()?;
309 if string_of(env, &class, "getName")?.as_deref() != Some(PAC_SELECTOR) {
310 return Ok(None);
311 }
312 let text = env.new_string(uri)?;
313 let uri = env
314 .call_static_method(
315 "java/net/URI",
316 "create",
317 "(Ljava/lang/String;)Ljava/net/URI;",
318 &[(&text).into()],
319 )?
320 .l()?;
321 let list = env
322 .call_method(
323 &selector,
324 "select",
325 "(Ljava/net/URI;)Ljava/util/List;",
326 &[(&uri).into()],
327 )?
328 .l()?;
329 let mut selected = Vec::new();
330 for index in 0..env.call_method(&list, "size", "()I", &[])?.i()? {
331 selected.push(env.with_local_frame(8, |env| {
333 let proxy = env
334 .call_method(&list, "get", "(I)Ljava/lang/Object;", &[index.into()])?
335 .l()?;
336 let kind = env
337 .call_method(&proxy, "type", "()Ljava/net/Proxy$Type;", &[])?
338 .l()?;
339 let kind = string_of(env, &kind, "name")?.unwrap_or_default();
340 let address = env
341 .call_method(&proxy, "address", "()Ljava/net/SocketAddress;", &[])?
342 .l()?;
343 let (host, port) = if address.is_null() {
344 (None, 0)
345 } else {
346 let host = string_of(env, &address, "getHostString")?;
347 (host, env.call_method(&address, "getPort", "()I", &[])?.i()?)
348 };
349 Ok::<_, jni::errors::Error>(SelectedProxy { kind, host, port })
350 })?);
351 }
352 Ok(Some(selected))
353}
354
355fn string_of(
357 env: &mut JNIEnv,
358 object: &JObject,
359 getter: &str,
360) -> jni::errors::Result<Option<String>> {
361 let value = JString::from(
362 env.call_method(object, getter, "()Ljava/lang/String;", &[])?
363 .l()?,
364 );
365 if value.is_null() {
366 return Ok(None);
367 }
368 Ok(Some(env.get_string(&value)?.into()))
369}
370
371fn jni_error(what: &'static str, source: impl std::fmt::Display) -> Error {
372 Error::io(what, std::io::Error::other(source.to_string()))
373}