• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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