1 /*
2  * Copyright (C) 2018 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 com.android.compatibility.common.util;
18 
19 import android.app.UiAutomation;
20 
21 import androidx.test.InstrumentationRegistry;
22 
23 import java.util.function.BiFunction;
24 import java.util.function.Supplier;
25 
26 /**
27  * Provides utility methods to invoke system and privileged APIs as the shell user.
28  */
29 public class ShellIdentityUtils {
30 
31     /**
32      * Utility interface to invoke a method against the target object.
33      *
34      * @param <T> the type returned by the invoked method.
35      * @param <U> the type of the object against which the method is invoked.
36      */
37     public interface ShellPermissionMethodHelper<T, U> {
38         /**
39          * Invokes the method against the target object.
40          *
41          * @param targetObject the object against which the method should be invoked.
42          * @return the result of the invoked method.
43          */
callMethod(U targetObject)44         T callMethod(U targetObject);
45     }
46 
47     /**
48      * Utility interface to invoke a method against the target object.
49      *
50      * @param <U> the type of the object against which the method is invoked.
51      */
52     public interface ShellPermissionMethodHelperNoReturn<U> {
53         /**
54          * Invokes the method against the target object.
55          *
56          * @param targetObject the object against which the method should be invoked.
57          */
callMethod(U targetObject)58         void callMethod(U targetObject);
59     }
60 
61     /**
62      * Utility interface to invoke a method against the target object that may throw an Exception.
63      *
64      * @param <U> the type of the object against which the method is invoked.
65      */
66     public interface ShellPermissionThrowableMethodHelper<T, U, E extends Throwable> {
67         /**
68          * Invokes the method against the target object.
69          *
70          * @param targetObject the object against which the method should be invoked.
71          * @return the result of the target method.
72          */
callMethod(U targetObject)73         T callMethod(U targetObject) throws E;
74     }
75 
76     /**
77      * Utility interface to invoke a method against the target object that may throw an Exception.
78      *
79      * @param <U> the type of the object against which the method is invoked.
80      */
81     public interface ShellPermissionThrowableMethodHelperNoReturn<U, E extends Throwable> {
82         /**
83          * Invokes the method against the target object.
84          *
85          * @param targetObject the object against which the method should be invoked.
86          */
callMethod(U targetObject)87         void callMethod(U targetObject) throws E;
88     }
89 
90     /**
91      * Invokes the specified method on the targetObject as the shell user. The method can be invoked
92      * as follows:
93      *
94      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
95      *        (tm) -> tm.getDeviceId());}
96      */
invokeMethodWithShellPermissions(U targetObject, ShellPermissionMethodHelper<T, U> methodHelper)97     public static <T, U> T invokeMethodWithShellPermissions(U targetObject,
98             ShellPermissionMethodHelper<T, U> methodHelper) {
99         final UiAutomation uiAutomation =
100                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
101         try {
102             uiAutomation.adoptShellPermissionIdentity();
103             return methodHelper.callMethod(targetObject);
104         } finally {
105             uiAutomation.dropShellPermissionIdentity();
106         }
107     }
108 
109     /**
110      * Invokes the specified method on the targetObject as the shell user with only the subset of
111      * permissions specified. The method can be invoked as follows:
112      *
113      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
114      *        (tm) -> tm.getDeviceId(), "android.permission.READ_PHONE_STATE");}
115      */
invokeMethodWithShellPermissions(U targetObject, ShellPermissionMethodHelper<T, U> methodHelper, String... permissions)116     public static <T, U> T invokeMethodWithShellPermissions(U targetObject,
117             ShellPermissionMethodHelper<T, U> methodHelper, String... permissions) {
118         final UiAutomation uiAutomation =
119                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
120         try {
121             uiAutomation.adoptShellPermissionIdentity(permissions);
122             return methodHelper.callMethod(targetObject);
123         } finally {
124             uiAutomation.dropShellPermissionIdentity();
125         }
126     }
127 
128     /** A three argument {@link java.util.function.Function}. */
129     public interface TriFunction<T, U, V, R> {
apply(T t, U u, V v)130         R apply(T t, U u, V v);
131     }
132 
133     /** A four argument {@link java.util.function.Function}. */
134     public interface QuadFunction<T, U, V, W, R> {
apply(T t, U u, V v, W w)135         R apply(T t, U u, V v, W w);
136     }
137 
138     /**
139      * Invokes the specified method wht arg1 and arg2, as the shell user
140      * with only the subset of permissions specified. The method can be invoked as follows:
141      *
142      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(op, pers,
143      *        mTelephonyManager::setNetworkSelectionModeManual(on, p),
144      *        "android.permission.MODIFY_PHONE_STATE");}
145      */
invokeMethodWithShellPermissions(T arg1, U arg2, BiFunction<? super T, ? super U, ? extends R> methodHelper, String... permissions)146     public static <T, U, R> R invokeMethodWithShellPermissions(T arg1, U arg2,
147             BiFunction<? super T, ? super U, ? extends R>  methodHelper, String... permissions) {
148         final UiAutomation uiAutomation =
149                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
150         try {
151             uiAutomation.adoptShellPermissionIdentity(permissions);
152             return methodHelper.apply(arg1, arg2);
153         } finally {
154             uiAutomation.dropShellPermissionIdentity();
155         }
156     }
157 
158     /**
159      * Invokes the specified method with arg1, arg2 and arg3, as the shell user
160      * with only the subset of permissions specified. The method can be invoked as follows:
161      *
162      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(req, cb, exe,
163      *        mTelephonyManager::requestNetworkScan,
164      *        "android.permission.MODIFY_PHONE_STATE",
165      *        "android.permission.ACCESS_FINE_LOCATION");}
166      */
invokeMethodWithShellPermissions(T arg1, U arg2, V arg3, TriFunction<? super T, ? super U, ? super V, ? extends R> methodHelper, String... permissions)167     public static <T, U, V, R> R invokeMethodWithShellPermissions(T arg1, U arg2, V arg3,
168             TriFunction<? super T, ? super U, ? super V, ? extends R>  methodHelper,
169             String... permissions) {
170         final UiAutomation uiAutomation =
171                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
172         try {
173             uiAutomation.adoptShellPermissionIdentity(permissions);
174             return methodHelper.apply(arg1, arg2, arg3);
175         } finally {
176             uiAutomation.dropShellPermissionIdentity();
177         }
178     }
179 
180     /**
181      * Invokes the specified method with arg1, arg2, arg3 and arg4, as the shell
182      * user with only the subset of permissions specified. The method can be invoked as follows:
183      *
184      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(a, b, c, d,
185      *        mTelephonyManager::requestSomething,
186      *        "android.permission.MODIFY_PHONE_STATE",
187      *        "android.permission.ACCESS_FINE_LOCATION");}
188      */
invokeMethodWithShellPermissions(T arg1, U arg2, V arg3, W arg4, QuadFunction<? super T, ? super U, ? super V, ? super W, ? extends R> methodHelper, String... permissions)189     public static <T, U, V, W, R> R invokeMethodWithShellPermissions(T arg1, U arg2, V arg3, W arg4,
190             QuadFunction<? super T, ? super U, ? super V, ? super W, ? extends R>  methodHelper,
191             String... permissions) {
192         final UiAutomation uiAutomation =
193                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
194         try {
195             uiAutomation.adoptShellPermissionIdentity(permissions);
196             return methodHelper.apply(arg1, arg2, arg3, arg4);
197         } finally {
198             uiAutomation.dropShellPermissionIdentity();
199         }
200     }
201 
202     /**
203      * Invokes the specified method on the targetObject as the shell user with only the subset of
204      * permissions specified. The method can be invoked as follows:
205      *
206      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mRcsUceAdapter,
207      *        (m) -> RcsUceAdapter::getUcePublishState, ImsException.class,
208      *                     "android.permission.READ_PRIVILEGED_PHONE_STATE")}
209      */
invokeThrowableMethodWithShellPermissions( U targetObject, ShellPermissionThrowableMethodHelper<T, U, E> methodHelper, Class<E> clazz, String... permissions)210     public static <T, U, E extends Throwable> T invokeThrowableMethodWithShellPermissions(
211             U targetObject, ShellPermissionThrowableMethodHelper<T, U, E> methodHelper,
212             Class<E> clazz, String... permissions) throws E {
213         final UiAutomation uiAutomation =
214                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
215         try {
216             uiAutomation.adoptShellPermissionIdentity(permissions);
217             return methodHelper.callMethod(targetObject);
218         } finally {
219             uiAutomation.dropShellPermissionIdentity();
220         }
221     }
222 
223     /**
224      * Invokes the specified method on the targetObject as the shell user for only the permissions
225      * specified. The method can be invoked as follows:
226      *
227      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
228      *        (tm) -> tm.getDeviceId(), "android.permission.READ_PHONE_STATE");}
229      */
invokeMethodWithShellPermissionsNoReturn( U targetObject, ShellPermissionMethodHelperNoReturn<U> methodHelper, String... permissions)230     public static <U> void invokeMethodWithShellPermissionsNoReturn(
231             U targetObject, ShellPermissionMethodHelperNoReturn<U> methodHelper,
232             String... permissions) {
233         final UiAutomation uiAutomation =
234                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
235         try {
236             uiAutomation.adoptShellPermissionIdentity(permissions);
237             methodHelper.callMethod(targetObject);
238         } finally {
239             uiAutomation.dropShellPermissionIdentity();
240         }
241     }
242 
243     /**
244      * Invokes the specified throwable method on the targetObject as the shell user with only the
245      * subset of permissions specified specified. The method can be invoked as follows:
246      *
247      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mImsMmtelManager,
248      *        (m) -> m.isSupported(...), ImsException.class);}
249      */
invokeThrowableMethodWithShellPermissionsNoReturn( U targetObject, ShellPermissionThrowableMethodHelperNoReturn<U, E> methodHelper, Class<E> clazz)250     public static <U, E extends Throwable> void invokeThrowableMethodWithShellPermissionsNoReturn(
251             U targetObject, ShellPermissionThrowableMethodHelperNoReturn<U, E> methodHelper,
252             Class<E> clazz) throws E {
253         final UiAutomation uiAutomation =
254                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
255         try {
256             uiAutomation.adoptShellPermissionIdentity();
257             methodHelper.callMethod(targetObject);
258         } finally {
259             uiAutomation.dropShellPermissionIdentity();
260         }
261     }
262 
263     /**
264      * Invokes the specified throwable method on the targetObject as the shell user with only the
265      * subset of permissions specified specified. The method can be invoked as follows:
266      *
267      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mImsMmtelManager,
268      *        (m) -> m.isSupported(...), ImsException.class,
269      *        "android.permission.READ_PRIVILEGED_PHONE_STATE");}
270      */
invokeThrowableMethodWithShellPermissionsNoReturn( U targetObject, ShellPermissionThrowableMethodHelperNoReturn<U, E> methodHelper, Class<E> clazz, String... permissions)271     public static <U, E extends Throwable> void invokeThrowableMethodWithShellPermissionsNoReturn(
272             U targetObject, ShellPermissionThrowableMethodHelperNoReturn<U, E> methodHelper,
273             Class<E> clazz, String... permissions) throws E {
274         final UiAutomation uiAutomation =
275                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
276         try {
277             uiAutomation.adoptShellPermissionIdentity(permissions);
278             methodHelper.callMethod(targetObject);
279         } finally {
280             uiAutomation.dropShellPermissionIdentity();
281         }
282     }
283 
284 
285     /**
286      * Invokes the specified method on the targetObject as the shell user. The method can be invoked
287      * as follows:
288      *
289      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
290      *        (tm) -> tm.getDeviceId());}
291      */
invokeMethodWithShellPermissionsNoReturn( U targetObject, ShellPermissionMethodHelperNoReturn<U> methodHelper)292     public static <U> void invokeMethodWithShellPermissionsNoReturn(
293             U targetObject, ShellPermissionMethodHelperNoReturn<U> methodHelper) {
294         final UiAutomation uiAutomation =
295                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
296         try {
297             uiAutomation.adoptShellPermissionIdentity();
298             methodHelper.callMethod(targetObject);
299         } finally {
300             uiAutomation.dropShellPermissionIdentity();
301         }
302     }
303 
304     /**
305      * Utility interface to invoke a static method.
306      *
307      * @param <T> the type returned by the invoked method.
308      */
309     public interface StaticShellPermissionMethodHelper<T> {
310         /**
311          * Invokes the static method.
312          *
313          * @return the result of the invoked method.
314          */
callMethod()315         T callMethod();
316     }
317 
318     /**
319      * Invokes the specified static method as the shell user. This method can be invoked as follows:
320      *
321      * {@code ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial));}
322      */
invokeStaticMethodWithShellPermissions( StaticShellPermissionMethodHelper<T> methodHelper)323     public static <T> T invokeStaticMethodWithShellPermissions(
324             StaticShellPermissionMethodHelper<T> methodHelper) {
325         final UiAutomation uiAutomation =
326                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
327         try {
328             uiAutomation.adoptShellPermissionIdentity();
329             return methodHelper.callMethod();
330         } finally {
331             uiAutomation.dropShellPermissionIdentity();
332         }
333     }
334 
335     /**
336      * Drop the shell permission identity adopted by a previous call to
337      * {@link UiAutomation#adoptShellPermissionIdentity()}.
338      */
dropShellPermissionIdentity()339     public static void dropShellPermissionIdentity() {
340         final UiAutomation uiAutomation =
341                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
342 
343         uiAutomation.dropShellPermissionIdentity();
344     }
345 
346     /**
347      * Run an arbitrary piece of code while holding shell permissions.
348      *
349      * @param supplier an expression that performs the desired operation with shell permissions
350      * @param <T> the return type of the expression
351      * @return the return value of the expression
352      */
invokeWithShellPermissions(Supplier<T> supplier)353     public static <T> T invokeWithShellPermissions(Supplier<T> supplier) {
354         final UiAutomation uiAutomation =
355                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
356         try {
357             uiAutomation.adoptShellPermissionIdentity();
358             return supplier.get();
359         } finally {
360             uiAutomation.dropShellPermissionIdentity();
361         }
362     }
363 
364     /**
365      * Run an arbitrary piece of code while holding shell permissions.
366      *
367      * @param runnable an expression that performs the desired operation with shell permissions
368      * @return the return value of the expression
369      */
invokeWithShellPermissions(Runnable runnable)370     public static void invokeWithShellPermissions(Runnable runnable) {
371         final UiAutomation uiAutomation =
372                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
373         try {
374             uiAutomation.adoptShellPermissionIdentity();
375             runnable.run();
376         } finally {
377             uiAutomation.dropShellPermissionIdentity();
378         }
379     }
380 }
381