1 /* 2 * Copyright (C) 2024 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.server.pm.permission; 18 19 import static android.os.Process.INVALID_UID; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.UserIdInt; 24 import android.app.AppOpsManager; 25 import android.app.AppOpsManager.AttributionFlags; 26 import android.app.AppOpsManagerInternal.CheckOpsDelegate; 27 import android.app.SyncNotedAppOp; 28 import android.content.AttributionSource; 29 import android.content.pm.PackageManager; 30 import android.content.pm.PackageManagerInternal; 31 import android.os.Binder; 32 import android.os.IBinder; 33 import android.os.Process; 34 import android.os.UserHandle; 35 import android.text.TextUtils; 36 import android.util.ArrayMap; 37 import android.util.SparseArray; 38 39 import com.android.internal.util.ArrayUtils; 40 import com.android.internal.util.function.DodecFunction; 41 import com.android.internal.util.function.HexConsumer; 42 import com.android.internal.util.function.HexFunction; 43 import com.android.internal.util.function.OctFunction; 44 import com.android.internal.util.function.QuadFunction; 45 import com.android.internal.util.function.TriFunction; 46 import com.android.internal.util.function.UndecFunction; 47 import com.android.server.LocalServices; 48 import com.android.server.pm.permission.PermissionManagerServiceInternal.CheckPermissionDelegate; 49 50 import java.util.List; 51 import java.util.Map; 52 53 /** 54 * Interface to intercept incoming parameters and outgoing results to permission and appop checks 55 */ 56 public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDelegate { 57 58 /** 59 * Assigns the package whose permissions are delegated the state of Shell's permissions. 60 * 61 * @param delegateUid the UID whose permissions are delegated to shell 62 * @param packageName the name of the package whose permissions are delegated to shell 63 * @param permissions the set of permissions to delegate to shell. If null then all 64 * permission will be delegated 65 */ setShellPermissionDelegate(int delegateUid, @NonNull String packageName, @Nullable String[] permissions)66 void setShellPermissionDelegate(int delegateUid, @NonNull String packageName, 67 @Nullable String[] permissions); 68 69 /** 70 * Removes the assigned Shell permission delegate. 71 */ removeShellPermissionDelegate()72 void removeShellPermissionDelegate(); 73 74 /** 75 * @return a list of permissions delegated to Shell's permission state 76 */ 77 @NonNull getDelegatedPermissionNames()78 List<String> getDelegatedPermissionNames(); 79 80 /** 81 * @return whether there exists a Shell permission delegate 82 */ hasShellPermissionDelegate()83 boolean hasShellPermissionDelegate(); 84 85 /** 86 * @param uid the UID to check 87 * @return whether the UID's permissions are delegated to Shell's and the owner of overrides 88 */ isDelegateAndOwnerUid(int uid)89 boolean isDelegateAndOwnerUid(int uid); 90 91 /** 92 * @param uid the UID to check 93 * @param packageName the package to check 94 * @return whether the UID and package combination's permissions are delegated to Shell's 95 * permissions 96 */ isDelegatePackage(int uid, @NonNull String packageName)97 boolean isDelegatePackage(int uid, @NonNull String packageName); 98 99 /** 100 * Adds permission to be overridden to the given state. 101 * 102 * @param ownerUid the UID of the app who assigned the permission override 103 * @param uid The UID of the app whose permission will be overridden 104 * @param permission The permission whose state will be overridden 105 * @param result The state to override the permission to 106 */ addOverridePermissionState(int ownerUid, int uid, @NonNull String permission, int result)107 void addOverridePermissionState(int ownerUid, int uid, @NonNull String permission, 108 int result); 109 110 /** 111 * Removes overridden permission. UiAutomation must be connected to root user. 112 * 113 * @param uid The UID of the app whose permission is overridden 114 * @param permission The permission whose state will no longer be overridden 115 * 116 * @hide 117 */ removeOverridePermissionState(int uid, @NonNull String permission)118 void removeOverridePermissionState(int uid, @NonNull String permission); 119 120 /** 121 * Clears all overridden permissions for the given UID. 122 * 123 * @param uid The UID of the app whose permissions will no longer be overridden 124 */ clearOverridePermissionStates(int uid)125 void clearOverridePermissionStates(int uid); 126 127 /** 128 * Clears all overridden permissions on the device. 129 */ clearAllOverridePermissionStates()130 void clearAllOverridePermissionStates(); 131 132 /** 133 * @return whether there exists any permission overrides 134 */ hasOverriddenPermissions()135 boolean hasOverriddenPermissions(); 136 137 /** 138 * @return whether there exists permissions delegated to Shell's permissions or overridden 139 */ hasDelegateOrOverrides()140 boolean hasDelegateOrOverrides(); 141 142 class AccessCheckDelegateImpl implements AccessCheckDelegate { 143 public static final String SHELL_PKG = "com.android.shell"; 144 private int mDelegateAndOwnerUid = INVALID_UID; 145 @Nullable 146 private String mDelegatePackage; 147 @Nullable 148 private String[] mDelegatePermissions; 149 boolean mDelegateAllPermissions; 150 @Nullable 151 private SparseArray<ArrayMap<String, Integer>> mOverridePermissionStates; 152 153 @Override setShellPermissionDelegate(int uid, @NonNull String packageName, @Nullable String[] permissions)154 public void setShellPermissionDelegate(int uid, @NonNull String packageName, 155 @Nullable String[] permissions) { 156 mDelegateAndOwnerUid = uid; 157 mDelegatePackage = packageName; 158 mDelegatePermissions = permissions; 159 mDelegateAllPermissions = permissions == null; 160 PackageManager.invalidatePackageInfoCache(); 161 } 162 163 @Override removeShellPermissionDelegate()164 public void removeShellPermissionDelegate() { 165 mDelegatePackage = null; 166 mDelegatePermissions = null; 167 mDelegateAllPermissions = false; 168 PackageManager.invalidatePackageInfoCache(); 169 } 170 171 @Override addOverridePermissionState(int ownerUid, int uid, @NonNull String permission, int state)172 public void addOverridePermissionState(int ownerUid, int uid, @NonNull String permission, 173 int state) { 174 if (mOverridePermissionStates == null) { 175 mDelegateAndOwnerUid = ownerUid; 176 mOverridePermissionStates = new SparseArray<>(); 177 } 178 179 int uidIdx = mOverridePermissionStates.indexOfKey(uid); 180 ArrayMap<String, Integer> perUidOverrides; 181 if (uidIdx < 0) { 182 perUidOverrides = new ArrayMap<>(); 183 mOverridePermissionStates.put(uid, perUidOverrides); 184 } else { 185 perUidOverrides = mOverridePermissionStates.valueAt(uidIdx); 186 } 187 188 perUidOverrides.put(permission, state); 189 PackageManager.invalidatePackageInfoCache(); 190 } 191 192 @Override removeOverridePermissionState(int uid, @NonNull String permission)193 public void removeOverridePermissionState(int uid, @NonNull String permission) { 194 if (mOverridePermissionStates == null) { 195 return; 196 } 197 198 ArrayMap<String, Integer> perUidOverrides = mOverridePermissionStates.get(uid); 199 200 if (perUidOverrides == null) { 201 return; 202 } 203 204 perUidOverrides.remove(permission); 205 PackageManager.invalidatePackageInfoCache(); 206 207 if (perUidOverrides.isEmpty()) { 208 mOverridePermissionStates.remove(uid); 209 } 210 if (mOverridePermissionStates.size() == 0) { 211 mOverridePermissionStates = null; 212 } 213 } 214 215 @Override clearOverridePermissionStates(int uid)216 public void clearOverridePermissionStates(int uid) { 217 if (mOverridePermissionStates == null) { 218 return; 219 } 220 221 mOverridePermissionStates.remove(uid); 222 PackageManager.invalidatePackageInfoCache(); 223 224 if (mOverridePermissionStates.size() == 0) { 225 mOverridePermissionStates = null; 226 } 227 } 228 229 @Override clearAllOverridePermissionStates()230 public void clearAllOverridePermissionStates() { 231 mOverridePermissionStates = null; 232 PackageManager.invalidatePackageInfoCache(); 233 } 234 235 @Override getDelegatedPermissionNames()236 public List<String> getDelegatedPermissionNames() { 237 return mDelegatePermissions == null ? null : List.of(mDelegatePermissions); 238 } 239 240 @Override hasShellPermissionDelegate()241 public boolean hasShellPermissionDelegate() { 242 return mDelegateAllPermissions || mDelegatePermissions != null; 243 } 244 245 @Override isDelegatePackage(int uid, @NonNull String packageName)246 public boolean isDelegatePackage(int uid, @NonNull String packageName) { 247 return mDelegateAndOwnerUid == uid && TextUtils.equals(mDelegatePackage, packageName); 248 } 249 250 @Override hasOverriddenPermissions()251 public boolean hasOverriddenPermissions() { 252 return mOverridePermissionStates != null; 253 } 254 255 @Override isDelegateAndOwnerUid(int uid)256 public boolean isDelegateAndOwnerUid(int uid) { 257 return uid == mDelegateAndOwnerUid; 258 } 259 260 @Override hasDelegateOrOverrides()261 public boolean hasDelegateOrOverrides() { 262 return hasShellPermissionDelegate() || hasOverriddenPermissions(); 263 } 264 265 @Override checkPermission(@onNull String packageName, @NonNull String permissionName, @NonNull String persistentDeviceId, @UserIdInt int userId, @NonNull QuadFunction<String, String, String, Integer, Integer> superImpl)266 public int checkPermission(@NonNull String packageName, @NonNull String permissionName, 267 @NonNull String persistentDeviceId, @UserIdInt int userId, 268 @NonNull QuadFunction<String, String, String, Integer, Integer> superImpl) { 269 if (TextUtils.equals(mDelegatePackage, packageName) && !SHELL_PKG.equals(packageName)) { 270 if (isDelegatePermission(permissionName)) { 271 final long identity = Binder.clearCallingIdentity(); 272 try { 273 return checkPermission(SHELL_PKG, permissionName, persistentDeviceId, 274 userId, superImpl); 275 } finally { 276 Binder.restoreCallingIdentity(identity); 277 } 278 } 279 } 280 if (mOverridePermissionStates != null) { 281 int uid = LocalServices.getService(PackageManagerInternal.class) 282 .getPackageUid(packageName, 0, userId); 283 if (uid >= 0) { 284 Map<String, Integer> permissionGrants = mOverridePermissionStates.get(uid); 285 if (permissionGrants != null && permissionGrants.containsKey(permissionName)) { 286 return permissionGrants.get(permissionName); 287 } 288 } 289 } 290 return superImpl.apply(packageName, permissionName, persistentDeviceId, userId); 291 } 292 293 @Override checkUidPermission(int uid, @NonNull String permissionName, @NonNull String persistentDeviceId, @NonNull TriFunction<Integer, String, String, Integer> superImpl)294 public int checkUidPermission(int uid, @NonNull String permissionName, 295 @NonNull String persistentDeviceId, 296 @NonNull TriFunction<Integer, String, String, Integer> superImpl) { 297 if (uid == mDelegateAndOwnerUid && uid != Process.SHELL_UID) { 298 if (isDelegatePermission(permissionName)) { 299 final long identity = Binder.clearCallingIdentity(); 300 try { 301 return checkUidPermission(Process.SHELL_UID, permissionName, 302 persistentDeviceId, superImpl); 303 } finally { 304 Binder.restoreCallingIdentity(identity); 305 } 306 } 307 } 308 if (mOverridePermissionStates != null) { 309 Map<String, Integer> permissionGrants = mOverridePermissionStates.get(uid); 310 if (permissionGrants != null && permissionGrants.containsKey(permissionName)) { 311 return permissionGrants.get(permissionName); 312 } 313 } 314 return superImpl.apply(uid, permissionName, persistentDeviceId); 315 } 316 317 @Override checkOperation(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, boolean raw, @NonNull HexFunction<Integer, Integer, String, String, Integer, Boolean, Integer> superImpl)318 public int checkOperation(int code, int uid, @Nullable String packageName, 319 @Nullable String attributionTag, int virtualDeviceId, boolean raw, 320 @NonNull HexFunction<Integer, Integer, String, String, Integer, Boolean, Integer> 321 superImpl) { 322 if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) { 323 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid), 324 Process.SHELL_UID); 325 final long identity = Binder.clearCallingIdentity(); 326 try { 327 return superImpl.apply(code, shellUid, SHELL_PKG, null, virtualDeviceId, raw); 328 } finally { 329 Binder.restoreCallingIdentity(identity); 330 } 331 } 332 return superImpl.apply(code, uid, packageName, attributionTag, virtualDeviceId, raw); 333 } 334 335 @Override checkAudioOperation(int code, int usage, int uid, @Nullable String packageName, @NonNull QuadFunction<Integer, Integer, Integer, String, Integer> superImpl)336 public int checkAudioOperation(int code, int usage, int uid, @Nullable String packageName, 337 @NonNull QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) { 338 if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) { 339 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid), 340 Process.SHELL_UID); 341 final long identity = Binder.clearCallingIdentity(); 342 try { 343 return superImpl.apply(code, usage, shellUid, SHELL_PKG); 344 } finally { 345 Binder.restoreCallingIdentity(identity); 346 } 347 } 348 return superImpl.apply(code, usage, uid, packageName); 349 } 350 351 @Override noteOperation(int code, int uid, @Nullable String packageName, @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String, Boolean, SyncNotedAppOp> superImpl)352 public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName, 353 @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, 354 @Nullable String message, boolean shouldCollectMessage, 355 @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String, 356 Boolean, SyncNotedAppOp> superImpl) { 357 if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) { 358 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid), 359 Process.SHELL_UID); 360 final long identity = Binder.clearCallingIdentity(); 361 try { 362 return superImpl.apply(code, shellUid, SHELL_PKG, featureId, virtualDeviceId, 363 shouldCollectAsyncNotedOp, message, shouldCollectMessage); 364 } finally { 365 Binder.restoreCallingIdentity(identity); 366 } 367 } 368 return superImpl.apply(code, uid, packageName, featureId, virtualDeviceId, 369 shouldCollectAsyncNotedOp, message, shouldCollectMessage); 370 } 371 372 @Override noteProxyOperation(int code, @NonNull AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, boolean skiProxyOperation, @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean, Boolean, SyncNotedAppOp> superImpl)373 public SyncNotedAppOp noteProxyOperation(int code, 374 @NonNull AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp, 375 @Nullable String message, boolean shouldCollectMessage, boolean skiProxyOperation, 376 @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean, 377 Boolean, SyncNotedAppOp> superImpl) { 378 if (attributionSource.getUid() == mDelegateAndOwnerUid && isDelegateOp(code)) { 379 final int shellUid = UserHandle.getUid( 380 UserHandle.getUserId(attributionSource.getUid()), Process.SHELL_UID); 381 final long identity = Binder.clearCallingIdentity(); 382 try { 383 return superImpl.apply(code, 384 new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG, 385 attributionSource.getAttributionTag(), 386 attributionSource.getToken(), /*renouncedPermissions*/ null, 387 attributionSource.getDeviceId(), attributionSource.getNext()), 388 shouldCollectAsyncNotedOp, message, shouldCollectMessage, 389 skiProxyOperation); 390 } finally { 391 Binder.restoreCallingIdentity(identity); 392 } 393 } 394 return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp, 395 message, shouldCollectMessage, skiProxyOperation); 396 } 397 398 @Override startOperation(@onNull IBinder token, int code, int uid, @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, int attributionChainId, @NonNull DodecFunction<IBinder, Integer, Integer, String, String, Integer, Boolean, Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl)399 public SyncNotedAppOp startOperation(@NonNull IBinder token, int code, int uid, 400 @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, 401 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, 402 @Nullable String message, boolean shouldCollectMessage, 403 @AttributionFlags int attributionFlags, int attributionChainId, 404 @NonNull DodecFunction<IBinder, Integer, Integer, String, String, Integer, Boolean, 405 Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) { 406 if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) { 407 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid), 408 Process.SHELL_UID); 409 final long identity = Binder.clearCallingIdentity(); 410 try { 411 return superImpl.apply(token, code, shellUid, SHELL_PKG, attributionTag, 412 virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message, 413 shouldCollectMessage, attributionFlags, attributionChainId); 414 } finally { 415 Binder.restoreCallingIdentity(identity); 416 } 417 } 418 return superImpl.apply(token, code, uid, packageName, attributionTag, virtualDeviceId, 419 startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, 420 attributionFlags, attributionChainId); 421 } 422 423 @Override startProxyOperation(@onNull IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags, int attributionChainId, @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String, Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl)424 public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code, 425 @NonNull AttributionSource attributionSource, boolean startIfModeDefault, 426 boolean shouldCollectAsyncNotedOp, @Nullable String message, 427 boolean shouldCollectMessage, boolean skipProxyOperation, 428 @AttributionFlags int proxyAttributionFlags, 429 @AttributionFlags int proxiedAttributionFlags, int attributionChainId, 430 @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, 431 Boolean, String, Boolean, Boolean, Integer, Integer, Integer, 432 SyncNotedAppOp> superImpl) { 433 if (attributionSource.getUid() == mDelegateAndOwnerUid && isDelegateOp(code)) { 434 final int shellUid = UserHandle.getUid(UserHandle.getUserId( 435 attributionSource.getUid()), Process.SHELL_UID); 436 final long identity = Binder.clearCallingIdentity(); 437 try { 438 return superImpl.apply(clientId, code, 439 new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG, 440 attributionSource.getAttributionTag(), 441 attributionSource.getToken(), /*renouncedPermissions*/ null, 442 attributionSource.getDeviceId(), attributionSource.getNext()), 443 startIfModeDefault, shouldCollectAsyncNotedOp, message, 444 shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, 445 proxiedAttributionFlags, attributionChainId); 446 } finally { 447 Binder.restoreCallingIdentity(identity); 448 } 449 } 450 return superImpl.apply(clientId, code, attributionSource, startIfModeDefault, 451 shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation, 452 proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); 453 } 454 455 @Override finishProxyOperation(@onNull IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation, @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl)456 public void finishProxyOperation(@NonNull IBinder clientId, int code, 457 @NonNull AttributionSource attributionSource, boolean skipProxyOperation, 458 @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, 459 Void> superImpl) { 460 if (attributionSource.getUid() == mDelegateAndOwnerUid && isDelegateOp(code)) { 461 final int shellUid = UserHandle.getUid(UserHandle.getUserId( 462 attributionSource.getUid()), Process.SHELL_UID); 463 final long identity = Binder.clearCallingIdentity(); 464 try { 465 superImpl.apply(clientId, code, 466 new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG, 467 attributionSource.getAttributionTag(), 468 attributionSource.getToken(), /*renouncedPermissions*/ null, 469 attributionSource.getDeviceId(), attributionSource.getNext()), 470 skipProxyOperation); 471 return; 472 } finally { 473 Binder.restoreCallingIdentity(identity); 474 } 475 } 476 superImpl.apply(clientId, code, attributionSource, skipProxyOperation); 477 } 478 479 @Override finishOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag, int virtualDeviceId, @NonNull HexConsumer<IBinder, Integer, Integer, String, String, Integer> superImpl)480 public void finishOperation(IBinder clientId, int code, int uid, String packageName, 481 String attributionTag, int virtualDeviceId, @NonNull HexConsumer<IBinder, Integer, 482 Integer, String, String, Integer> superImpl) { 483 if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) { 484 final int shellUid = 485 UserHandle.getUid(UserHandle.getUserId(uid), Process.SHELL_UID); 486 final long identity = Binder.clearCallingIdentity(); 487 try { 488 superImpl.accept(clientId, code, shellUid, SHELL_PKG, attributionTag, 489 virtualDeviceId); 490 return; 491 } finally { 492 Binder.restoreCallingIdentity(identity); 493 } 494 } 495 superImpl.accept(clientId, code, uid, packageName, attributionTag, 496 virtualDeviceId); 497 } 498 isDelegatePermission(@onNull String permission)499 private boolean isDelegatePermission(@NonNull String permission) { 500 // null permissions means all permissions are delegated 501 return mDelegateAndOwnerUid != INVALID_UID 502 && (mDelegateAllPermissions 503 || ArrayUtils.contains(mDelegatePermissions, permission)); 504 } 505 isDelegateOp(int code)506 private boolean isDelegateOp(int code) { 507 if (mDelegateAllPermissions) { 508 return true; 509 } 510 // no permission for the op means the op is targeted 511 final String permission = AppOpsManager.opToPermission(code); 512 if (permission == null) { 513 return true; 514 } 515 return isDelegatePermission(permission); 516 } 517 } 518 } 519