Android revoke permission at start of each test

2019-06-15 08:41发布

问题:

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.

回答1:

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.



回答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.



回答3:

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?



回答4:

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.



回答5:

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.