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