/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the Lice`nse is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.test.rule; import static androidx.test.internal.util.Checks.checkNotNull; import android.Manifest.permission; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.test.internal.platform.ServiceLoaderWrapper; import androidx.test.internal.platform.content.PermissionGranter; import androidx.test.runner.permission.PermissionRequester; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; /** * The {@code GrantPermissionRule} Rule allows granting of runtime permissions on Android M (API 23) * and above. Use this {@code Rule} when a test requires a runtime permission to do its work. * *

When applied to a test class this Rule attempts to grant all requested runtime permissions. * The requested permissions will then be granted on the device and will take immediate effect. * Permissions can only be requested on Android M (API 23) or above and will be ignored on all other * API levels. Once a permission is granted it will apply for all tests running in the current * Instrumentation. There is no way of revoking a permission after it was granted. Attempting to do * so will crash the Instrumentation process. * *

Note, this Rule is usually used to grant runtime permissions to avoid the permission dialog * from showing up and blocking the App's Ui. This is especially helpful for Ui-Testing to avoid * losing control over the app under test. * *

The requested permissions will be granted for all test methods in the test class. Use {@link * #grant(String...)} static factory method to request a variable number of permissions. * *

Usage: * *

 * @Rule
 * public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule
 *     .grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
 * 
* *

* *

Note: As per * the documentation this rule will automatically grant {@link * android.Manifest.permission#READ_EXTERNAL_STORAGE} when {@link * android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is requested. * *

See Request App * Permissions for more details on runtime permissions. */ public class GrantPermissionRule implements TestRule { private PermissionGranter permissionGranter; private GrantPermissionRule() { this(ServiceLoaderWrapper.loadSingleService(PermissionGranter.class, PermissionRequester::new)); } @VisibleForTesting GrantPermissionRule(@NonNull PermissionGranter permissionGranter) { setPermissionGranter(permissionGranter); } /** * Static factory method that grants the requested permissions. * *

Permissions will be granted before any methods annotated with {@code @Before} but before * any test method execution. * * @param permissions a variable list of Android permissions * @return {@link GrantPermissionRule} * @see android.Manifest.permission */ public static GrantPermissionRule grant(String... permissions) { GrantPermissionRule grantPermissionRule = new GrantPermissionRule(); grantPermissionRule.grantPermissions(permissions); return grantPermissionRule; } private void grantPermissions(String... permissions) { Set permissionSet = satisfyPermissionDependencies(permissions); permissionGranter.addPermissions(permissionSet.toArray(new String[permissionSet.size()])); } @VisibleForTesting Set satisfyPermissionDependencies(String... permissions) { Set permissionList = new LinkedHashSet<>(Arrays.asList(permissions)); // Explicitly grant READ_EXTERNAL_STORAGE permission when WRITE_EXTERNAL_STORAGE was requested. if (permissionList.contains(permission.WRITE_EXTERNAL_STORAGE)) { permissionList.add(permission.READ_EXTERNAL_STORAGE); } return permissionList; } @Override public final Statement apply(final Statement base, Description description) { return new RequestPermissionStatement(base); } @VisibleForTesting void setPermissionGranter(PermissionGranter permissionGranter) { this.permissionGranter = checkNotNull(permissionGranter, "permissionRequester cannot be null!"); } private class RequestPermissionStatement extends Statement { private final Statement base; public RequestPermissionStatement(Statement base) { this.base = base; } @Override public void evaluate() throws Throwable { permissionGranter.requestPermissions(); base.evaluate(); } } }