1 /* 2 * Copyright (C) 2020 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.cts.statsdatom.appops; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import android.cts.statsdatom.lib.AtomTestUtils; 23 import android.cts.statsdatom.lib.ConfigUtils; 24 import android.cts.statsdatom.lib.DeviceUtils; 25 import android.cts.statsdatom.lib.ReportUtils; 26 27 import com.android.os.AtomsProto; 28 import com.android.tradefed.build.IBuildInfo; 29 import com.android.tradefed.testtype.DeviceTestCase; 30 import com.android.tradefed.testtype.IBuildReceiver; 31 import com.android.tradefed.util.RunUtil; 32 33 import com.google.protobuf.Descriptors; 34 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.HashSet; 38 import java.util.Map; 39 import java.util.Set; 40 41 public class AppOpsTests extends DeviceTestCase implements IBuildReceiver { 42 private static final int NUM_APP_OPS = AtomsProto.AttributedAppOps.getDefaultInstance().getOp(). 43 getDescriptorForType().getValues().size() - 1; 44 45 private static final int APP_OP_RECORD_AUDIO = 27; 46 private static final int APP_OP_RECORD_AUDIO_HOTWORD = 102; 47 private static final int APP_OP_ACCESS_RESTRICTED_SETTINGS = 119; 48 49 private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; 50 private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only"; 51 52 /** 53 * Some ops are only available to specific dynamic uids and are otherwise transformed to less 54 * privileged ops. For example, RECORD_AUDIO_HOTWORD is downgraded to RECORD_AUDIO. This stores 55 * a mapping from an op to the op it can be transformed from. 56 */ 57 private final Map<Integer, Integer> mTransformedFromOp = new HashMap<>(); 58 59 private IBuildInfo mCtsBuild; 60 61 @Override setUp()62 protected void setUp() throws Exception { 63 super.setUp(); 64 65 mTransformedFromOp.clear(); 66 // The hotword op is allowed to all UIDs on some devices. 67 boolean hotwordDetectionServiceRequired = Boolean.parseBoolean( 68 getDevice().executeShellCommand( 69 "getprop ro.hotword.detection_service_required").trim()); 70 if (!(DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE) 71 || DeviceUtils.hasFeature(getDevice(), FEATURE_LEANBACK_ONLY) 72 || !hotwordDetectionServiceRequired)) { 73 mTransformedFromOp.put(APP_OP_RECORD_AUDIO, APP_OP_RECORD_AUDIO_HOTWORD); 74 } 75 76 assertThat(mCtsBuild).isNotNull(); 77 ConfigUtils.removeConfig(getDevice()); 78 ReportUtils.clearReports(getDevice()); 79 DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild); 80 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 81 } 82 83 @Override tearDown()84 protected void tearDown() throws Exception { 85 ConfigUtils.removeConfig(getDevice()); 86 ReportUtils.clearReports(getDevice()); 87 DeviceUtils.uninstallStatsdTestApp(getDevice()); 88 super.tearDown(); 89 } 90 91 @Override setBuild(IBuildInfo buildInfo)92 public void setBuild(IBuildInfo buildInfo) { 93 mCtsBuild = buildInfo; 94 } 95 testAppOps()96 public void testAppOps() throws Exception { 97 // Set up what to collect 98 ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, 99 AtomsProto.Atom.APP_OPS_FIELD_NUMBER); 100 101 DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testAppOps"); 102 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT); 103 104 // Pull a report 105 AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice()); 106 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT); 107 108 ArrayList<Integer> expectedOps = new ArrayList<>(); 109 Set<Integer> transformedOps = new HashSet<>(mTransformedFromOp.values()); 110 for (int i = 0; i < NUM_APP_OPS; i++) { 111 // Ignore access restricted setting as it cannot be read by normal app. 112 if (i == APP_OP_ACCESS_RESTRICTED_SETTINGS) { 113 continue; 114 } 115 if (!transformedOps.contains(i)) { 116 expectedOps.add(i); 117 } 118 } 119 120 for (Descriptors.EnumValueDescriptor valueDescriptor : 121 AtomsProto.AttributedAppOps.getDefaultInstance().getOp().getDescriptorForType() 122 .getValues()) { 123 if (valueDescriptor.getOptions().hasDeprecated()) { 124 // Deprecated app op, remove from list of expected ones. 125 expectedOps.remove(expectedOps.indexOf(valueDescriptor.getNumber())); 126 } 127 } 128 for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) { 129 AtomsProto.AppOps appOps = atom.getAppOps(); 130 int opCode = appOps.getOpId().getNumber(); 131 if (appOps.getPackageName().equals(DeviceUtils.STATSD_ATOM_TEST_PKG)) { 132 if (opCode == -1) { 133 continue; 134 } 135 long totalNoted = appOps.getTrustedForegroundGrantedCount() 136 + appOps.getTrustedBackgroundGrantedCount() 137 + appOps.getTrustedForegroundRejectedCount() 138 + appOps.getTrustedBackgroundRejectedCount(); 139 int expectedNoted = 1 + computeExpectedTransformedNoted(opCode); 140 assertWithMessage("NoteOp count mismatches, op code:" + opCode) 141 .that(totalNoted).isEqualTo(expectedNoted); 142 assertWithMessage("Unexpected Op reported") 143 .that(expectedOps).contains(opCode); 144 expectedOps.remove(expectedOps.indexOf(appOps.getOpId().getNumber())); 145 } 146 } 147 assertWithMessage("Noted ops are missing in report.").that(expectedOps).isEmpty(); 148 } 149 computeExpectedTransformedNoted(int op)150 private int computeExpectedTransformedNoted(int op) { 151 if (!mTransformedFromOp.containsKey(op)) { 152 return 0; 153 } 154 return 1; 155 } 156 }