1 /* 2 * Copyright (C) 2018 Google Inc. 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 com.android.compatibility.common.util; 18 19 import static android.app.AppOpsManager.MODE_ALLOWED; 20 import static android.app.AppOpsManager.MODE_DEFAULT; 21 import static android.app.AppOpsManager.MODE_ERRORED; 22 import static android.app.AppOpsManager.MODE_IGNORED; 23 24 import android.app.AppOpsManager; 25 26 import androidx.test.InstrumentationRegistry; 27 28 import java.io.IOException; 29 30 /** 31 * Utilities for controlling App Ops settings, and testing whether ops are logged. 32 */ 33 public final class AppOpsUtils { 34 35 // TODO(b/272376728): make a instance objects instead 36 private static final UserHelper sUserHelper = new UserHelper( 37 InstrumentationRegistry.getTargetContext()); 38 39 /** 40 * Resets a package's app ops configuration to the device default. See AppOpsManager for the 41 * default op settings. 42 * 43 * <p> 44 * It's recommended to call this in setUp() and tearDown() of your test so the test starts and 45 * ends with a reproducible default state, and so doesn't affect other tests. 46 * 47 * <p> 48 * Some app ops are configured to be non-resettable, which means that the state of these will 49 * not be reset even when calling this method. 50 */ reset(String packageName)51 public static String reset(String packageName) throws IOException { 52 return runCommand(appops("reset") + packageName); 53 } 54 55 /** 56 * Sets the app op mode (e.g. allowed, denied) for a single package and operation. 57 */ setOpMode(String packageName, String opStr, int mode)58 public static String setOpMode(String packageName, String opStr, int mode) 59 throws IOException { 60 String modeStr; 61 switch (mode) { 62 case MODE_ALLOWED: 63 modeStr = "allow"; 64 break; 65 case MODE_ERRORED: 66 modeStr = "deny"; 67 break; 68 case MODE_IGNORED: 69 modeStr = "ignore"; 70 break; 71 case MODE_DEFAULT: 72 modeStr = "default"; 73 break; 74 default: 75 throw new IllegalArgumentException("Unexpected app op type"); 76 } 77 String command = appops("set") + packageName + " " + opStr + " " + modeStr; 78 return runCommand(command); 79 } 80 81 /** 82 * Sets the app op mode for a given uid. 83 */ setUidMode(int uid, String opStr, int mode)84 public static void setUidMode(int uid, String opStr, int mode) { 85 final AppOpsManager aom = InstrumentationRegistry.getTargetContext().getSystemService( 86 AppOpsManager.class); 87 SystemUtil.runWithShellPermissionIdentity(() -> aom.setUidMode(opStr, uid, mode)); 88 } 89 90 /** 91 * Get the app op mode (e.g. MODE_ALLOWED, MODE_DEFAULT) for a single package and operation. 92 */ getOpMode(String packageName, String opStr)93 public static int getOpMode(String packageName, String opStr) 94 throws IOException { 95 String opState = getOpState(packageName, opStr); 96 if (opState.contains(" allow")) { 97 return MODE_ALLOWED; 98 } else if (opState.contains(" deny")) { 99 return MODE_ERRORED; 100 } else if (opState.contains(" ignore")) { 101 return MODE_IGNORED; 102 } else if (opState.contains(" default")) { 103 return MODE_DEFAULT; 104 } else { 105 throw new IllegalStateException("Unexpected app op mode returned " + opState); 106 } 107 } 108 109 /** 110 * Returns whether an allowed operation has been logged by the AppOpsManager for a 111 * package. Operations are noted when the app attempts to perform them and calls e.g. 112 * {@link AppOpsManager#noteOperation}. 113 * 114 * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS). 115 */ allowedOperationLogged(String packageName, String opStr)116 public static boolean allowedOperationLogged(String packageName, String opStr) 117 throws IOException { 118 return getOpState(packageName, opStr).contains(" time="); 119 } 120 121 /** 122 * Returns whether a rejected operation has been logged by the AppOpsManager for a 123 * package. Operations are noted when the app attempts to perform them and calls e.g. 124 * {@link AppOpsManager#noteOperation}. 125 * 126 * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS). 127 */ rejectedOperationLogged(String packageName, String opStr)128 public static boolean rejectedOperationLogged(String packageName, String opStr) 129 throws IOException { 130 return getOpState(packageName, opStr).contains(" rejectTime="); 131 } 132 133 /** 134 * Returns the app op state for a package. Includes information on when the operation was last 135 * attempted to be performed by the package. 136 * 137 * Format: "SEND_SMS: allow; time=+23h12m54s980ms ago; rejectTime=+1h10m23s180ms" 138 */ getOpState(String packageName, String opStr)139 private static String getOpState(String packageName, String opStr) throws IOException { 140 return runCommand(appops("get") + packageName + " " + opStr); 141 } 142 runCommand(String command)143 private static String runCommand(String command) throws IOException { 144 return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command); 145 } 146 appops(String cmd)147 private static String appops(String cmd) { 148 return sUserHelper.getAppopsCmd(cmd); 149 } 150 } 151