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.platform.app.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         UiAutomation uiAutomation = null;
100         //Retry multiple times for UIAutomation connection timed out fail
101         for (int i = 0; i < 5; i++) {
102             uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
103             if (uiAutomation != null) {
104                 break;
105             }
106         }
107         if (uiAutomation == null) {
108             throw new IllegalStateException("Could not get UiAutomation");
109         }
110         try {
111             uiAutomation.adoptShellPermissionIdentity();
112             return methodHelper.callMethod(targetObject);
113         } finally {
114             uiAutomation.dropShellPermissionIdentity();
115         }
116     }
117 
118     /**
119      * Invokes the specified method on the targetObject as the shell user with only the subset of
120      * permissions specified. The method can be invoked as follows:
121      *
122      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
123      *        (tm) -> tm.getDeviceId(), "android.permission.READ_PHONE_STATE");}
124      */
invokeMethodWithShellPermissions(U targetObject, ShellPermissionMethodHelper<T, U> methodHelper, String... permissions)125     public static <T, U> T invokeMethodWithShellPermissions(U targetObject,
126             ShellPermissionMethodHelper<T, U> methodHelper, String... permissions) {
127         final UiAutomation uiAutomation =
128                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
129         try {
130             uiAutomation.adoptShellPermissionIdentity(permissions);
131             return methodHelper.callMethod(targetObject);
132         } finally {
133             uiAutomation.dropShellPermissionIdentity();
134         }
135     }
136 
137     /** A three argument {@link java.util.function.Function}. */
138     public interface TriFunction<T, U, V, R> {
apply(T t, U u, V v)139         R apply(T t, U u, V v);
140     }
141 
142     /** A four argument {@link java.util.function.Function}. */
143     public interface QuadFunction<T, U, V, W, R> {
apply(T t, U u, V v, W w)144         R apply(T t, U u, V v, W w);
145     }
146 
147     /**
148      * Invokes the specified method wht arg1 and arg2, as the shell user
149      * with only the subset of permissions specified. The method can be invoked as follows:
150      *
151      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(op, pers,
152      *        mTelephonyManager::setNetworkSelectionModeManual(on, p),
153      *        "android.permission.MODIFY_PHONE_STATE");}
154      */
invokeMethodWithShellPermissions(T arg1, U arg2, BiFunction<? super T, ? super U, ? extends R> methodHelper, String... permissions)155     public static <T, U, R> R invokeMethodWithShellPermissions(T arg1, U arg2,
156             BiFunction<? super T, ? super U, ? extends R>  methodHelper, String... permissions) {
157         final UiAutomation uiAutomation =
158                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
159         try {
160             uiAutomation.adoptShellPermissionIdentity(permissions);
161             return methodHelper.apply(arg1, arg2);
162         } finally {
163             uiAutomation.dropShellPermissionIdentity();
164         }
165     }
166 
167     /**
168      * Invokes the specified method with arg1, arg2 and arg3, as the shell user
169      * with only the subset of permissions specified. The method can be invoked as follows:
170      *
171      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(req, cb, exe,
172      *        mTelephonyManager::requestNetworkScan,
173      *        "android.permission.MODIFY_PHONE_STATE",
174      *        "android.permission.ACCESS_FINE_LOCATION");}
175      */
invokeMethodWithShellPermissions(T arg1, U arg2, V arg3, TriFunction<? super T, ? super U, ? super V, ? extends R> methodHelper, String... permissions)176     public static <T, U, V, R> R invokeMethodWithShellPermissions(T arg1, U arg2, V arg3,
177             TriFunction<? super T, ? super U, ? super V, ? extends R>  methodHelper,
178             String... permissions) {
179         final UiAutomation uiAutomation =
180                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
181         try {
182             uiAutomation.adoptShellPermissionIdentity(permissions);
183             return methodHelper.apply(arg1, arg2, arg3);
184         } finally {
185             uiAutomation.dropShellPermissionIdentity();
186         }
187     }
188 
189     /**
190      * Invokes the specified method with arg1, arg2, arg3 and arg4, as the shell
191      * user with only the subset of permissions specified. The method can be invoked as follows:
192      *
193      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(a, b, c, d,
194      *        mTelephonyManager::requestSomething,
195      *        "android.permission.MODIFY_PHONE_STATE",
196      *        "android.permission.ACCESS_FINE_LOCATION");}
197      */
invokeMethodWithShellPermissions(T arg1, U arg2, V arg3, W arg4, QuadFunction<? super T, ? super U, ? super V, ? super W, ? extends R> methodHelper, String... permissions)198     public static <T, U, V, W, R> R invokeMethodWithShellPermissions(T arg1, U arg2, V arg3, W arg4,
199             QuadFunction<? super T, ? super U, ? super V, ? super W, ? extends R>  methodHelper,
200             String... permissions) {
201         final UiAutomation uiAutomation =
202                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
203         try {
204             uiAutomation.adoptShellPermissionIdentity(permissions);
205             return methodHelper.apply(arg1, arg2, arg3, arg4);
206         } finally {
207             uiAutomation.dropShellPermissionIdentity();
208         }
209     }
210 
211     /**
212      * Invokes the specified method on the targetObject as the shell user with only the subset of
213      * permissions specified. The method can be invoked as follows:
214      *
215      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mRcsUceAdapter,
216      *        (m) -> RcsUceAdapter::getUcePublishState, ImsException.class,
217      *                     "android.permission.READ_PRIVILEGED_PHONE_STATE")}
218      */
invokeThrowableMethodWithShellPermissions( U targetObject, ShellPermissionThrowableMethodHelper<T, U, E> methodHelper, Class<E> clazz, String... permissions)219     public static <T, U, E extends Throwable> T invokeThrowableMethodWithShellPermissions(
220             U targetObject, ShellPermissionThrowableMethodHelper<T, U, E> methodHelper,
221             Class<E> clazz, String... permissions) throws E {
222         final UiAutomation uiAutomation =
223                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
224         try {
225             uiAutomation.adoptShellPermissionIdentity(permissions);
226             return methodHelper.callMethod(targetObject);
227         } finally {
228             uiAutomation.dropShellPermissionIdentity();
229         }
230     }
231 
232     /**
233      * Invokes the specified method on the targetObject as the shell user for only the permissions
234      * specified. The method can be invoked as follows:
235      *
236      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
237      *        (tm) -> tm.getDeviceId(), "android.permission.READ_PHONE_STATE");}
238      */
invokeMethodWithShellPermissionsNoReturn( U targetObject, ShellPermissionMethodHelperNoReturn<U> methodHelper, String... permissions)239     public static <U> void invokeMethodWithShellPermissionsNoReturn(
240             U targetObject, ShellPermissionMethodHelperNoReturn<U> methodHelper,
241             String... permissions) {
242         final UiAutomation uiAutomation =
243                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
244         try {
245             uiAutomation.adoptShellPermissionIdentity(permissions);
246             methodHelper.callMethod(targetObject);
247         } finally {
248             uiAutomation.dropShellPermissionIdentity();
249         }
250     }
251 
252     /**
253      * Invokes the specified throwable method on the targetObject as the shell user with only the
254      * subset of permissions specified specified. The method can be invoked as follows:
255      *
256      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mImsMmtelManager,
257      *        (m) -> m.isSupported(...), ImsException.class);}
258      */
invokeThrowableMethodWithShellPermissionsNoReturn( U targetObject, ShellPermissionThrowableMethodHelperNoReturn<U, E> methodHelper, Class<E> clazz)259     public static <U, E extends Throwable> void invokeThrowableMethodWithShellPermissionsNoReturn(
260             U targetObject, ShellPermissionThrowableMethodHelperNoReturn<U, E> methodHelper,
261             Class<E> clazz) throws E {
262         final UiAutomation uiAutomation =
263                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
264         try {
265             uiAutomation.adoptShellPermissionIdentity();
266             methodHelper.callMethod(targetObject);
267         } finally {
268             uiAutomation.dropShellPermissionIdentity();
269         }
270     }
271 
272     /**
273      * Invokes the specified throwable method on the targetObject as the shell user with only the
274      * subset of permissions specified specified. The method can be invoked as follows:
275      *
276      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mImsMmtelManager,
277      *        (m) -> m.isSupported(...), ImsException.class,
278      *        "android.permission.READ_PRIVILEGED_PHONE_STATE");}
279      */
invokeThrowableMethodWithShellPermissionsNoReturn( U targetObject, ShellPermissionThrowableMethodHelperNoReturn<U, E> methodHelper, Class<E> clazz, String... permissions)280     public static <U, E extends Throwable> void invokeThrowableMethodWithShellPermissionsNoReturn(
281             U targetObject, ShellPermissionThrowableMethodHelperNoReturn<U, E> methodHelper,
282             Class<E> clazz, String... permissions) throws E {
283         final UiAutomation uiAutomation =
284                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
285         try {
286             uiAutomation.adoptShellPermissionIdentity(permissions);
287             methodHelper.callMethod(targetObject);
288         } finally {
289             uiAutomation.dropShellPermissionIdentity();
290         }
291     }
292 
293 
294     /**
295      * Invokes the specified method on the targetObject as the shell user. The method can be invoked
296      * as follows:
297      *
298      * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
299      *        (tm) -> tm.getDeviceId());}
300      */
invokeMethodWithShellPermissionsNoReturn( U targetObject, ShellPermissionMethodHelperNoReturn<U> methodHelper)301     public static <U> void invokeMethodWithShellPermissionsNoReturn(
302             U targetObject, ShellPermissionMethodHelperNoReturn<U> methodHelper) {
303         final UiAutomation uiAutomation =
304                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
305         try {
306             uiAutomation.adoptShellPermissionIdentity();
307             methodHelper.callMethod(targetObject);
308         } finally {
309             uiAutomation.dropShellPermissionIdentity();
310         }
311     }
312 
313     /**
314      * Utility interface to invoke a static method.
315      *
316      * @param <T> the type returned by the invoked method.
317      */
318     public interface StaticShellPermissionMethodHelper<T> {
319         /**
320          * Invokes the static method.
321          *
322          * @return the result of the invoked method.
323          */
callMethod()324         T callMethod();
325     }
326 
327     /**
328      * Invokes the specified static method as the shell user. This method can be invoked as follows:
329      *
330      * {@code ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial));}
331      */
invokeStaticMethodWithShellPermissions( StaticShellPermissionMethodHelper<T> methodHelper)332     public static <T> T invokeStaticMethodWithShellPermissions(
333             StaticShellPermissionMethodHelper<T> methodHelper) {
334         final UiAutomation uiAutomation =
335                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
336         try {
337             uiAutomation.adoptShellPermissionIdentity();
338             return methodHelper.callMethod();
339         } finally {
340             uiAutomation.dropShellPermissionIdentity();
341         }
342     }
343 
344     /**
345      * Drop the shell permission identity adopted by a previous call to
346      * {@link UiAutomation#adoptShellPermissionIdentity()}.
347      */
dropShellPermissionIdentity()348     public static void dropShellPermissionIdentity() {
349         final UiAutomation uiAutomation =
350                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
351 
352         uiAutomation.dropShellPermissionIdentity();
353     }
354 
355     /**
356      * Run an arbitrary piece of code while holding shell permissions.
357      *
358      * @param supplier an expression that performs the desired operation with shell permissions
359      * @param <T> the return type of the expression
360      * @return the return value of the expression
361      */
invokeWithShellPermissions(Supplier<T> supplier)362     public static <T> T invokeWithShellPermissions(Supplier<T> supplier) {
363         final UiAutomation uiAutomation =
364                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
365         try {
366             uiAutomation.adoptShellPermissionIdentity();
367             return supplier.get();
368         } finally {
369             uiAutomation.dropShellPermissionIdentity();
370         }
371     }
372 
373     /**
374      * Run an arbitrary piece of code while holding shell permissions.
375      *
376      * @param runnable an expression that performs the desired operation with shell permissions
377      * @return the return value of the expression
378      */
invokeWithShellPermissions(Runnable runnable)379     public static void invokeWithShellPermissions(Runnable runnable) {
380         final UiAutomation uiAutomation =
381                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
382         try {
383             uiAutomation.adoptShellPermissionIdentity();
384             runnable.run();
385         } finally {
386             uiAutomation.dropShellPermissionIdentity();
387         }
388     }
389 }
390