1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Reads Android's packaged `versionName` and `versionCode`.
//!
//! Both live in the manifest of the installed APK, not in anything the Rust
//! compiler saw: Gradle can add a `versionNameSuffix` per build type, and CI
//! commonly stamps the version code at package time. So a version baked into
//! the binary is the version the *source* wanted, and the one `PackageManager`
//! reports is the version the user actually has — which is the one an About
//! screen should print and a bug report should quote.
//!
//! Read once at startup: the packaged identity of a running process cannot
//! change, and a JNI round trip does not belong on any repeated path.
use cranpose_services::app_info::{set_platform_app_info, AppInfo};
use jni::objects::JString;
use jni::{jni_sig, jni_str};
use std::rc::Rc;
struct AndroidAppInfo {
version_name: Option<String>,
build_version: Option<String>,
}
impl AppInfo for AndroidAppInfo {
fn version_name(&self) -> Option<String> {
self.version_name.clone()
}
fn build_version(&self) -> Option<String> {
self.build_version.clone()
}
}
/// Reads the packaged version and installs it as the platform app info.
///
/// A failure is not fatal — the app keeps whatever fallback it had, which is
/// the behaviour before this was readable at all — but it is worth a log,
/// because a version screen that quietly shows the wrong number is worse than
/// one that shows nothing.
pub(crate) fn install_app_info(app: &android_activity::AndroidApp) {
match query_app_info(app) {
Ok((version_name, build_version)) => {
set_platform_app_info(Rc::new(AndroidAppInfo {
version_name,
build_version,
}));
}
Err(error) => {
log::warn!("[android-app-info] could not read the packaged version: {error}");
}
}
}
fn query_app_info(
app: &android_activity::AndroidApp,
) -> Result<(Option<String>, Option<String>), String> {
crate::android_jni::with_android_activity_env(app, |env, activity| {
let describe = |env: &mut jni::Env<'_>, what: &str, error: jni::errors::Error| {
crate::android_jni::clear_pending_android_jni_exception(env);
format!("{what} failed: {error}")
};
let manager = env
.call_method(
&activity,
jni_str!("getPackageManager"),
jni_sig!("()Landroid/content/pm/PackageManager;"),
&[],
)
.and_then(|value| value.l())
.map_err(|error| describe(env, "Activity.getPackageManager", error))?;
let package = env
.call_method(
&activity,
jni_str!("getPackageName"),
jni_sig!("()Ljava/lang/String;"),
&[],
)
.and_then(|value| value.l())
.map_err(|error| describe(env, "Activity.getPackageName", error))?;
let info = env
.call_method(
&manager,
jni_str!("getPackageInfo"),
jni_sig!("(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"),
&[(&package).into(), 0i32.into()],
)
.and_then(|value| value.l())
.map_err(|error| describe(env, "PackageManager.getPackageInfo", error))?;
// `versionName` is nullable in the manifest, so an absent one is a
// legitimate answer rather than a failure.
let name_object = env
.get_field(
&info,
jni_str!("versionName"),
jni_sig!("Ljava/lang/String;"),
)
.and_then(|value| value.l())
.map_err(|error| describe(env, "PackageInfo.versionName", error))?;
let version_name = if name_object.is_null() {
None
} else {
JString::cast_local(env, name_object)
.and_then(|text| text.try_to_string(env))
.ok()
};
// `getLongVersionCode` since API 28; below that the `int` field is the
// whole story, and the same number.
let build_version = env
.call_method(&info, jni_str!("getLongVersionCode"), jni_sig!("()J"), &[])
.and_then(|value| value.j())
.or_else(|_| {
crate::android_jni::clear_pending_android_jni_exception(env);
env.get_field(&info, jni_str!("versionCode"), jni_sig!("I"))
.and_then(|value| value.i())
.map(i64::from)
})
.ok()
.map(|version| version.to_string());
Ok((version_name, build_version))
})
}