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