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.Manifest.permission.MANAGE_EXTERNAL_STORAGE; 21 import static android.app.AppOpsManager.MODE_ALLOWED; 22 import static android.app.AppOpsManager.permissionToOp; 23 import static android.content.pm.PackageManager.PERMISSION_DENIED; 24 25 import static com.android.providers.media.util.PermissionUtils.checkAppOpRequestInstallPackagesForSharedUid; 26 import static com.android.providers.media.util.PermissionUtils.checkIsLegacyStorageGranted; 27 import static com.android.providers.media.util.PermissionUtils.checkPermissionAccessMtp; 28 import static com.android.providers.media.util.PermissionUtils.checkPermissionDelegator; 29 import static com.android.providers.media.util.PermissionUtils.checkPermissionInstallPackages; 30 import static com.android.providers.media.util.PermissionUtils.checkPermissionManager; 31 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadAudio; 32 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadImages; 33 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadStorage; 34 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadVideo; 35 import static com.android.providers.media.util.PermissionUtils.checkPermissionSelf; 36 import static com.android.providers.media.util.PermissionUtils.checkPermissionShell; 37 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteAudio; 38 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages; 39 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage; 40 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteVideo; 41 import static com.android.providers.media.util.PermissionUtils.checkWriteImagesOrVideoAppOps; 42 43 import android.annotation.Nullable; 44 import android.app.AppOpsManager; 45 import android.app.compat.CompatChanges; 46 import android.compat.annotation.ChangeId; 47 import android.compat.annotation.EnabledAfter; 48 import android.compat.annotation.EnabledSince; 49 import android.content.ContentProvider; 50 import android.content.Context; 51 import android.content.pm.ApplicationInfo; 52 import android.content.pm.PackageManager.NameNotFoundException; 53 import android.os.Binder; 54 import android.os.Build; 55 import android.os.Process; 56 import android.os.SystemProperties; 57 import android.os.UserHandle; 58 import android.os.UserManager; 59 import android.util.ArrayMap; 60 61 import androidx.annotation.GuardedBy; 62 import androidx.annotation.NonNull; 63 64 import com.android.modules.utils.build.SdkLevel; 65 import com.android.providers.media.util.LongArray; 66 import com.android.providers.media.util.UserCache; 67 68 import java.util.Locale; 69 70 public class LocalCallingIdentity { 71 public final int pid; 72 public final int uid; 73 private final UserHandle user; 74 private final Context context; 75 private final String packageNameUnchecked; 76 // Info used for logging permission checks 77 private final @Nullable String attributionTag; 78 private final Object lock = new Object(); 79 LocalCallingIdentity(Context context, int pid, int uid, UserHandle user, String packageNameUnchecked, @Nullable String attributionTag)80 private LocalCallingIdentity(Context context, int pid, int uid, UserHandle user, 81 String packageNameUnchecked, @Nullable String attributionTag) { 82 this.context = context; 83 this.pid = pid; 84 this.uid = uid; 85 this.user = user; 86 this.packageNameUnchecked = packageNameUnchecked; 87 this.attributionTag = attributionTag; 88 } 89 90 /** 91 * See definition in {@link android.os.Environment} 92 */ 93 private static final long DEFAULT_SCOPED_STORAGE = 149924527L; 94 95 /** 96 * See definition in {@link android.os.Environment} 97 */ 98 private static final long FORCE_ENABLE_SCOPED_STORAGE = 132649864L; 99 100 private static final long UNKNOWN_ROW_ID = -1; 101 fromBinder(Context context, ContentProvider provider, UserCache userCache)102 public static LocalCallingIdentity fromBinder(Context context, ContentProvider provider, 103 UserCache userCache) { 104 String callingPackage = provider.getCallingPackageUnchecked(); 105 int binderUid = Binder.getCallingUid(); 106 if (callingPackage == null) { 107 if (binderUid == Process.SYSTEM_UID) { 108 // If UID is system assume we are running as ourself and not handling IPC 109 // Otherwise, we'd crash when we attempt AppOpsManager#checkPackage 110 // in LocalCallingIdentity#getPackageName 111 return fromSelf(context); 112 } 113 callingPackage = context.getOpPackageName(); 114 } 115 String callingAttributionTag = provider.getCallingAttributionTag(); 116 if (callingAttributionTag == null) { 117 callingAttributionTag = context.getAttributionTag(); 118 } 119 UserHandle user; 120 if (binderUid == Process.SHELL_UID || binderUid == Process.ROOT_UID) { 121 // For requests coming from the shell (eg `content query`), assume they are 122 // for the user we are running as. 123 user = Process.myUserHandle(); 124 } else { 125 user = UserHandle.getUserHandleForUid(binderUid); 126 } 127 // We need to use the cached variant here, because the uncached version may 128 // make a binder transaction, which would cause infinite recursion here. 129 // Using the cached variant is fine, because we shouldn't be getting any binder 130 // requests for this volume before it has been mounted anyway, at which point 131 // we must already know about the new user. 132 if (!userCache.userSharesMediaWithParentCached(user)) { 133 // It's possible that we got a cross-profile intent from a regular work profile; in 134 // that case, the request was explicitly targeted at the media database of the owner 135 // user; reflect that here. 136 user = Process.myUserHandle(); 137 } 138 return new LocalCallingIdentity(context, Binder.getCallingPid(), binderUid, 139 user, callingPackage, callingAttributionTag); 140 } 141 fromExternal(Context context, @Nullable UserCache userCache, int uid)142 public static LocalCallingIdentity fromExternal(Context context, @Nullable UserCache userCache, 143 int uid) { 144 final String[] sharedPackageNames = context.getPackageManager().getPackagesForUid(uid); 145 if (sharedPackageNames == null || sharedPackageNames.length == 0) { 146 throw new IllegalArgumentException("UID " + uid + " has no associated package"); 147 } 148 LocalCallingIdentity ident = fromExternal(context, userCache, uid, sharedPackageNames[0], 149 null); 150 ident.sharedPackageNames = sharedPackageNames; 151 ident.sharedPackageNamesResolved = true; 152 if (uid == Process.SHELL_UID) { 153 // This is useful for debugging/testing/development 154 if (SystemProperties.getBoolean("persist.sys.fuse.shell.redaction-needed", false)) { 155 ident.hasPermission |= PERMISSION_IS_REDACTION_NEEDED; 156 ident.hasPermissionResolved = PERMISSION_IS_REDACTION_NEEDED; 157 } 158 } 159 160 return ident; 161 } 162 fromExternal(Context context, @Nullable UserCache userCache, int uid, String packageName, @Nullable String attributionTag)163 public static LocalCallingIdentity fromExternal(Context context, @Nullable UserCache userCache, 164 int uid, String packageName, @Nullable String attributionTag) { 165 UserHandle user = UserHandle.getUserHandleForUid(uid); 166 if (userCache != null && !userCache.userSharesMediaWithParentCached(user)) { 167 // This can happen on some proprietary app clone solutions, where the owner 168 // and clone user each have their own MediaProvider instance, but refer to 169 // each other for cross-user file access through the use of bind mounts. 170 // In this case, assume the access is for the owner user, since that is 171 // the only user for which we manage volumes anyway. 172 user = Process.myUserHandle(); 173 } 174 return new LocalCallingIdentity(context, -1, uid, user, packageName, attributionTag); 175 } 176 fromSelf(Context context)177 public static LocalCallingIdentity fromSelf(Context context) { 178 return fromSelfAsUser(context, Process.myUserHandle()); 179 } 180 fromSelfAsUser(Context context, UserHandle user)181 public static LocalCallingIdentity fromSelfAsUser(Context context, UserHandle user) { 182 final LocalCallingIdentity ident = new LocalCallingIdentity( 183 context, 184 android.os.Process.myPid(), 185 android.os.Process.myUid(), 186 user, 187 context.getOpPackageName(), 188 context.getAttributionTag()); 189 190 ident.packageName = ident.packageNameUnchecked; 191 ident.packageNameResolved = true; 192 // Use ident.attributionTag from context, hence no change 193 ident.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; 194 ident.targetSdkVersionResolved = true; 195 ident.shouldBypass = false; 196 ident.shouldBypassResolved = true; 197 ident.hasPermission = ~(PERMISSION_IS_LEGACY_GRANTED | PERMISSION_IS_LEGACY_WRITE 198 | PERMISSION_IS_LEGACY_READ | PERMISSION_IS_REDACTION_NEEDED 199 | PERMISSION_IS_SHELL | PERMISSION_IS_DELEGATOR); 200 ident.hasPermissionResolved = ~0; 201 return ident; 202 } 203 204 private volatile String packageName; 205 private volatile boolean packageNameResolved; 206 getPackageName()207 public String getPackageName() { 208 if (!packageNameResolved) { 209 packageName = getPackageNameInternal(); 210 packageNameResolved = true; 211 } 212 return packageName; 213 } 214 getPackageNameInternal()215 private String getPackageNameInternal() { 216 // Verify that package name is actually owned by UID 217 context.getSystemService(AppOpsManager.class) 218 .checkPackage(uid, packageNameUnchecked); 219 return packageNameUnchecked; 220 } 221 222 private volatile String[] sharedPackageNames; 223 private volatile boolean sharedPackageNamesResolved; 224 getSharedPackageNames()225 public String[] getSharedPackageNames() { 226 if (!sharedPackageNamesResolved) { 227 sharedPackageNames = getSharedPackageNamesInternal(); 228 sharedPackageNamesResolved = true; 229 } 230 return sharedPackageNames; 231 } 232 getSharedPackageNamesInternal()233 private String[] getSharedPackageNamesInternal() { 234 final String[] packageNames = context.getPackageManager().getPackagesForUid(uid); 235 return (packageNames != null) ? packageNames : new String[0]; 236 } 237 238 private volatile int targetSdkVersion; 239 private volatile boolean targetSdkVersionResolved; 240 getTargetSdkVersion()241 public int getTargetSdkVersion() { 242 if (!targetSdkVersionResolved) { 243 targetSdkVersion = getTargetSdkVersionInternal(); 244 targetSdkVersionResolved = true; 245 } 246 return targetSdkVersion; 247 } 248 getTargetSdkVersionInternal()249 private int getTargetSdkVersionInternal() { 250 try { 251 final ApplicationInfo ai = context.getPackageManager() 252 .getApplicationInfo(getPackageName(), 0); 253 if (ai != null) { 254 return ai.targetSdkVersion; 255 } 256 } catch (NameNotFoundException ignored) { 257 } 258 return Build.VERSION_CODES.CUR_DEVELOPMENT; 259 } 260 getUser()261 public UserHandle getUser() { 262 return user; 263 } 264 265 public static final int PERMISSION_IS_SELF = 1 << 0; 266 public static final int PERMISSION_IS_SHELL = 1 << 1; 267 public static final int PERMISSION_IS_MANAGER = 1 << 2; 268 public static final int PERMISSION_IS_DELEGATOR = 1 << 3; 269 270 public static final int PERMISSION_IS_REDACTION_NEEDED = 1 << 8; 271 public static final int PERMISSION_IS_LEGACY_GRANTED = 1 << 9; 272 public static final int PERMISSION_IS_LEGACY_READ = 1 << 10; 273 public static final int PERMISSION_IS_LEGACY_WRITE = 1 << 11; 274 275 public static final int PERMISSION_READ_AUDIO = 1 << 16; 276 public static final int PERMISSION_READ_VIDEO = 1 << 17; 277 public static final int PERMISSION_READ_IMAGES = 1 << 18; 278 public static final int PERMISSION_WRITE_AUDIO = 1 << 19; 279 public static final int PERMISSION_WRITE_VIDEO = 1 << 20; 280 public static final int PERMISSION_WRITE_IMAGES = 1 << 21; 281 282 public static final int PERMISSION_IS_SYSTEM_GALLERY = 1 << 22; 283 /** 284 * Explicitly checks **only** for INSTALL_PACKAGES runtime permission. 285 */ 286 public static final int PERMISSION_INSTALL_PACKAGES = 1 << 23; 287 public static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 1 << 24; 288 289 /** 290 * Checks if REQUEST_INSTALL_PACKAGES app-op is allowed for any package sharing this UID. 291 */ 292 public static final int APPOP_REQUEST_INSTALL_PACKAGES_FOR_SHARED_UID = 1 << 25; 293 public static final int PERMISSION_ACCESS_MTP = 1 << 26; 294 295 private volatile int hasPermission; 296 private volatile int hasPermissionResolved; 297 hasPermission(int permission)298 public boolean hasPermission(int permission) { 299 if ((hasPermissionResolved & permission) == 0) { 300 if (hasPermissionInternal(permission)) { 301 hasPermission |= permission; 302 } 303 hasPermissionResolved |= permission; 304 } 305 return (hasPermission & permission) != 0; 306 } 307 hasPermissionInternal(int permission)308 private boolean hasPermissionInternal(int permission) { 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); 342 case PERMISSION_READ_VIDEO: 343 return checkPermissionReadVideo( 344 context, pid, uid, getPackageName(), attributionTag); 345 case PERMISSION_READ_IMAGES: 346 return checkPermissionReadImages( 347 context, pid, uid, getPackageName(), attributionTag); 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