Run an instrument test from within app and wait for result - android

I am developing a QA automation solution that can record/playback QA tests on Android. A key business requirement is to have no dependency on a connected PC while playing back the recorded test. To that end, I'm trying to run an Instrumentation test without a connected PC. (Specifically, an Appium UiAutomator2 test).
My current approach is trying to run the test programmatically from my app. If I were running the test normally from a connected PC, I would use the command adb shell am instrument -w. I tried accessing ADB Shell from my app and running am instrument -w, but this produces an error that I am missing the INTERACT_ACROSS_USERS_FULL permission.
To get around this issue, I am trying to run the test using startInstrumentation. This successfully starts the test. However, the test immediately crashes. Upon further investigation, I traced the crash to an NPE: the test is trying to retrieve InstrumentationRegistry.getInstrumentation.getUiAutomation(0), but this returns null.
How do I run the test programatically and give it access to the required UiAutomation instance?
This is how I start the test:
public void runTest() {
final String pm = getPackageName().replaceFirst(".test$", "");
final InstrumentationInfo info = getInstrumentationInfo(pm);
if (info != null) {
final ComponentName cn = new ComponentName(info.packageName,
info.name);
Bundle arguments = new Bundle();
arguments.putString("class", "io.testim.appiumwrapper.test.AppiumUiAutomator2Server");
//cn = {io.extension.test/android.support.test.runner.AndroidJUnitRunner}
startInstrumentation(cn, null, arguments);
}
}

see signature protection level - clarifying ...unless having the package white-listed by Google's release key, you won't be able to obtain the necessary permission. this is a security/integrity feature, with the purpose to limit what malware is able to do - and what you intend to do there, is typical malware behavior - no matter the actual intention of it; working against the system leads nowhere.
the only way I could imaging would be to run commands directly from a terminal emulator or the test application - against a custom build of AOSP, so that you could add android:protectionLevel="signature" to the Manifest.xml and then require android.permission.INTERACT_ACROSS_USERS_FULL. but with a stock ROM, there definitely is no chance to do so. it is not, that it would be "impossible", but building a custom ROM means quite some effort, in order to get there. at least for Nexus and Pixel devices, the required drivers are available here; for other devices, you'd have to find them at the device's vendor, if they're even available.
the trick is to sign off the ROM with the same key as the app - only then signature level permissions can be obtained - while for a stock ROM, you'd (theoretically) need Google's release key to sign off the package. one can enforce single user, as explained here, while this also is only available to system apps.

Related

How to set device owner on Android Things?

I would like to set my Android app as device owner on a device running Android Things.
(I know that Android Things is getting shut down, but still I would need to make it work)
I tried the usual command: adb shell dpm set-device-owner <package>/<component>
This command works well on a standard Android device, but on Android Things I get the following response:
java.lang.RuntimeException: Can't set package ComponentInfo{<package>/<component>} as device owner.
at com.android.commands.dpm.Dpm.runSetDeviceOwner(Dpm.java:149)
at com.android.commands.dpm.Dpm.onRun(Dpm.java:96)
at com.android.internal.os.BaseCommand.run(BaseCommand.java:54)
at com.android.commands.dpm.Dpm.main(Dpm.java:41)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:285)
I tried running the command as root (after adb shell & su), but still get the same error.
I also tried to create manually device_owner.xml, device_policies.xml,... files in /system/data, as mentionned in topics related to AOSP, but it has no effect.
Is it somehow possible to set device owner on an Android Things device?
Android Things are already deprecated now, you can use smartphone OS or Android. That is based on this quote:
Android Things is a now-deprecated Android-based embedded operating system platform by Google, announced at Google I/O 2015, and launched in 2018. In 2019, Android Things dropped support for low-power hardware and refocused on smartphone-class devices.
Owner in android can be used to set in this document explanation, sample code :
val provisioningActivity = getActivity()
// You'll need the package name for the DPC app.
val myDPCPackageName = "com.example.myDPCApp"
// Set up the provisioning intent
val provisioningIntent = Intent("android.app.action.PROVISION_MANAGED_PROFILE")
provisioningIntent.putExtra(myDPCPackageName,
provisioningActivity.applicationContext.packageName)
if (provisioningIntent.resolveActivity(provisioningActivity.packageManager) == null) {
// No handler for intent! Can't provision this device.
// Show an error message and cancel.
} else {
// REQUEST_PROVISION_MANAGED_PROFILE is defined
// to be a suitable request code
startActivityForResult(provisioningIntent,
REQUEST_PROVISION_MANAGED_PROFILE)
provisioningActivity.finish()
}

Should a data application signed with the vendor key be able to run 'su'

