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