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