Background: I am developing for a signage device which is to be remotely operated. I need an application that can
Fetch and install new packages
Reboot the device (for troubleshooting)
I have an unrooted Android device. I also have files which I am told are the platform keys.
I have developed an application which attempts to kick off the su process.
Process p = Runtime.getRuntime().exec("su");
Before I signed the application with the platform keys this was throwing an IOException, with the message being Permission Denied.
I signed the application with these platform keys, and I am still getting the Permission Denied exception.
Here are three contradictory statements. Which one of these statements (if any) is correct?
Statment 1: This should work. The application, even though is is stored in /data/app, should be able to run su. Either I have the wrong keys, or there's some other entry I need to add to the manifest to get it to work.
Statement 2: This shouldn't work. Even though it is signed with the platform key, the application is in /data/app, so it's a data application, not a system application. Data applications cannot run su on an unrooted devices. If this application was installed into /system/app, then it would be able to run su. (And I can't get it into /system/app because it's unrooted, so I'm stuck).
Statment 3: This will never work. If the device is not rooted, then NOTHING can run su, even if it is a signed system app.
Android shouldn't even have a su binary if you didn't flash some sort of root method to the device, such as Magisk or SuperSU.
Even if it does have a su binary, I wouldn't expect it to work, for one of two reasons. Assuming that your device comes with a preinstalled su binary, who's managing it? If it's unmanaged, it should just deny all requests. If you flash a root method, then it's up to that manager to decide if your app gets access to su, regardless of whether you have signature-level permissions or nor (the root manager uses a different signature, after all).
And why would you even need access to su as a signature app? You have total access to the device anyway. If you need to run a command, you should have no problems no matter what you run, as long as it's done from your platform-signed package. But since you have full access, the native APIs should let you do everything you need.
As for the IOException returned when you try to execute su in a Process, that's just a weird Android quirk. If there's no su binary installed, it'll sometimes return command not found and other times permission denied, depending on the device.
The point I think I'm making is that, unless your app is the root manager, you could be part of the system_server and still have the same access to su as everyone else. For which statement I agree with, I think #3, although I don't fully agree with it, because chances are su just doesn't exist, or it's a dud binary.
I've explained why #1 shouldn't be true, but #2 is just incorrect. If you look at the platform manifest, every permission that requires a privileged app can also be granted to signature apps. So even if you did move your app to /system/priv-app/ (/system/app/ won't make it privileged), it wouldn't make a difference. Basically, if your app is signed by the platform signature, it doesn't matter where it's installed.
EDIT:
You can easily reboot by just running reboot as a command, since you have signature-level access to the system, but it's a little more elegant to use the proper API for this. If you use the API, you get the shutdown animation, but you also let the system shut down gracefully, stopping services and sending the ACTION_REBOOT broadcast to any apps that might be listening for that.
To use the API, first add the following permission to your AndroidManifest:
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
Now, where you need to call the reboot action, use the following code:
IStatusBarService bar = IStatusBarService.Stub.asInterface(ServiceManager.getService(Context.STATUS_BAR_SERVICE));
bar.reboot(false); //using true here will reboot to Safe Mode
This method is a hidden method, so if you're using Android Studio to edit and compile, it'll error out. You can use reflection, or use Android Hidden API to access it directly.
This is how System UI implements it in the power menu: https://github.com/aosp-mirror/platform_frameworks_base/blob/master/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
This is the class that implements IStatusBar service: https://github.com/aosp-mirror/platform_frameworks_base/blob/master/services/core/java/com/android/server/statusbar/StatusBarManagerService.java#L969
I'd go with Statement 3. This will never work on an unrooted Android device. At least, not on recent Android OS versions (I have no idea if this might work on really old Android devices).
"su" is an application -- there has to be an "su" binary on disk in order to execute it, and Android does not by default provide an "su" binary for security purposes. When you use thirdparty rootkits, they install their own "su" binary to provide a mechanism for the user to elevate themselves to root privileges.
If your app is signed with a special key and granted elevated privileges from startup, why would you need to execute "su" anyway?

AWS Device Farm - Pass parameters to a Robotium Test Script - Android

I want to automate the Robotium tests running on my Android application, however, I need to pass in parameters to my tests in order to allow them to run with that configuration.
I can start my tests from the command line as such:
adb shell am instrument -r -w -e PARAM PARAM_1,PARAM_2 com.company.product.application.test/android.support.test.runner.AndroidJUnitRunner
I grab these parameters as such in my code:
Bundle extras = InstrumentationRegistry.getArguments();
if (extras.containsKey("PARAM")) {
this.param1= new ArrayList<>();
this.param1= Arrays.asList(extras.getString("PARAM").split("\\s*,\\s*"));
}
I am intending to call the Device Farm API (https://docs.aws.amazon.com/devicefarm/latest/APIReference/Welcome.html) to run the tests, but I am struggling to find anywhere where I can declare my arguments/parameters. Or find a way of uploading them in a file somewhere in AWS for the Device farm to read from.
I have found some articles from 4-5 years ago claiming that passing launcher parameters was not supported but it was being looked into, in case some claim me lazy, but no one has responded to any questions there, or on the Amazon forums so I thought I would place the question here.
Can anyone help me with the above?
The tests are executed by Device Farm internally so there's no way to control the arguments that get sent to it when running, currently. However, it should be possible to include additional files which could hold that same info for the tests. Here are two ideas I have regarding this:
The tests could use the extra data feature of Device Farm. The extra data zip file gets uploaded to the /sdcard on the device. Then the tests could get the information from the device itself. This is part of the schedule run API so the script running the tests can do this programmatically.
There could be a file that's included in the test apk that's uploaded to Device Farm. I've done something similar with Java Appium tests but not with instumention. In theory it should work.
Other than that, I have not found other ways of including arguments or files for Device Farm.
Hth
-James

Getting root permissions on android app within native code

I recently jumped into an android development tutorials and doing my own app for the sake of learning. I want to get root permissions within my code in a proper way. So not by calling /system/xbin/su but by using something like seteuid(0). Unfortunately seteuid method does not work for me.
I am testing app on a real device, which I rooted, enabled debugging mode and well I clearly see that when using a call to /system/xbin/su my app requests root permissions from system, which does not happen with seteuid and seteguid (setuid and setguid do not work either but I would not expect those to do it as they are capable only in lowering the permissions).
Please, advice on where to look for a proper code implementation for requesting root permissions like it would do a top notch developer. Maybe some native api call? Or does everyone just use a call to su to get the needed access?
The usual way in Linux of elevating privileges -- that is, to run an application with greater privileges than the logged-in user -- is to set the SUID flag on the executable (e.g., chmod ug+s ...). This will make the process take the identity of the binary's owner (usually root), rather than the logged-in user.
This is tricky to do effectively on Android, even on a rooted device. First, you won't be able to install an app using the usual (APK) mechanisms that includes binaries with SUID permissions. Second, an Android app is not an executable in the usual sense -- a single executable handles the launching of all apps.
Still, if you want to experiment on the command line, it should be possible to set the SUID flag on binaries, in at least some filesystem locations.
If you have a rooted Android, then very likely there is some infrastructure already in place to control privilege elevation. Most likely the "su" command will work (because there will be kernel mods to make it work), and it will be provided either with credentials or with some other way to control which apps can use it. I believe that, as you suggest, calling "su" is the usual way to do privilege elevation in apps on rooted Android. It is fraught with difficulties, however. There's a document https://su.chainfire.eu/ that explains how "su" is typically implemented in rooted Android devices, and gives some guidance on how to use it properly.
Just in case posting my solution to the problem which I did in Java (no native code is needed here):
protected void hideRoot() throws IOException, InterruptedException {
Process p = Runtime.getRuntime().exec("su");
DataOutputStream dos = new DataOutputStream(p.getOutputStream());
dos.writeBytes("mount -o remount,rw /system\n");
dos.writeBytes("mv /system/xbin/su /system/xbin/suhidden\n");
dos.writeBytes("exit\n");
dos.flush();
p.waitFor();
}

Can I run Appium for mobile with no target app?

Since I'm working in a platform which works with many apps (rather than a single target app), I find the selection of a target app inefficient for my needs. I wondered if I could do something to avoid it.
I'd like to run freely, sending UI commands to iOS and Android real devices, including installing an app from another app (like Play Store, Apple Store, Test Flight, etc.)
Thanks for the help,
David.
The rule is: 1 Webdriver instance per application.
You can run Appium's server with no --app argument by making sure auto-launch is set to false, and not setting bundleId or app.
Then, in your client/test framework, you use several webdrivers, configured to use different desired capabilities, to tie it all together under a single test case/suite.
The solution:
You may have a test suite that sets desired_capabilities to launch the Safari app, then you install the app, then you quit the webdriver.
Then you change the desired_capabilities to point to the bundle_id of the new app, launch another webdriver instance, do your tests, quit the webdriver..
Then you change the desired_capabilities to point to (etc., etc.)
driver = webdriver.new(url, desired_capabilities)
// do some stuff
driver.quit()
desired_capabilities['app'] = 'company.app.com'
driver = webdriver.new(url, desired_capabilities)
// do some stuff
driver.quit()
desired_capabilities['app'] = '/path/to/application.app'
driver = webdriver.new(url, desired_capabilities)
// do some stuff
driver.quit()

Categories

Resources