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