18

I'm using Espresso and UIAutomator to write my test cases. I'm testing external storage permissions when it's denied and when it's allowed. I have different test cases which all require the permission to be revoked at the start of the test case. However, some of the test cases should and do result in the permission being granted, so I need to revoke the permission when the next test is being executed. I have searched around and the closest thing I came across is using the pm manager to execute adb shell commands to revoke the permission. However by doing so, I'll get the following error, Instrumentation run failed due to 'process crash'. Is there any way I can ensure permissions are revoked at the beginning of every test case? If not, how can this issue be resolved regarding testing permissions? Thank you for your help in advance!

This is the code snippet I currently have to revoke permission before each test case (which doesn't work):

@Before
public void setUp() {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        getInstrumentation().getUiAutomation().executeShellCommand(
                "pm revoke " + getTargetContext().getPackageName()
                        + " android.permission.WRITE_EXTERNAL_STORAGE");
    }
}

Corresponding error message when trying to revoke permission with above code snippet:

Test failed to run to completion. Reason: 'Instrumentation run failed due to 'Process crashed.''.

I have also come across these two posts: this and this.

Charles Li
  • 1,835
  • 3
  • 24
  • 40

7 Answers7

12

You should not revoke any permission before the tests start. This will restart the entire test app process and the test will be marked as failure.

Instead you can revoke the permission after test execution is completed i.e. in @After method as follows:

@After
fun tearDown(){
    InstrumentationRegistry.getInstrumentation().uiAutomation.
            executeShellCommand("pm revoke ${getTargetContext().packageName} android.permission.WRITE_EXTERNAL_STORAGE")
}

Alternatively, you can use Android Test Orchestrator version 1.0.2 and set

 testInstrumentationRunnerArguments clearPackageData: 'true'

this will clear the package data after each test.

Note: Android Test Orchestrator v1.0.2 has issues when it comes to managing code coverage reports via Jacoco.

Sagar
  • 23,903
  • 4
  • 62
  • 62
6

ADB has the capability to revoke permissions, but it can't be called from an instrumentation test because revoking a permission will restart the entire app process and end the test with failure. The following Kotlin code will perform the revocation, but will crash your test if a permission gets revoked.

/**
 *  Will revoke, but also CRASH if called by a test
 *  Revocation requires a full process restart, which crashes your test process.
 */
fun revokePermissions(vararg permissions: String) {
    permissions.forEach {
        InstrumentationRegistry.getInstrumentation().uiAutomation.
                executeShellCommand("pm revoke ${getTargetContext().packageName} $it")
    }
}

If you run your tests from a shell script, you can simply use ONE of the following shell commands before starting each test.

adb shell pm revoke com.package.appname android.permission.CAMERA
adb shell pm reset-permissions com.package.appname
adb shell pm clear com.package.appname

The same crash happens whether you revoke permissions from a custom AndroidJUnitRunner's onCreate() or onStart() or from the middle of a test. You cannot revoke permissions during an instrumentation test without crashing even when the activity is not opened yet.

It seems like the best option is to revoke all permissions from Gradle before running any instrumentation test and then you can re-enable any permissions you want set before the test starts using a GrantPermissionRule.

The Android Test Orchestrator was planned to clear all data automatically between each test, but that feature is still not there today. I filed a bug you can vote up to have automatic cleanup between tests.

colintheshots
  • 1,992
  • 19
  • 37
2

I'm not sure on how to do that at runtime without prompting the user. However, you stated in the title that it is for test purposes. Therefore I have some options i can suggest you

a)When testing need be done without permissions, just revoke it manually using the settings of your device.

b) You could check and ask for permission, then refuse to give permission. I'd give you some code I use to check and ask for camera permission. You'd just have to change the permission name, and maybe the condition to check :

public static boolean checkCameraPermission(MainActivity thisActivity) {
    return ContextCompat.checkSelfPermission(thisActivity,
            Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED;
}

public static void checkAndAskCameraPermission(final MainActivity thisActivity) {

    if (!checkCameraPermission(thisActivity)) {
        //No right is granted
        // Should we show an explanation?
        if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
                Manifest.permission.CAMERA)) {

            //Open a dialog explaining why you are asking permission then when when positive button is triggered, call this line
            ActivityCompat.requestPermissions(thisActivity,
                            new String[]{Manifest.permission.CAMERA},
                            CHECK_FOR_CAMERA_PERMISSION);

        } else {
            // No explanation needed, we can request the permission.
            ActivityCompat.requestPermissions(thisActivity,
                    new String[]{Manifest.permission.CAMERA},
                    CHECK_FOR_CAMERA_PERMISSION);
        }
    }
}

I could give you some more links about the b) (how to ask permissions) if you need so (just comment it). However as i'm not sure this answer would interest you, i won't spend time to document this option.

