1 /* 2 * Copyright (C) 2019 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.providers.media; 18 19 import static android.Manifest.permission.ACCESS_MEDIA_LOCATION; 20 import static android.app.AppOpsManager.MODE_ALLOWED; 21 import static android.app.AppOpsManager.permissionToOp; 22 import static android.content.pm.PackageManager.PERMISSION_DENIED; 23 24 import static com.android.providers.media.util.PermissionUtils.checkAppOpRequestInstallPackagesForSharedUid; 25 import static com.android.providers.media.util.PermissionUtils.checkIsLegacyStorageGranted; 26 import static com.android.providers.media.util.PermissionUtils.checkPermissionAccessMtp; 27 import static com.android.providers.media.util.PermissionUtils.checkPermissionDelegator; 28 import static com.android.providers.media.util.PermissionUtils.checkPermissionInstallPackages; 29 import static com.android.providers.media.util.PermissionUtils.checkPermissionManager; 30 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadAudio; 31 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadImages; 32 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadStorage; 33 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadVideo; 34 import static com.android.providers.media.util.PermissionUtils.checkPermissionSelf; 35 import static com.android.providers.media.util.PermissionUtils.checkPermissionShell; 36 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteAudio; 37 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages; 38 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage; 39 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteVideo; 40 import static com.android.providers.media.util.PermissionUtils.checkWriteImagesOrVideoAppOps; 41 42 import android.annotation.Nullable; 43 import android.app.AppOpsManager; 44 import android.app.compat.CompatChanges; 45 import android.compat.annotation.ChangeId; 46 import android.compat.annotation.EnabledAfter; 47 import android.compat.annotation.EnabledSince; 48 import android.content.ContentProvider; 49 import android.content.Context; 50 import android.content.pm.ApplicationInfo; 51 import android.content.pm.PackageManager.NameNotFoundException; 52 import android.os.Binder; 53 import android.os.Build; 54 import android.os.Process; 55 import android.os.SystemProperties; 56 import android.os.UserHandle; 57 import android.os.UserManager; 58 import android.util.ArrayMap; 59 60 import androidx.annotation.GuardedBy; 61 import androidx.annotation.NonNull; 62 63 import com.android.modules.utils.build.SdkLevel; 64 import com.android.providers.media.util.LongArray; 65 import com.android.providers.media.util.UserCache; 66 67 import java.util.Locale; 68 69 public class LocalCallingIdentity { 70 public final int pid; 71 public final int uid; 72 private final UserHandle user; 73 private final Context context; 74 private final String packageNameUnchecked; 75 // Info used for logging permission checks 76 private final @Nullable String attributionTag; 77 private final Object lock = new Object(); 78 LocalCallingIdentity(Context context, int pid, int uid, UserHandle user, String packageNameUnchecked, @Nullable String attributionTag)79 private LocalCallingIdentity(Context context, int pid, int uid, UserHandle user, 80 String packageNameUnchecked, @Nullable String attributionTag) { 81 this.context = context; 82 this.pid = pid; 83 this.uid = uid; 84 this.user = user; 85 this.packageNameUnchecked = packageNameUnchecked; 86 this.attributionTag = attributionTag; 87 } 88 89 /** 90 * See definition in {@link android.os.Environment} 91 */ 92 private static final long DEFAULT_SCOPED_STORAGE = 149924527L; 93 94 /** 95 * See definition in {@link android.os.Environment} 96 */ 97 private static final long FORCE_ENABLE_SCOPED_STORAGE = 132649864L; 98 99 private static final long UNKNOWN_ROW_ID = -1; 100 fromBinder(Context context, ContentProvider provider, UserCache userCache)101 public static LocalCallingIdentity fromBinder(Context context, ContentProvider provider, 102 UserCache userCache) { 103 String callingPackage = provider.getCallingPackageUnchecked(); 104 int binderUid = Binder.getCallingUid(); 105 if (callingPackage == null) { 106 if (binderUid == Process.SYSTEM_UID) { 107 // If UID is system assume we are running as ourself and not handling IPC 108 // Otherwise, we'd crash when we attempt AppOpsManager#checkPackage 109 // in LocalCallingIdentity#getPackageName 110 return fromSelf(context); 111 } 112 callingPackage = context.getOpPackageName(); 113 } 114 String callingAttributionTag = provider.getCallingAttributionTag(); 115 if (callingAttributionTag == null) { 116 callingAttributionTag = context.getAttributionTag(); 117 } 118 UserHandle user; 119 if (binderUid == Process.SHELL_UID || binderUid == Process.ROOT_UID) { 120 // For requests coming from the shell (eg `content query`), assume they are 121 // for the user we are running as. 122 user = Process.myUserHandle(); 123 } else { 124 user = UserHandle.getUserHandleForUid(binderUid); 125 } 126 // We need to use the cached variant here, because the uncached version may 127 // make a binder transaction, which would cause infinite recursion here. 128 // Using the cached variant is fine, because we shouldn't be getting any binder 129 // requests for this volume before it has been mounted anyway, at which point 130 // we must already know about the new user. 131 if (!userCache.userSharesMediaWithParentCached(user)) { 132 // It's possible that we got a cross-profile intent from a regular work profile; in 133 // that case, the request was explicitly targeted at the media database of the owner 134 // user; reflect that here. 135 user = Process.myUserHandle(); 136 } 137 return new LocalCallingIdentity(context, Binder.getCallingPid(), binderUid, 138 user, callingPackage, callingAttributionTag); 139 } 140 fromExternal(Context context, @Nullable UserCache userCache, int uid)141 public static LocalCallingIdentity fromExternal(Context context, @Nullable UserCache userCache, 142 int uid) { 143 final String[] sharedPackageNames = context.getPackageManager().getPackagesForUid(uid); 144 if (sharedPackageNames == null || sharedPackageNames.length == 0) { 145 throw new IllegalArgumentException("UID " + uid + " has no associated package"); 146 } 147 LocalCallingIdentity ident = fromExternal(context, userCache, uid, sharedPackageNames[0], 148 null); 149 ident.sharedPackageNames = sharedPackageNames; 150 ident.sharedPackageNamesResolved = true; 151 if (uid == Process.SHELL_UID) { 152 // This is useful for debugging/testing/development 153 if (SystemProperties.getBoolean("persist.sys.fuse.shell.redaction-needed", false)) { 154 ident.hasPermission |= PERMISSION_IS_REDACTION_NEEDED; 155 ident.hasPermissionResolved = PERMISSION_IS_REDACTION_NEEDED; 156 } 157 } 158 159 return ident; 160 } 161 fromExternal(Context context, @Nullable UserCache userCache, int uid, String packageName, @Nullable String attributionTag)162 public static LocalCallingIdentity fromExternal(Context context, @Nullable UserCache userCache, 163 int uid, String packageName, @Nullable String attributionTag) { 164 UserHandle user = UserHandle.getUserHandleForUid(uid); 165 if (userCache != null && !userCache.userSharesMediaWithParentCached(user)) { 166 // This can happen on some proprietary app clone solutions, where the owner 167 // and clone user each have their own MediaProvider instance, but refer to 168 // each other for cross-user file access through the use of bind mounts. 169 // In this case, assume the access is for the owner user, since that is 170 // the only user for which we manage volumes anyway. 171 user = Process.myUserHandle(); 172 } 173 return new LocalCallingIdentity(context, -1, uid, user, packageName, attributionTag); 174 } 175 fromSelf(Context context)176 public static LocalCallingIdentity fromSelf(Context context) { 177 return fromSelfAsUser(context, Process.myUserHandle()); 178 } 179 fromSelfAsUser(Context context, UserHandle user)180 public static LocalCallingIdentity fromSelfAsUser(Context context, UserHandle user) { 181 final LocalCallingIdentity ident = new LocalCallingIdentity( 182 context, 183 android.os.Process.myPid(), 184 android.os.Process.myUid(), 185 user, 186 context.getOpPackageName(), 187 context.getAttributionTag()); 188 189 ident.packageName = ident.packageNameUnchecked; 190 ident.packageNameResolved = true; 191 // Use ident.attributionTag from context, hence no change 192 ident.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; 193 ident.targetSdkVersionResolved = true; 194 ident.shouldBypass = false; 195 ident.shouldBypassResolved = true; 196 ident.hasPermission = ~(PERMISSION_IS_LEGACY_GRANTED | PERMISSION_IS_LEGACY_WRITE 197 | PERMISSION_IS_LEGACY_READ | PERMISSION_IS_REDACTION_NEEDED 198 | PERMISSION_IS_SHELL | PERMISSION_IS_DELEGATOR); 199 ident.hasPermissionResolved = ~0; 200 return ident; 201 } 202 203 private volatile String packageName; 204 private volatile boolean packageNameResolved; 205 getPackageName()206 public String getPackageName() { 207 if (!packageNameResolved) { 208 packageName = getPackageNameInternal(); 209 packageNameResolved = true; 210 } 211 return packageName; 212 } 213 getPackageNameInternal()214 private String getPackageNameInternal() { 215 // Verify that package name is actually owned by UID 216 context.getSystemService(AppOpsManager.class) 217 .checkPackage(uid, packageNameUnchecked); 218 return packageNameUnchecked; 219 } 220 221 private volatile String[] sharedPackageNames; 222 private volatile boolean sharedPackageNamesResolved; 223 getSharedPackageNames()224 public String[] getSharedPackageNames() { 225 if (!sharedPackageNamesResolved) { 226 sharedPackageNames = getSharedPackageNamesInternal(); 227 sharedPackageNamesResolved = true; 228 } 229 return sharedPackageNames; 230 } 231 getSharedPackageNamesInternal()232 private String[] getSharedPackageNamesInternal() { 233 final String[] packageNames = context.getPackageManager().getPackagesForUid(uid); 234 return (packageNames != null) ? packageNames : new String[0]; 235 } 236 237 private volatile int targetSdkVersion; 238 private volatile boolean targetSdkVersionResolved; 239 getTargetSdkVersion()240 public int getTargetSdkVersion() { 241 if (!targetSdkVersionResolved) { 242 targetSdkVersion = getTargetSdkVersionInternal(); 243 targetSdkVersionResolved = true; 244 } 245 return targetSdkVersion; 246 } 247 getTargetSdkVersionInternal()248 private int getTargetSdkVersionInternal() { 249 try { 250 final ApplicationInfo ai = context.getPackageManager() 251 .getApplicationInfo(getPackageName(), 0); 252 if (ai != null) { 253 return ai.targetSdkVersion; 254 } 255 } catch (NameNotFoundException ignored) { 256 } 257 return Build.VERSION_CODES.CUR_DEVELOPMENT; 258 } 259 getUser()260 public UserHandle getUser() { 261 return user; 262 } 263 264 public static final int PERMISSION_IS_SELF = 1 << 0; 265 public static final int PERMISSION_IS_SHELL = 1 << 1; 266 public static final int PERMISSION_IS_MANAGER = 1 << 2; 267 public static final int PERMISSION_IS_DELEGATOR = 1 << 3; 268 269 public static final int PERMISSION_IS_REDACTION_NEEDED = 1 << 8; 270 public static final int PERMISSION_IS_LEGACY_GRANTED = 1 << 9; 271 public static final int PERMISSION_IS_LEGACY_READ = 1 << 10; 272 public static final int PERMISSION_IS_LEGACY_WRITE = 1 << 11; 273 274 public static final int PERMISSION_READ_AUDIO = 1 << 16; 275 public static final int PERMISSION_READ_VIDEO = 1 << 17; 276 public static final int PERMISSION_READ_IMAGES = 1 << 18; 277 public static final int PERMISSION_WRITE_AUDIO = 1 << 19; 278 public static final int PERMISSION_WRITE_VIDEO = 1 << 20; 279 public static final int PERMISSION_WRITE_IMAGES = 1 << 21; 280 281 public static final int PERMISSION_IS_SYSTEM_GALLERY = 1 << 22; 282 /** 283 * Explicitly checks **only** for INSTALL_PACKAGES runtime permission. 284 */ 285 public static final int PERMISSION_INSTALL_PACKAGES = 1 << 23; 286 public static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 1 << 24; 287 288 /** 289 * Checks if REQUEST_INSTALL_PACKAGES app-op is allowed for any package sharing this UID. 290 */ 291 public static final int APPOP_REQUEST_INSTALL_PACKAGES_FOR_SHARED_UID = 1 << 25; 292 public static final int PERMISSION_ACCESS_MTP = 1 << 26; 293 294 private volatile int hasPermission; 295 private volatile int hasPermissionResolved; 296 hasPermission(int permission)297 public boolean hasPermission(int permission) { 298 if ((hasPermissionResolved & permission) == 0) { 299 if (hasPermissionInternal(permission)) { 300 hasPermission |= permission; 301 } 302 hasPermissionResolved |= permission; 303 } 304 return (hasPermission & permission) != 0; 305 } 306 hasPermissionInternal(int permission)307 private boolean hasPermissionInternal(int permission) { 308 boolean targetSdkIsAtLeastT = getTargetSdkVersion() > Build.VERSION_CODES.S_V2; 309 // While we're here, enforce any broad user-level restrictions 310 if ((uid == Process.SHELL_UID) && context.getSystemService(UserManager.class) 311 .hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) { 312 throw new SecurityException( 313 "Shell user cannot access files for user " + UserHandle.myUserId()); 314 } 315 316 switch (permission) { 317 case PERMISSION_IS_SELF: 318 return checkPermissionSelf(context, pid, uid); 319 case PERMISSION_IS_SHELL: 320 return checkPermissionShell(context, pid, uid); 321 case PERMISSION_IS_MANAGER: 322 return checkPermissionManager(context, pid, uid, getPackageName(), attributionTag); 323 case PERMISSION_IS_DELEGATOR: 324 return checkPermissionDelegator(context, pid, uid); 325 326 case PERMISSION_IS_REDACTION_NEEDED: 327 return isRedactionNeededInternal(); 328 case PERMISSION_IS_LEGACY_GRANTED: 329 return isLegacyStorageGranted(); 330 case PERMISSION_IS_LEGACY_READ: 331 return isLegacyReadInternal(); 332 case PERMISSION_IS_LEGACY_WRITE: 333 return isLegacyWriteInternal(); 334 335 case PERMISSION_WRITE_EXTERNAL_STORAGE: 336 return checkPermissionWriteStorage( 337 context, pid, uid, getPackageName(), attributionTag); 338 339 case PERMISSION_READ_AUDIO: 340 return checkPermissionReadAudio( 341 context, pid, uid, getPackageName(), attributionTag, targetSdkIsAtLeastT); 342 case PERMISSION_READ_VIDEO: 343 return checkPermissionReadVideo( 344 context, pid, uid, getPackageName(), attributionTag, targetSdkIsAtLeastT); 345 case PERMISSION_READ_IMAGES: 346 return checkPermissionReadImages( 347 context, pid, uid, getPackageName(), attributionTag, targetSdkIsAtLeastT); 348 case PERMISSION_WRITE_AUDIO: 349 return checkPermissionWriteAudio( 350 context, pid, uid, getPackageName(), attributionTag); 351 case PERMISSION_WRITE_VIDEO: 352 return checkPermissionWriteVideo( 353 context, pid, uid, getPackageName(), attributionTag); 354 case PERMISSION_WRITE_IMAGES: 355 return checkPermissionWriteImages( 356 context, pid, uid, getPackageName(), attributionTag); 357 case PERMISSION_IS_SYSTEM_GALLERY: 358 return checkWriteImagesOrVideoAppOps( 359 context, uid, getPackageName(), attributionTag); 360 case PERMISSION_INSTALL_PACKAGES: 361 return checkPermissionInstallPackages( 362 context, pid, uid, getPackageName(), attributionTag); 363 case APPOP_REQUEST_INSTALL_PACKAGES_FOR_SHARED_UID: 364 return checkAppOpRequestInstallPackagesForSharedUid( 365 context, uid, getSharedPackageNames(), attributionTag); 366 case PERMISSION_ACCESS_MTP: 367 return checkPermissionAccessMtp( 368 context, pid, uid, getPackageName(), attributionTag); 369 default: 370 return false; 371 } 372 } 373 isLegacyStorageGranted()374 private boolean isLegacyStorageGranted() { 375 boolean defaultScopedStorage = CompatChanges.isChangeEnabled( 376 DEFAULT_SCOPED_STORAGE, getPackageName(), UserHandle.getUserHandleForUid(uid)); 377 boolean forceEnableScopedStorage = CompatChanges.isChangeEnabled( 378 FORCE_ENABLE_SCOPED_STORAGE, getPackageName(), UserHandle.getUserHandleForUid(uid)); 379 380 // if Scoped Storage is strictly enforced, the app does *not* have legacy storage access 381 if (isScopedStorageEnforced(defaultScopedStorage, forceEnableScopedStorage)) { 382 return false; 383 } 384 // if Scoped Storage is strictly disabled, the app has legacy storage access 385 if (isScopedStorageDisabled(defaultScopedStorage, forceEnableScopedStorage)) { 386 return true; 387 } 388 389 return checkIsLegacyStorageGranted(context, uid, getPackageName(), attributionTag); 390 } 391 392 private volatile boolean shouldBypass; 393 private volatile boolean shouldBypassResolved; 394 395 /** 396 * Allow apps holding {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} 397 * permission to request raw external storage access. 398 */ 399 @ChangeId 400 @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) 401 static final long ENABLE_RAW_MANAGE_EXTERNAL_STORAGE_ACCESS = 178209446L; 402 403 /** 404 * Allow apps holding {@link android.app.role}#SYSTEM_GALLERY role to request raw external 405 * storage access. 406 */ 407 @ChangeId 408 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.R) 409 static final long ENABLE_RAW_SYSTEM_GALLERY_ACCESS = 183372781L; 410 411 /** 412 * Checks if app chooses to bypass database operations. 413 * 414 * <p> 415 * Note that this method doesn't check if app qualifies to bypass database operations. 416 * 417 * @return {@code true} if AndroidManifest.xml of this app has 418 * android:requestRawExternalStorageAccess=true 419 * {@code false} otherwise. 420 */ shouldBypassDatabase(boolean isSystemGallery)421 public boolean shouldBypassDatabase(boolean isSystemGallery) { 422 if (!shouldBypassResolved) { 423 shouldBypass = shouldBypassDatabaseInternal(isSystemGallery); 424 shouldBypassResolved = true; 425 } 426 return shouldBypass; 427 } 428 shouldBypassDatabaseInternal(boolean isSystemGallery)429 private boolean shouldBypassDatabaseInternal(boolean isSystemGallery) { 430 if (!SdkLevel.isAtLeastS()) { 431 // We need to parse the manifest flag ourselves here. 432 // TODO(b/178209446): Parse app manifest to get new flag values 433 return true; 434 } 435 436 final ApplicationInfo ai; 437 try { 438 ai = context.getPackageManager() 439 .getApplicationInfo(getPackageName(), 0); 440 if (ai != null) { 441 final int requestRawExternalStorageValue 442 = ai.getRequestRawExternalStorageAccess(); 443 if (requestRawExternalStorageValue 444 != ApplicationInfo.RAW_EXTERNAL_STORAGE_ACCESS_DEFAULT) { 445 return requestRawExternalStorageValue 446 == ApplicationInfo.RAW_EXTERNAL_STORAGE_ACCESS_REQUESTED; 447 } 448 // Manifest flag is not set, hence return default value based on the category of the 449 // app and targetSDK. 450 if (isSystemGallery) { 451 if (CompatChanges.isChangeEnabled( 452 ENABLE_RAW_SYSTEM_GALLERY_ACCESS, uid)) { 453 // If systemGallery, then the flag will default to false when they are 454 // targeting targetSDK>=30. 455 return false; 456 } 457 } else if (CompatChanges.isChangeEnabled( 458 ENABLE_RAW_MANAGE_EXTERNAL_STORAGE_ACCESS, uid)) { 459 // If app has MANAGE_EXTERNAL_STORAGE, the flag will default to false when they 460 // are targeting targetSDK>=31. 461 return false; 462 } 463 } 464 } catch (NameNotFoundException e) { 465 } 466 return true; 467 } 468 isScopedStorageEnforced(boolean defaultScopedStorage, boolean forceEnableScopedStorage)469 private boolean isScopedStorageEnforced(boolean defaultScopedStorage, 470 boolean forceEnableScopedStorage) { 471 return defaultScopedStorage && forceEnableScopedStorage; 472 } 473 isScopedStorageDisabled(boolean defaultScopedStorage, boolean forceEnableScopedStorage)474 private boolean isScopedStorageDisabled(boolean defaultScopedStorage, 475 boolean forceEnableScopedStorage) { 476 return !defaultScopedStorage && !forceEnableScopedStorage; 477 } 478 isLegacyWriteInternal()479 private boolean isLegacyWriteInternal() { 480 return hasPermission(PERMISSION_IS_LEGACY_GRANTED) 481 && checkPermissionWriteStorage(context, pid, uid, getPackageName(), attributionTag); 482 } 483 isLegacyReadInternal()484 private boolean isLegacyReadInternal() { 485 return hasPermission(PERMISSION_IS_LEGACY_GRANTED) 486 && checkPermissionReadStorage(context, pid, uid, getPackageName(), attributionTag); 487 } 488 489 /** System internals or callers holding permission have no redaction */ isRedactionNeededInternal()490 private boolean isRedactionNeededInternal() { 491 if (hasPermission(PERMISSION_IS_SELF) || hasPermission(PERMISSION_IS_SHELL)) { 492 return false; 493 } 494 495 if (context.checkPermission(ACCESS_MEDIA_LOCATION, pid, uid) == PERMISSION_DENIED 496 || context.getSystemService(AppOpsManager.class).noteProxyOpNoThrow( 497 permissionToOp(ACCESS_MEDIA_LOCATION), getPackageName(), uid, attributionTag, null) 498 != MODE_ALLOWED) { 499 return true; 500 } 501 502 return false; 503 } 504 505 @GuardedBy("lock") 506 private final LongArray ownedIds = new LongArray(); 507 isOwned(long id)508 public boolean isOwned(long id) { 509 synchronized (lock) { 510 return ownedIds.indexOf(id) != -1; 511 } 512 } 513 setOwned(long id, boolean owned)514 public void setOwned(long id, boolean owned) { 515 synchronized (lock) { 516 final int index = ownedIds.indexOf(id); 517 if (owned) { 518 if (index == -1) { 519 ownedIds.add(id); 520 } 521 } else { 522 if (index != -1) { 523 ownedIds.remove(index); 524 } 525 } 526 } 527 } 528 529 @GuardedBy("lock") 530 private final ArrayMap<String, Long> rowIdOfDeletedPaths = new ArrayMap<>(); 531 addDeletedRowId(@onNull String path, long id)532 public void addDeletedRowId(@NonNull String path, long id) { 533 synchronized (lock) { 534 rowIdOfDeletedPaths.put(path.toLowerCase(Locale.ROOT), id); 535 } 536 } 537 removeDeletedRowId(long id)538 public boolean removeDeletedRowId(long id) { 539 synchronized (lock) { 540 int index = rowIdOfDeletedPaths.indexOfValue(id); 541 final boolean isDeleted = index > -1; 542 while (index > -1) { 543 rowIdOfDeletedPaths.removeAt(index); 544 index = rowIdOfDeletedPaths.indexOfValue(id); 545 } 546 return isDeleted; 547 } 548 } 549 getDeletedRowId(@onNull String path)550 public long getDeletedRowId(@NonNull String path) { 551 synchronized (lock) { 552 return rowIdOfDeletedPaths.getOrDefault(path.toLowerCase(Locale.ROOT), UNKNOWN_ROW_ID); 553 } 554 } 555 556 private volatile int applicationMediaCapabilitiesSupportedFlags = -1; 557 private volatile int applicationMediaCapabilitiesUnsupportedFlags = -1; 558 getApplicationMediaCapabilitiesSupportedFlags()559 public int getApplicationMediaCapabilitiesSupportedFlags() { 560 return applicationMediaCapabilitiesSupportedFlags; 561 } 562 getApplicationMediaCapabilitiesUnsupportedFlags()563 public int getApplicationMediaCapabilitiesUnsupportedFlags() { 564 return applicationMediaCapabilitiesUnsupportedFlags; 565 } 566 setApplicationMediaCapabilitiesFlags(int supportedFlags, int unsupportedFlags)567 public void setApplicationMediaCapabilitiesFlags(int supportedFlags, int unsupportedFlags) { 568 applicationMediaCapabilitiesSupportedFlags = supportedFlags; 569 applicationMediaCapabilitiesUnsupportedFlags = unsupportedFlags; 570 } 571 } 572