1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.car.test; 18 19 import static java.lang.annotation.ElementType.METHOD; 20 import static java.lang.annotation.ElementType.TYPE; 21 import static java.lang.annotation.RetentionPolicy.RUNTIME; 22 23 import android.app.UiAutomation; 24 import android.util.ArraySet; 25 import android.util.Log; 26 27 import androidx.test.platform.app.InstrumentationRegistry; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 31 import org.junit.rules.TestRule; 32 import org.junit.runner.Description; 33 import org.junit.runners.model.Statement; 34 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.Target; 37 import java.util.Set; 38 39 /** 40 * {@code JUnit} rule that uses {@link UiAutomation} to adopt the Shell permissions defined by 41 * {@link EnsureHasPermission}. 42 */ 43 // TODO(b/250108245): move to Bedstead itself or merge with 44 // {@code com.android.compatibility.common.util.AdoptShellPermissionsRule} (which currently takes 45 // the permissions from the constructor) 46 public final class PermissionsCheckerRule implements TestRule { 47 48 @VisibleForTesting 49 static final String TAG = PermissionsCheckerRule.class.getSimpleName(); 50 51 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 52 53 private final UiAutomation mUiAutomation; 54 PermissionsCheckerRule()55 public PermissionsCheckerRule() { 56 this(InstrumentationRegistry.getInstrumentation().getUiAutomation()); 57 } 58 59 @VisibleForTesting PermissionsCheckerRule(UiAutomation uiAutomation)60 PermissionsCheckerRule(UiAutomation uiAutomation) { 61 mUiAutomation = uiAutomation; 62 } 63 64 @Override apply(Statement base, Description description)65 public Statement apply(Statement base, Description description) { 66 return new Statement() { 67 @Override 68 public void evaluate() throws Throwable { 69 if (DBG) { 70 Log.d(TAG, "evaluating " + description.getDisplayName()); 71 } 72 73 Set<String> permissionsBefore = mUiAutomation.getAdoptedShellPermissions(); 74 if (permissionsBefore != null && !permissionsBefore.isEmpty()) { 75 Log.w(TAG, "Permissions were adopted before the test: " + permissionsBefore); 76 } 77 78 // Gets all permissions, from test, test class, and superclasses 79 ArraySet<String> permissions = new ArraySet<>(); 80 // Test itself 81 addPermissions(permissions, 82 description.getAnnotation(EnsureHasPermission.class)); 83 // Test class and superclasses 84 Class<?> testClass = description.getTestClass(); 85 while (testClass != null) { 86 addPermissions(permissions, 87 testClass.getAnnotation(EnsureHasPermission.class)); 88 testClass = testClass.getSuperclass(); 89 } 90 91 if (permissions.isEmpty()) { 92 if (DBG) { 93 Log.d(TAG, "No annotation, running tests as-is"); 94 } 95 base.evaluate(); 96 return; 97 } 98 99 adoptShellPermissions(permissions, "Adopting Shell permissions before test: %s"); 100 try { 101 base.evaluate(); 102 } finally { 103 if (DBG) { 104 Log.d(TAG, "Clearing shell permissions"); 105 } 106 mUiAutomation.dropShellPermissionIdentity(); 107 adoptShellPermissions(permissionsBefore, "Restoring previous permissions: %s"); 108 } 109 } 110 }; 111 } // apply() 112 113 private void adoptShellPermissions(Set<String> permissionsSet, String messageTemplate) { 114 if (permissionsSet == null || permissionsSet.isEmpty()) { 115 return; 116 } 117 Log.d(TAG, String.format(messageTemplate, permissionsSet)); 118 String[] permissions = permissionsSet.stream().toArray(n -> new String[n]); 119 mUiAutomation.adoptShellPermissionIdentity(permissions); 120 } 121 122 private static void addPermissions(Set<String> permissions, EnsureHasPermission annotation) { 123 if (annotation == null) { 124 return; 125 } 126 for (String value : annotation.value()) { 127 permissions.add(value); 128 } 129 } 130 131 // NOTE: ideally rule should use com.android.bedstead.harrier.annotations.EnsureHasPermission 132 // instead, but that annotation requires adding HarrierCommon, which causes other issues in the 133 // tests 134 /** 135 * Lists the permissions that will be adopted by a test method or class. 136 * 137 * <p>When defined by both method and class (or even superclasses), it will merge the 138 * permissions defined by such annotations. 139 */ 140 @Retention(RUNTIME) 141 @Target({METHOD, TYPE}) 142 public @interface EnsureHasPermission { 143 144 /** 145 * List of permissions to be adopted by the test. 146 */ 147 String[] value(); 148 } 149 } 150