diff --git a/.changes/android-build-variants.md b/.changes/android-build-variants.md new file mode 100644 index 00000000..7f234c29 --- /dev/null +++ b/.changes/android-build-variants.md @@ -0,0 +1,6 @@ +--- +"cargo-mobile2": patch +--- + +Added `Device::run_with_application_id_suffix` to support running different build variants. This relates to the `applicationIdSuffix` as defined in Android development, +described [in these docs](https://developer.android.com/build/build-variants#build-types). diff --git a/src/android/cli.rs b/src/android/cli.rs index fb08b6b4..71625670 100644 --- a/src/android/cli.rs +++ b/src/android/cli.rs @@ -76,6 +76,11 @@ pub enum Command { help = "Specifies which activtiy to launch" )] activity: Option, + #[structopt( + long = "application-id-suffix", + help = "Optional suffix for the application ID (e.g. \".debug\")" + )] + application_id_suffix: Option, }, #[structopt(name = "st", about = "Displays a detailed stacktrace for a device")] Stacktrace, @@ -301,12 +306,13 @@ impl Exec for Input { filter: cli::Filter { filter }, reinstall_deps: cli::ReinstallDeps { reinstall_deps }, activity, + application_id_suffix, } => with_config(non_interactive, wrapper, |config, metadata, env| { let build_app_bundle = metadata.asset_packs().is_some(); ensure_init(config)?; device_prompt(env) .map_err(Error::DevicePromptFailed)? - .run( + .run_with_application_id_suffix( config, env, noise_level, @@ -320,6 +326,7 @@ impl Exec for Input { .unwrap_or(DEFAULT_ACTIVITY) .to_string() }), + application_id_suffix, ) .and_then(|h| { h.wait().map(|_| ()).map_err(|err| RunError::CommandFailed { diff --git a/src/android/device.rs b/src/android/device.rs index ada160db..f19cbee4 100644 --- a/src/android/device.rs +++ b/src/android/device.rs @@ -370,6 +370,12 @@ impl<'a> Device<'a> { Ok(()) } + /** + * This is the legacy function that doesn't support applicationIdSuffix, which + * is required for running different build variants (such as debug and release). + * + * It is kept for backwards compatibility. + */ #[allow(clippy::too_many_arguments)] pub fn run( &self, @@ -381,6 +387,32 @@ impl<'a> Device<'a> { build_app_bundle: bool, reinstall_deps: bool, activity: String, + ) -> Result { + return self.run_with_application_id_suffix( + config, + env, + noise_level, + profile, + filter_level, + build_app_bundle, + reinstall_deps, + activity, + None, + ); + } + + #[allow(clippy::too_many_arguments)] + pub fn run_with_application_id_suffix( + &self, + config: &Config, + env: &Env, + noise_level: NoiseLevel, + profile: Profile, + filter_level: Option, + build_app_bundle: bool, + reinstall_deps: bool, + activity: String, + application_id_suffix: Option, ) -> Result { if build_app_bundle { bundletool::install(reinstall_deps).map_err(RunError::BundletoolInstallFailed)?; @@ -402,7 +434,12 @@ impl<'a> Device<'a> { self.install_apk(config, env, profile) .map_err(RunError::ApkInstallFailed)?; } - let activity = format!("{}/{}", config.app().identifier(), activity); + + let activity = Device::resolve_activity_name( + config.app().identifier().to_string(), + application_id_suffix, + activity, + ); let activity_ = activity.clone(); let cmd = self .adb(env) @@ -518,4 +555,72 @@ impl<'a> Device<'a> { } Ok(()) } + + /** + * The activity name is the fully qualified class name of the activity to launch. + * Here are the expected formats for the activity name: of the release and debug variants + * + * Even though the actual logic of this method is very simple, it is kept + * separate for clarity and for unit testing. + * + * Release variants have 2 acceptable formats: + * * `com.example.app/.MainActivity` + * * `com.example.app/com.example.app.MainActivity` + * + * Debug variants have 1 acceptable format: + * * `com.example.app.debug/com.example.app.MainActivity` + */ + fn resolve_activity_name( + app_identifier: String, + application_id_suffix: Option, + activity: String, + ) -> String { + return format!( + "{app_identifier}{}/{activity}", + application_id_suffix.unwrap_or_default() + ); + } +} + +#[cfg(test)] +mod test { + use super::*; + use rstest::rstest; + #[rstest( + app_identifier, + application_id_suffix, + activity, + expected, + case( + "com.example.app", + Some(".debug"), + "com.example.app.MainActivity", + "com.example.app.debug/com.example.app.MainActivity" + ), + case( + "com.example.app", + None, + ".MainActivity", + "com.example.app/.MainActivity" + ), + case( + "com.example.app", + None, + "com.example.app.MainActivity", + "com.example.app/com.example.app.MainActivity" + ) + )] + fn test_resolve_activity_name( + app_identifier: String, + application_id_suffix: Option<&str>, + activity: String, + expected: String, + ) { + let activity = Device::resolve_activity_name( + app_identifier, + application_id_suffix.map(|s| s.to_string()), + activity, + ); + assert_eq!(activity, expected); + } }