But then, maybe someone will be able to explain you how to do it directly at runtime without prompting, but I though it may be interesting to provide you at least this information.

Feuby
  • 708
  • 4
  • 7
  • Thank you for your help Feuby! However, the problem is not with asking permission. The problem is some test cases will end up with permission granted. And for the following test case, I'll need to test a case where I need to perform the action of denying permission, which can't be done since it's already granted. And in regards to a) I can fix the order in which test cases are run and always reset the permission manually in the settings, but it's not ideal since running my test class back to back shouldn't require me manually resetting the permission. Again, thanks for your help! – Charles Li Apr 25 '17 at 03:26
  • Then i'd suggest you to take a look at something called "tokens" i thing. I'm not sure about the exact term, but it's something your application can give to another application to allow it to execute with your currect application permissions. Maybe you could manage to "restrict" this token and give it to one of your activities through a general intent (handled properly by your application) in order to deny this permission. However, i'm not really sure of the name "token" nor on how to do that, it's something I did not pay attention to when i was searching for another problem. – Feuby Apr 25 '17 at 13:21
2

You can't. Grant/Revoke permissions is under user control.

However, you can grant/revoke permissions via adb and you can trigger adb commands programmatically, but I have no idea if this works in combination?...

adb shell pm grant [app.package] [permission]
adb shell pm grant com.app.package.path android.permission.READ_CONTACTS

(look here)

You might map your permissions to boolean value for testing purposes?

Community
  • 1
  • 1
longi
  • 11,104
  • 10
  • 55
  • 89
  • Thanks for your suggestion longilong. The suggestion you mentioned is what I have been using as mentioned in the question. However, it doesn't work as it'll generate an error message. I'm looking for a solution that can allow me to run my test class without having to make any changes manually before each run as it will defeat the purpose. Thanks for your help anyways! – Charles Li Apr 26 '17 at 22:48
  • 1
    This answer is incorrect, the OP is asking for Grant/Revoke permissions during tests and not in normal use. During tests the app or the instrumentation code could get access to a shell with the same exact privileges as `adb shell` which the OP already stated and is `getInstrumentation().getUiAutomation().executeShellCommand()` or similar. Hence during the tests only the app or the instrumentation can Grant and Revoke itself permissions. However the problem exists in revoking as this causes the OS to force close the app while it is executing tests hence crashing it – ahasbini Mar 05 '18 at 16:45
2

I ran into the same problem and have applied the following solution ( working great everytime for now)

Create a gradle task:

task revokeLocationPermissions(type: Exec) {
    def revokeLocationPermission = ['adb', 'shell', 'pm', 'revoke', 'yourPackageNameHere' , 'android.permission.ACCESS_FINE_LOCATION']
    commandLine revokeLocationPermission
    outputs.upToDateWhen { false }
}

Go to Run/Debug Configurations, and select your Android Instrumented Test. Below, you will have an option "Before launch:" Click the plus sign, and add your custom gradle task

Image

Hope it helps! I've been trying to find a scalable solution for projects with multiple flavors, and thus multiple package names, but so far nothing that really works.

Petermonteer
  • 296
  • 4
  • 17
1

I've implemented a solution which leverages wrapper classes, overriding and build variant configuration. The solution is quite long to explain and is found over here: https://github.com/ahasbini/AndroidTestMockPermissionUtils.

It is not yet packed in an sdk but the main idea is to override the functionalities of ContextWrapper.checkSelfPermission and ActivityCompat.requestPermissions to be manipulated and return mocked results tricking the app into the different scenarios to be tested like: permission was denied hence the app requested it and ended with granted permission. This scenario will occur even if the app had the permission all along but the idea is that it was tricked by the mocked results from the overriding implementation.

Furthermore the implementation has a TestRule called PermissionRule class which can be used in the test classes to easily simulate all of the conditions to test the permissions seamlessly. Also assertions can be made like ensuring the app has called requestPermissions() for example.

ahasbini
  • 6,761
  • 2
  • 29
  • 45
0

I used Petermonteer answer as a start, I finished up doing the following to reset ALL permissions (sadly also system settings):

  1. Go to build.gradle file, find the android block
  2. Inside that block, copy paste this code:

Code:

task revokeAllPermissions(type: Exec) {
    // If you get an error here, install adb first (easy through homebrew: $ brew install android-platform-tools)
    def revokeAllPermissions = [
            'adb',
            'shell',
            'pm',
            'reset-permissions',
    ]

    commandLine revokeAllPermissions

    outputs.upToDateWhen { false }
}

Next up, go to the configuration of your task and add this:

enter image description here

In which the gradle tasks looks like:

enter image description here

J. Doe
  • 12,159
  • 9
  • 60
  • 114