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 class AppOpsUtils {
34 
35     /**
36      * Resets a package's app ops configuration to the device default. See AppOpsManager for the
37      * default op settings.
38      *
39      * <p>
40      * It's recommended to call this in setUp() and tearDown() of your test so the test starts and
41      * ends with a reproducible default state, and so doesn't affect other tests.
42      *
43      * <p>
44      * Some app ops are configured to be non-resettable, which means that the state of these will
45      * not be reset even when calling this method.
46      */
reset(String packageName)47     public static String reset(String packageName) throws IOException {
48         return runCommand("appops reset " + packageName);
49     }
50 
51     /**
52      * Sets the app op mode (e.g. allowed, denied) for a single package and operation.
53      */
setOpMode(String packageName, String opStr, int mode)54     public static String setOpMode(String packageName, String opStr, int mode)
55             throws IOException {
56         String modeStr;
57         switch (mode) {
58             case MODE_ALLOWED:
59                 modeStr = "allow";
60                 break;
61             case MODE_ERRORED:
62                 modeStr = "deny";
63                 break;
64             case MODE_IGNORED:
65                 modeStr = "ignore";
66                 break;
67             case MODE_DEFAULT:
68                 modeStr = "default";
69                 break;
70             default:
71                 throw new IllegalArgumentException("Unexpected app op type");
72         }
73         String command = "appops set " + packageName + " " + opStr + " " + modeStr;
74         return runCommand(command);
75     }
76 
77     /**
78      * Get the app op mode (e.g. MODE_ALLOWED, MODE_DEFAULT) for a single package and operation.
79      */
getOpMode(String packageName, String opStr)80     public static int getOpMode(String packageName, String opStr)
81             throws IOException {
82         String opState = getOpState(packageName, opStr);
83         if (opState.contains(" allow")) {
84             return MODE_ALLOWED;
85         } else if (opState.contains(" deny")) {
86             return MODE_ERRORED;
87         } else if (opState.contains(" ignore")) {
88             return MODE_IGNORED;
89         } else if (opState.contains(" default")) {
90             return MODE_DEFAULT;
91         } else {
92             throw new IllegalStateException("Unexpected app op mode returned " + opState);
93         }
94     }
95 
96     /**
97      * Returns whether an allowed operation has been logged by the AppOpsManager for a
98      * package. Operations are noted when the app attempts to perform them and calls e.g.
99      * {@link AppOpsManager#noteOperation}.
100      *
101      * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
102      */
allowedOperationLogged(String packageName, String opStr)103     public static boolean allowedOperationLogged(String packageName, String opStr)
104             throws IOException {
105         return getOpState(packageName, opStr).contains(" time=");
106     }
107 
108     /**
109      * Returns whether a rejected operation has been logged by the AppOpsManager for a
110      * package. Operations are noted when the app attempts to perform them and calls e.g.
111      * {@link AppOpsManager#noteOperation}.
112      *
113      * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
114      */
rejectedOperationLogged(String packageName, String opStr)115     public static boolean rejectedOperationLogged(String packageName, String opStr)
116             throws IOException {
117         return getOpState(packageName, opStr).contains(" rejectTime=");
118     }
119 
120     /**
121      * Returns the app op state for a package. Includes information on when the operation was last
122      * attempted to be performed by the package.
123      *
124      * Format: "SEND_SMS: allow; time=+23h12m54s980ms ago; rejectTime=+1h10m23s180ms"
125      */
getOpState(String packageName, String opStr)126     private static String getOpState(String packageName, String opStr) throws IOException {
127         return runCommand("appops get " + packageName + " " + opStr);
128     }
129 
runCommand(String command)130     private static String runCommand(String command) throws IOException {
131         return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
132     }
133 }
134