Allow certain apps to access blobs across users.
BlobStoreManagerService is updated to store only one copy of any blob on device and to allow apps with new ACCESS_BLOBS_ACROSS_USERS priv permission to access blobs commited by their instances on other users. Bug: 175844032 Test: atest --test-mapping apex/blobstore Change-Id: If6d10d90b17bde33baf9e83eeaae75d09d0b50ae
This commit is contained in:
parent
086a0be6b2
commit
523643c45c
@ -26,8 +26,8 @@ import android.annotation.NonNull;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Base64;
|
||||
import android.util.IndentingPrintWriter;
|
||||
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
|
@ -37,9 +37,9 @@ import android.permission.PermissionManager;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Base64;
|
||||
import android.util.DebugUtils;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.server.blob;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_BLOBS_ACROSS_USERS;
|
||||
import static android.app.blob.XmlTags.ATTR_COMMIT_TIME_MS;
|
||||
import static android.app.blob.XmlTags.ATTR_DESCRIPTION;
|
||||
import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME;
|
||||
@ -36,6 +37,7 @@ import static com.android.server.blob.BlobStoreConfig.TAG;
|
||||
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME;
|
||||
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME;
|
||||
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC;
|
||||
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;
|
||||
import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed;
|
||||
import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
|
||||
import static com.android.server.blob.BlobStoreUtils.getPackageResources;
|
||||
@ -45,15 +47,18 @@ import android.annotation.Nullable;
|
||||
import android.app.blob.BlobHandle;
|
||||
import android.app.blob.LeaseInfo;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.ResourceId;
|
||||
import android.content.res.Resources;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RevocableFileDescriptor;
|
||||
import android.os.UserHandle;
|
||||
import android.permission.PermissionManager;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.StatsEvent;
|
||||
@ -62,7 +67,6 @@ import android.util.proto.ProtoOutputStream;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
import com.android.server.blob.BlobStoreManagerService.DumpArgs;
|
||||
|
||||
@ -85,7 +89,6 @@ class BlobMetadata {
|
||||
|
||||
private final long mBlobId;
|
||||
private final BlobHandle mBlobHandle;
|
||||
private final int mUserId;
|
||||
|
||||
@GuardedBy("mMetadataLock")
|
||||
private final ArraySet<Committer> mCommitters = new ArraySet<>();
|
||||
@ -94,24 +97,23 @@ class BlobMetadata {
|
||||
private final ArraySet<Leasee> mLeasees = new ArraySet<>();
|
||||
|
||||
/**
|
||||
* Contains packageName -> {RevocableFileDescriptors}.
|
||||
* Contains Accessor -> {RevocableFileDescriptors}.
|
||||
*
|
||||
* Keep track of RevocableFileDescriptors given to clients which are not yet revoked/closed so
|
||||
* that when clients access is revoked or the blob gets deleted, we can be sure that clients
|
||||
* do not have any reference to the blob and the space occupied by the blob can be freed.
|
||||
*/
|
||||
@GuardedBy("mRevocableFds")
|
||||
private final ArrayMap<String, ArraySet<RevocableFileDescriptor>> mRevocableFds =
|
||||
private final ArrayMap<Accessor, ArraySet<RevocableFileDescriptor>> mRevocableFds =
|
||||
new ArrayMap<>();
|
||||
|
||||
// Do not access this directly, instead use #getBlobFile().
|
||||
private File mBlobFile;
|
||||
|
||||
BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) {
|
||||
BlobMetadata(Context context, long blobId, BlobHandle blobHandle) {
|
||||
mContext = context;
|
||||
this.mBlobId = blobId;
|
||||
this.mBlobHandle = blobHandle;
|
||||
this.mUserId = userId;
|
||||
}
|
||||
|
||||
long getBlobId() {
|
||||
@ -122,10 +124,6 @@ class BlobMetadata {
|
||||
return mBlobHandle;
|
||||
}
|
||||
|
||||
int getUserId() {
|
||||
return mUserId;
|
||||
}
|
||||
|
||||
void addOrReplaceCommitter(@NonNull Committer committer) {
|
||||
synchronized (mMetadataLock) {
|
||||
// We need to override the committer data, so first remove any existing
|
||||
@ -155,13 +153,24 @@ class BlobMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
void removeCommittersFromUnknownPkgs(SparseArray<String> knownPackages) {
|
||||
void removeCommittersFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) {
|
||||
synchronized (mMetadataLock) {
|
||||
mCommitters.removeIf(committer ->
|
||||
!committer.packageName.equals(knownPackages.get(committer.uid)));
|
||||
mCommitters.removeIf(committer -> {
|
||||
final int userId = UserHandle.getUserId(committer.uid);
|
||||
final SparseArray<String> userPackages = knownPackages.get(userId);
|
||||
if (userPackages == null) {
|
||||
return true;
|
||||
}
|
||||
return !committer.packageName.equals(userPackages.get(committer.uid));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void addCommittersAndLeasees(BlobMetadata blobMetadata) {
|
||||
mCommitters.addAll(blobMetadata.mCommitters);
|
||||
mLeasees.addAll(blobMetadata.mLeasees);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Committer getExistingCommitter(@NonNull String packageName, int uid) {
|
||||
synchronized (mCommitters) {
|
||||
@ -201,10 +210,16 @@ class BlobMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
void removeLeaseesFromUnknownPkgs(SparseArray<String> knownPackages) {
|
||||
void removeLeaseesFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) {
|
||||
synchronized (mMetadataLock) {
|
||||
mLeasees.removeIf(leasee ->
|
||||
!leasee.packageName.equals(knownPackages.get(leasee.uid)));
|
||||
mLeasees.removeIf(leasee -> {
|
||||
final int userId = UserHandle.getUserId(leasee.uid);
|
||||
final SparseArray<String> userPackages = knownPackages.get(userId);
|
||||
if (userPackages == null) {
|
||||
return true;
|
||||
}
|
||||
return !leasee.packageName.equals(userPackages.get(leasee.uid));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,6 +229,25 @@ class BlobMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
void removeDataForUser(int userId) {
|
||||
synchronized (mMetadataLock) {
|
||||
mCommitters.removeIf(committer -> (userId == UserHandle.getUserId(committer.uid)));
|
||||
mLeasees.removeIf(leasee -> (userId == UserHandle.getUserId(leasee.uid)));
|
||||
mRevocableFds.entrySet().removeIf(entry -> {
|
||||
final Accessor accessor = entry.getKey();
|
||||
final ArraySet<RevocableFileDescriptor> rFds = entry.getValue();
|
||||
if (userId != UserHandle.getUserId(accessor.uid)) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0, fdCount = rFds.size(); i < fdCount; ++i) {
|
||||
rFds.valueAt(i).revoke();
|
||||
}
|
||||
rFds.clear();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasValidLeases() {
|
||||
synchronized (mMetadataLock) {
|
||||
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
|
||||
@ -244,8 +278,12 @@ class BlobMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
final int callingUserId = UserHandle.getUserId(callingUid);
|
||||
for (int i = 0, size = mCommitters.size(); i < size; ++i) {
|
||||
final Committer committer = mCommitters.valueAt(i);
|
||||
if (callingUserId != UserHandle.getUserId(committer.uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the caller is the same package that committed the blob.
|
||||
if (committer.equals(callingPackage, callingUid)) {
|
||||
@ -259,38 +297,105 @@ class BlobMetadata {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
final boolean canCallerAccessBlobsAcrossUsers = checkCallerCanAccessBlobsAcrossUsers(
|
||||
callingPackage, callingUserId);
|
||||
if (!canCallerAccessBlobsAcrossUsers) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0, size = mCommitters.size(); i < size; ++i) {
|
||||
final Committer committer = mCommitters.valueAt(i);
|
||||
final int committerUserId = UserHandle.getUserId(committer.uid);
|
||||
if (callingUserId == committerUserId) {
|
||||
continue;
|
||||
}
|
||||
if (!checkCallerCanAccessBlobsAcrossUsers(callingPackage, committerUserId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the caller is allowed access as per the access mode specified
|
||||
// by the committer.
|
||||
if (committer.blobAccessMode.isAccessAllowedForCaller(mContext,
|
||||
callingPackage, committer.packageName, callingUid, attributionTag)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean checkCallerCanAccessBlobsAcrossUsers(
|
||||
String callingPackage, int callingUserId) {
|
||||
return PermissionManager.checkPackageNamePermission(ACCESS_BLOBS_ACROSS_USERS,
|
||||
callingPackage, callingUserId) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
boolean hasACommitterOrLeaseeInUser(int userId) {
|
||||
return hasACommitterInUser(userId) || hasALeaseeInUser(userId);
|
||||
}
|
||||
|
||||
boolean hasACommitterInUser(int userId) {
|
||||
synchronized (mMetadataLock) {
|
||||
for (int i = 0, size = mCommitters.size(); i < size; ++i) {
|
||||
final Committer committer = mCommitters.valueAt(i);
|
||||
if (userId == UserHandle.getUserId(committer.uid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasALeaseeInUser(int userId) {
|
||||
synchronized (mMetadataLock) {
|
||||
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
|
||||
final Leasee leasee = mLeasees.valueAt(i);
|
||||
if (userId == UserHandle.getUserId(leasee.uid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isACommitter(@NonNull String packageName, int uid) {
|
||||
synchronized (mMetadataLock) {
|
||||
return isAnAccessor(mCommitters, packageName, uid);
|
||||
return isAnAccessor(mCommitters, packageName, uid, UserHandle.getUserId(uid));
|
||||
}
|
||||
}
|
||||
|
||||
boolean isALeasee(@Nullable String packageName, int uid) {
|
||||
synchronized (mMetadataLock) {
|
||||
final Leasee leasee = getAccessor(mLeasees, packageName, uid);
|
||||
final Leasee leasee = getAccessor(mLeasees, packageName, uid,
|
||||
UserHandle.getUserId(uid));
|
||||
return leasee != null && leasee.isStillValid();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isALeaseeInUser(@Nullable String packageName, int uid, int userId) {
|
||||
synchronized (mMetadataLock) {
|
||||
final Leasee leasee = getAccessor(mLeasees, packageName, uid, userId);
|
||||
return leasee != null && leasee.isStillValid();
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends Accessor> boolean isAnAccessor(@NonNull ArraySet<T> accessors,
|
||||
@Nullable String packageName, int uid) {
|
||||
@Nullable String packageName, int uid, int userId) {
|
||||
// Check if the package is an accessor of the data blob.
|
||||
return getAccessor(accessors, packageName, uid) != null;
|
||||
return getAccessor(accessors, packageName, uid, userId) != null;
|
||||
}
|
||||
|
||||
private static <T extends Accessor> T getAccessor(@NonNull ArraySet<T> accessors,
|
||||
@Nullable String packageName, int uid) {
|
||||
@Nullable String packageName, int uid, int userId) {
|
||||
// Check if the package is an accessor of the data blob.
|
||||
for (int i = 0, size = accessors.size(); i < size; ++i) {
|
||||
final Accessor accessor = accessors.valueAt(i);
|
||||
if (packageName != null && uid != INVALID_UID
|
||||
&& accessor.equals(packageName, uid)) {
|
||||
return (T) accessor;
|
||||
} else if (packageName != null && accessor.packageName.equals(packageName)) {
|
||||
} else if (packageName != null && accessor.packageName.equals(packageName)
|
||||
&& userId == UserHandle.getUserId(accessor.uid)) {
|
||||
return (T) accessor;
|
||||
} else if (uid != INVALID_UID && accessor.uid == uid) {
|
||||
return (T) accessor;
|
||||
@ -299,23 +404,29 @@ class BlobMetadata {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isALeasee(@NonNull String packageName) {
|
||||
return isALeasee(packageName, INVALID_UID);
|
||||
boolean shouldAttributeToLeasee(@NonNull String packageName, int userId,
|
||||
boolean callerHasStatsPermission) {
|
||||
if (!isALeaseeInUser(packageName, INVALID_UID, userId)) {
|
||||
return false;
|
||||
}
|
||||
if (!callerHasStatsPermission || !hasOtherLeasees(packageName, INVALID_UID, userId)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isALeasee(int uid) {
|
||||
return isALeasee(null, uid);
|
||||
boolean shouldAttributeToLeasee(int uid, boolean callerHasStatsPermission) {
|
||||
final int userId = UserHandle.getUserId(uid);
|
||||
if (!isALeaseeInUser(null, uid, userId)) {
|
||||
return false;
|
||||
}
|
||||
if (!callerHasStatsPermission || !hasOtherLeasees(null, uid, userId)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasOtherLeasees(@NonNull String packageName) {
|
||||
return hasOtherLeasees(packageName, INVALID_UID);
|
||||
}
|
||||
|
||||
boolean hasOtherLeasees(int uid) {
|
||||
return hasOtherLeasees(null, uid);
|
||||
}
|
||||
|
||||
private boolean hasOtherLeasees(@Nullable String packageName, int uid) {
|
||||
private boolean hasOtherLeasees(@Nullable String packageName, int uid, int userId) {
|
||||
synchronized (mMetadataLock) {
|
||||
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
|
||||
final Leasee leasee = mLeasees.valueAt(i);
|
||||
@ -326,7 +437,8 @@ class BlobMetadata {
|
||||
if (packageName != null && uid != INVALID_UID
|
||||
&& !leasee.equals(packageName, uid)) {
|
||||
return true;
|
||||
} else if (packageName != null && !leasee.packageName.equals(packageName)) {
|
||||
} else if (packageName != null && (!leasee.packageName.equals(packageName)
|
||||
|| userId != UserHandle.getUserId(leasee.uid))) {
|
||||
return true;
|
||||
} else if (uid != INVALID_UID && leasee.uid != uid) {
|
||||
return true;
|
||||
@ -371,7 +483,7 @@ class BlobMetadata {
|
||||
return mBlobFile;
|
||||
}
|
||||
|
||||
ParcelFileDescriptor openForRead(String callingPackage) throws IOException {
|
||||
ParcelFileDescriptor openForRead(String callingPackage, int callingUid) throws IOException {
|
||||
// TODO: Add limit on opened fds
|
||||
FileDescriptor fd;
|
||||
try {
|
||||
@ -381,7 +493,7 @@ class BlobMetadata {
|
||||
}
|
||||
try {
|
||||
if (BlobStoreConfig.shouldUseRevocableFdForReads()) {
|
||||
return createRevocableFd(fd, callingPackage);
|
||||
return createRevocableFd(fd, callingPackage, callingUid);
|
||||
} else {
|
||||
return new ParcelFileDescriptor(fd);
|
||||
}
|
||||
@ -393,26 +505,28 @@ class BlobMetadata {
|
||||
|
||||
@NonNull
|
||||
private ParcelFileDescriptor createRevocableFd(FileDescriptor fd,
|
||||
String callingPackage) throws IOException {
|
||||
String callingPackage, int callingUid) throws IOException {
|
||||
final RevocableFileDescriptor revocableFd =
|
||||
new RevocableFileDescriptor(mContext, fd);
|
||||
final Accessor accessor;
|
||||
synchronized (mRevocableFds) {
|
||||
ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
|
||||
mRevocableFds.get(callingPackage);
|
||||
if (revocableFdsForPkg == null) {
|
||||
revocableFdsForPkg = new ArraySet<>();
|
||||
mRevocableFds.put(callingPackage, revocableFdsForPkg);
|
||||
accessor = new Accessor(callingPackage, callingUid);
|
||||
ArraySet<RevocableFileDescriptor> revocableFdsForAccessor =
|
||||
mRevocableFds.get(accessor);
|
||||
if (revocableFdsForAccessor == null) {
|
||||
revocableFdsForAccessor = new ArraySet<>();
|
||||
mRevocableFds.put(accessor, revocableFdsForAccessor);
|
||||
}
|
||||
revocableFdsForPkg.add(revocableFd);
|
||||
revocableFdsForAccessor.add(revocableFd);
|
||||
}
|
||||
revocableFd.addOnCloseListener((e) -> {
|
||||
synchronized (mRevocableFds) {
|
||||
final ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
|
||||
mRevocableFds.get(callingPackage);
|
||||
if (revocableFdsForPkg != null) {
|
||||
revocableFdsForPkg.remove(revocableFd);
|
||||
if (revocableFdsForPkg.isEmpty()) {
|
||||
mRevocableFds.remove(callingPackage);
|
||||
final ArraySet<RevocableFileDescriptor> revocableFdsForAccessor =
|
||||
mRevocableFds.get(accessor);
|
||||
if (revocableFdsForAccessor != null) {
|
||||
revocableFdsForAccessor.remove(revocableFd);
|
||||
if (revocableFdsForAccessor.isEmpty()) {
|
||||
mRevocableFds.remove(accessor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -421,22 +535,23 @@ class BlobMetadata {
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
revokeAllFds();
|
||||
revokeAndClearAllFds();
|
||||
getBlobFile().delete();
|
||||
}
|
||||
|
||||
private void revokeAllFds() {
|
||||
private void revokeAndClearAllFds() {
|
||||
synchronized (mRevocableFds) {
|
||||
for (int i = 0, pkgCount = mRevocableFds.size(); i < pkgCount; ++i) {
|
||||
final ArraySet<RevocableFileDescriptor> packageFds =
|
||||
for (int i = 0, accessorCount = mRevocableFds.size(); i < accessorCount; ++i) {
|
||||
final ArraySet<RevocableFileDescriptor> rFds =
|
||||
mRevocableFds.valueAt(i);
|
||||
if (packageFds == null) {
|
||||
if (rFds == null) {
|
||||
continue;
|
||||
}
|
||||
for (int j = 0, fdCount = packageFds.size(); j < fdCount; ++j) {
|
||||
packageFds.valueAt(j).revoke();
|
||||
for (int j = 0, fdCount = rFds.size(); j < fdCount; ++j) {
|
||||
rFds.valueAt(j).revoke();
|
||||
}
|
||||
}
|
||||
mRevocableFds.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -547,10 +662,10 @@ class BlobMetadata {
|
||||
fout.println("<empty>");
|
||||
} else {
|
||||
for (int i = 0, count = mRevocableFds.size(); i < count; ++i) {
|
||||
final String packageName = mRevocableFds.keyAt(i);
|
||||
final ArraySet<RevocableFileDescriptor> packageFds =
|
||||
final Accessor accessor = mRevocableFds.keyAt(i);
|
||||
final ArraySet<RevocableFileDescriptor> rFds =
|
||||
mRevocableFds.valueAt(i);
|
||||
fout.println(packageName + "#" + packageFds.size());
|
||||
fout.println(accessor + ": #" + rFds.size());
|
||||
}
|
||||
}
|
||||
fout.decreaseIndent();
|
||||
@ -560,7 +675,6 @@ class BlobMetadata {
|
||||
void writeToXml(XmlSerializer out) throws IOException {
|
||||
synchronized (mMetadataLock) {
|
||||
XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId);
|
||||
XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId);
|
||||
|
||||
out.startTag(null, TAG_BLOB_HANDLE);
|
||||
mBlobHandle.writeToXml(out);
|
||||
@ -584,7 +698,9 @@ class BlobMetadata {
|
||||
static BlobMetadata createFromXml(XmlPullParser in, int version, Context context)
|
||||
throws XmlPullParserException, IOException {
|
||||
final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID);
|
||||
final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID);
|
||||
if (version < XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) {
|
||||
XmlUtils.readIntAttribute(in, ATTR_USER_ID);
|
||||
}
|
||||
|
||||
BlobHandle blobHandle = null;
|
||||
final ArraySet<Committer> committers = new ArraySet<>();
|
||||
@ -608,7 +724,7 @@ class BlobMetadata {
|
||||
return null;
|
||||
}
|
||||
|
||||
final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle, userId);
|
||||
final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle);
|
||||
blobMetadata.setCommitters(committers);
|
||||
blobMetadata.setLeasees(leasees);
|
||||
return blobMetadata;
|
||||
|
@ -27,12 +27,11 @@ import android.provider.DeviceConfig;
|
||||
import android.provider.DeviceConfig.Properties;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DataUnit;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.TimeUtils;
|
||||
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -47,8 +46,9 @@ class BlobStoreConfig {
|
||||
public static final int XML_VERSION_ADD_DESC_RES_NAME = 3;
|
||||
public static final int XML_VERSION_ADD_COMMIT_TIME = 4;
|
||||
public static final int XML_VERSION_ADD_SESSION_CREATION_TIME = 5;
|
||||
public static final int XML_VERSION_ALLOW_ACCESS_ACROSS_USERS = 6;
|
||||
|
||||
public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_SESSION_CREATION_TIME;
|
||||
public static final int XML_VERSION_CURRENT = XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;
|
||||
|
||||
public static final long INVALID_BLOB_ID = 0;
|
||||
public static final long INVALID_BLOB_SIZE = 0;
|
||||
|
@ -32,6 +32,7 @@ import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_ID;
|
||||
import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE;
|
||||
import static com.android.server.blob.BlobStoreConfig.LOGV;
|
||||
import static com.android.server.blob.BlobStoreConfig.TAG;
|
||||
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;
|
||||
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
|
||||
import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs;
|
||||
import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs;
|
||||
@ -83,6 +84,7 @@ import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.ExceptionUtils;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
@ -96,7 +98,6 @@ import com.android.internal.util.CollectionUtils;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
import com.android.internal.util.function.pooled.PooledLambda;
|
||||
@ -129,6 +130,7 @@ import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@ -146,9 +148,9 @@ public class BlobStoreManagerService extends SystemService {
|
||||
@GuardedBy("mBlobsLock")
|
||||
private long mCurrentMaxSessionId;
|
||||
|
||||
// Contains data of userId -> {BlobHandle -> {BlobMetadata}}
|
||||
// Contains data of BlobHandle -> BlobMetadata.
|
||||
@GuardedBy("mBlobsLock")
|
||||
private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>();
|
||||
private final ArrayMap<BlobHandle, BlobMetadata> mBlobsMap = new ArrayMap<>();
|
||||
|
||||
// Contains all ids that are currently in use.
|
||||
@GuardedBy("mBlobsLock")
|
||||
@ -265,16 +267,6 @@ public class BlobStoreManagerService extends SystemService {
|
||||
return userSessions;
|
||||
}
|
||||
|
||||
@GuardedBy("mBlobsLock")
|
||||
private ArrayMap<BlobHandle, BlobMetadata> getUserBlobsLocked(int userId) {
|
||||
ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.get(userId);
|
||||
if (userBlobs == null) {
|
||||
userBlobs = new ArrayMap<>();
|
||||
mBlobsMap.put(userId, userBlobs);
|
||||
}
|
||||
return userBlobs;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) {
|
||||
synchronized (mBlobsLock) {
|
||||
@ -283,9 +275,16 @@ public class BlobStoreManagerService extends SystemService {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void addUserBlobsForTest(ArrayMap<BlobHandle, BlobMetadata> userBlobs, int userId) {
|
||||
BlobMetadata getBlobForTest(BlobHandle blobHandle) {
|
||||
synchronized (mBlobsLock) {
|
||||
mBlobsMap.put(userId, userBlobs);
|
||||
return mBlobsMap.get(blobHandle);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
int getBlobsCountForTest() {
|
||||
synchronized (mBlobsLock) {
|
||||
return mBlobsMap.size();
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,14 +318,9 @@ public class BlobStoreManagerService extends SystemService {
|
||||
}
|
||||
|
||||
@GuardedBy("mBlobsLock")
|
||||
private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) {
|
||||
addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId));
|
||||
}
|
||||
|
||||
@GuardedBy("mBlobsLock")
|
||||
private void addBlobForUserLocked(BlobMetadata blobMetadata,
|
||||
ArrayMap<BlobHandle, BlobMetadata> userBlobs) {
|
||||
userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata);
|
||||
@VisibleForTesting
|
||||
void addBlobLocked(BlobMetadata blobMetadata) {
|
||||
mBlobsMap.put(blobMetadata.getBlobHandle(), blobMetadata);
|
||||
addActiveBlobIdLocked(blobMetadata.getBlobId());
|
||||
}
|
||||
|
||||
@ -404,8 +398,7 @@ public class BlobStoreManagerService extends SystemService {
|
||||
private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid,
|
||||
String callingPackage, String attributionTag) throws IOException {
|
||||
synchronized (mBlobsLock) {
|
||||
final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
|
||||
.get(blobHandle);
|
||||
final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
|
||||
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
|
||||
callingPackage, callingUid, attributionTag)) {
|
||||
if (blobMetadata == null) {
|
||||
@ -415,7 +408,7 @@ public class BlobStoreManagerService extends SystemService {
|
||||
} else {
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
|
||||
blobMetadata.getBlobId(), blobMetadata.getSize(),
|
||||
FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED);
|
||||
FrameworkStatsLog.BLOB_OPENED__RESULT__ACCESS_NOT_ALLOWED);
|
||||
}
|
||||
throw new SecurityException("Caller not allowed to access " + blobHandle
|
||||
+ "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
|
||||
@ -425,7 +418,7 @@ public class BlobStoreManagerService extends SystemService {
|
||||
blobMetadata.getBlobId(), blobMetadata.getSize(),
|
||||
FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS);
|
||||
|
||||
return blobMetadata.openForRead(callingPackage);
|
||||
return blobMetadata.openForRead(callingPackage, callingUid);
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,11 +426,11 @@ public class BlobStoreManagerService extends SystemService {
|
||||
private int getCommittedBlobsCountLocked(int uid, String packageName) {
|
||||
// TODO: Maintain a counter instead of traversing all the blobs
|
||||
final AtomicInteger blobsCount = new AtomicInteger(0);
|
||||
forEachBlobInUser((blobMetadata) -> {
|
||||
forEachBlobLocked(blobMetadata -> {
|
||||
if (blobMetadata.isACommitter(packageName, uid)) {
|
||||
blobsCount.getAndIncrement();
|
||||
}
|
||||
}, UserHandle.getUserId(uid));
|
||||
});
|
||||
return blobsCount.get();
|
||||
}
|
||||
|
||||
@ -445,11 +438,11 @@ public class BlobStoreManagerService extends SystemService {
|
||||
private int getLeasedBlobsCountLocked(int uid, String packageName) {
|
||||
// TODO: Maintain a counter instead of traversing all the blobs
|
||||
final AtomicInteger blobsCount = new AtomicInteger(0);
|
||||
forEachBlobInUser((blobMetadata) -> {
|
||||
forEachBlobLocked(blobMetadata -> {
|
||||
if (blobMetadata.isALeasee(packageName, uid)) {
|
||||
blobsCount.getAndIncrement();
|
||||
}
|
||||
}, UserHandle.getUserId(uid));
|
||||
});
|
||||
return blobsCount.get();
|
||||
}
|
||||
|
||||
@ -465,8 +458,16 @@ public class BlobStoreManagerService extends SystemService {
|
||||
throw new LimitExceededException("Too many leased blobs for the caller: "
|
||||
+ leasesCount);
|
||||
}
|
||||
final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
|
||||
.get(blobHandle);
|
||||
if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0
|
||||
&& leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
|
||||
INVALID_BLOB_ID, INVALID_BLOB_SIZE,
|
||||
FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID);
|
||||
throw new IllegalArgumentException(
|
||||
"Lease expiry cannot be later than blobs expiry time");
|
||||
}
|
||||
|
||||
final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
|
||||
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
|
||||
callingPackage, callingUid, attributionTag)) {
|
||||
if (blobMetadata == null) {
|
||||
@ -481,15 +482,7 @@ public class BlobStoreManagerService extends SystemService {
|
||||
throw new SecurityException("Caller not allowed to access " + blobHandle
|
||||
+ "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
|
||||
}
|
||||
if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0
|
||||
&& leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
|
||||
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
|
||||
blobMetadata.getBlobId(), blobMetadata.getSize(),
|
||||
FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID);
|
||||
throw new IllegalArgumentException(
|
||||
"Lease expiry cannot be later than blobs expiry time");
|
||||
}
|
||||
if (blobMetadata.getSize()
|
||||
> getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) {
|
||||
|
||||
@ -518,20 +511,18 @@ public class BlobStoreManagerService extends SystemService {
|
||||
@GuardedBy("mBlobsLock")
|
||||
long getTotalUsageBytesLocked(int callingUid, String callingPackage) {
|
||||
final AtomicLong totalBytes = new AtomicLong(0);
|
||||
forEachBlobInUser((blobMetadata) -> {
|
||||
forEachBlobLocked((blobMetadata) -> {
|
||||
if (blobMetadata.isALeasee(callingPackage, callingUid)) {
|
||||
totalBytes.getAndAdd(blobMetadata.getSize());
|
||||
}
|
||||
}, UserHandle.getUserId(callingUid));
|
||||
});
|
||||
return totalBytes.get();
|
||||
}
|
||||
|
||||
private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
|
||||
String callingPackage, String attributionTag) {
|
||||
synchronized (mBlobsLock) {
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
|
||||
getUserBlobsLocked(UserHandle.getUserId(callingUid));
|
||||
final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
|
||||
final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
|
||||
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
|
||||
callingPackage, callingUid, attributionTag)) {
|
||||
throw new SecurityException("Caller not allowed to access " + blobHandle
|
||||
@ -547,12 +538,12 @@ public class BlobStoreManagerService extends SystemService {
|
||||
synchronized (mBlobsLock) {
|
||||
// Check if blobMetadata object is still valid. If it is not, then
|
||||
// it means that it was already deleted and nothing else to do here.
|
||||
if (!Objects.equals(userBlobs.get(blobHandle), blobMetadata)) {
|
||||
if (!Objects.equals(mBlobsMap.get(blobHandle), blobMetadata)) {
|
||||
return;
|
||||
}
|
||||
if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
|
||||
deleteBlobLocked(blobMetadata);
|
||||
userBlobs.remove(blobHandle);
|
||||
mBlobsMap.remove(blobHandle);
|
||||
}
|
||||
writeBlobsInfoAsync();
|
||||
}
|
||||
@ -583,12 +574,18 @@ public class BlobStoreManagerService extends SystemService {
|
||||
}
|
||||
return packageResources;
|
||||
};
|
||||
getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> {
|
||||
forEachBlobLocked((blobHandle, blobMetadata) -> {
|
||||
if (!blobMetadata.hasACommitterOrLeaseeInUser(userId)) {
|
||||
return;
|
||||
}
|
||||
final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>();
|
||||
blobMetadata.forEachLeasee(leasee -> {
|
||||
if (!leasee.isStillValid()) {
|
||||
return;
|
||||
}
|
||||
if (userId != UserHandle.getUserId(leasee.uid)) {
|
||||
return;
|
||||
}
|
||||
final int descriptionResId = leasee.descriptionResEntryName == null
|
||||
? Resources.ID_NULL
|
||||
: getDescriptionResourceId(resourcesGetter.apply(leasee.packageName),
|
||||
@ -608,9 +605,7 @@ public class BlobStoreManagerService extends SystemService {
|
||||
|
||||
private void deleteBlobInternal(long blobId, int callingUid) {
|
||||
synchronized (mBlobsLock) {
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
|
||||
UserHandle.getUserId(callingUid));
|
||||
userBlobs.entrySet().removeIf(entry -> {
|
||||
mBlobsMap.entrySet().removeIf(entry -> {
|
||||
final BlobMetadata blobMetadata = entry.getValue();
|
||||
if (blobMetadata.getBlobId() == blobId) {
|
||||
deleteBlobLocked(blobMetadata);
|
||||
@ -625,19 +620,20 @@ public class BlobStoreManagerService extends SystemService {
|
||||
private List<BlobHandle> getLeasedBlobsInternal(int callingUid,
|
||||
@NonNull String callingPackage) {
|
||||
final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>();
|
||||
forEachBlobInUser(blobMetadata -> {
|
||||
if (blobMetadata.isALeasee(callingPackage, callingUid)) {
|
||||
leasedBlobs.add(blobMetadata.getBlobHandle());
|
||||
}
|
||||
}, UserHandle.getUserId(callingUid));
|
||||
synchronized (mBlobsLock) {
|
||||
forEachBlobLocked(blobMetadata -> {
|
||||
if (blobMetadata.isALeasee(callingPackage, callingUid)) {
|
||||
leasedBlobs.add(blobMetadata.getBlobHandle());
|
||||
}
|
||||
});
|
||||
}
|
||||
return leasedBlobs;
|
||||
}
|
||||
|
||||
private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle,
|
||||
int callingUid, @NonNull String callingPackage, String attributionTag) {
|
||||
synchronized (mBlobsLock) {
|
||||
final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
|
||||
.get(blobHandle);
|
||||
final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
|
||||
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
|
||||
callingPackage, callingUid, attributionTag)) {
|
||||
throw new SecurityException("Caller not allowed to access " + blobHandle
|
||||
@ -699,14 +695,14 @@ public class BlobStoreManagerService extends SystemService {
|
||||
FrameworkStatsLog.BLOB_COMMITTED__RESULT__COUNT_LIMIT_EXCEEDED);
|
||||
break;
|
||||
}
|
||||
final int userId = UserHandle.getUserId(session.getOwnerUid());
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
|
||||
userId);
|
||||
BlobMetadata blob = userBlobs.get(session.getBlobHandle());
|
||||
if (blob == null) {
|
||||
final BlobMetadata blob;
|
||||
final int blobIndex = mBlobsMap.indexOfKey(session.getBlobHandle());
|
||||
if (blobIndex >= 0) {
|
||||
blob = mBlobsMap.valueAt(blobIndex);
|
||||
} else {
|
||||
blob = new BlobMetadata(mContext, session.getSessionId(),
|
||||
session.getBlobHandle(), userId);
|
||||
addBlobForUserLocked(blob, userBlobs);
|
||||
session.getBlobHandle());
|
||||
addBlobLocked(blob);
|
||||
}
|
||||
final Committer existingCommitter = blob.getExistingCommitter(
|
||||
session.getOwnerPackageName(), session.getOwnerUid());
|
||||
@ -738,7 +734,7 @@ public class BlobStoreManagerService extends SystemService {
|
||||
// But if it is a recommit, just leave it as is.
|
||||
if (session.getSessionId() == blob.getBlobId()) {
|
||||
deleteBlobLocked(blob);
|
||||
userBlobs.remove(blob.getBlobHandle());
|
||||
mBlobsMap.remove(blob.getBlobHandle());
|
||||
}
|
||||
}
|
||||
// Delete redundant data from recommits.
|
||||
@ -874,13 +870,10 @@ public class BlobStoreManagerService extends SystemService {
|
||||
out.startTag(null, TAG_BLOBS);
|
||||
XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
|
||||
|
||||
for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
|
||||
for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
|
||||
out.startTag(null, TAG_BLOB);
|
||||
userBlobs.valueAt(j).writeToXml(out);
|
||||
out.endTag(null, TAG_BLOB);
|
||||
}
|
||||
for (int i = 0, count = mBlobsMap.size(); i < count; ++i) {
|
||||
out.startTag(null, TAG_BLOB);
|
||||
mBlobsMap.valueAt(i).writeToXml(out);
|
||||
out.endTag(null, TAG_BLOB);
|
||||
}
|
||||
|
||||
out.endTag(null, TAG_BLOBS);
|
||||
@ -925,16 +918,21 @@ public class BlobStoreManagerService extends SystemService {
|
||||
if (TAG_BLOB.equals(in.getName())) {
|
||||
final BlobMetadata blobMetadata = BlobMetadata.createFromXml(
|
||||
in, version, mContext);
|
||||
final SparseArray<String> userPackages = allPackages.get(
|
||||
blobMetadata.getUserId());
|
||||
if (userPackages == null) {
|
||||
blobMetadata.getBlobFile().delete();
|
||||
} else {
|
||||
addBlobForUserLocked(blobMetadata, blobMetadata.getUserId());
|
||||
blobMetadata.removeCommittersFromUnknownPkgs(userPackages);
|
||||
blobMetadata.removeLeaseesFromUnknownPkgs(userPackages);
|
||||
}
|
||||
blobMetadata.removeCommittersFromUnknownPkgs(allPackages);
|
||||
blobMetadata.removeLeaseesFromUnknownPkgs(allPackages);
|
||||
mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId());
|
||||
if (version >= XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) {
|
||||
addBlobLocked(blobMetadata);
|
||||
} else {
|
||||
final BlobMetadata existingBlobMetadata = mBlobsMap.get(
|
||||
blobMetadata.getBlobHandle());
|
||||
if (existingBlobMetadata == null) {
|
||||
addBlobLocked(blobMetadata);
|
||||
} else {
|
||||
existingBlobMetadata.addCommittersAndLeasees(blobMetadata);
|
||||
blobMetadata.getBlobFile().delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (LOGV) {
|
||||
@ -977,14 +975,6 @@ public class BlobStoreManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
private int getPackageUid(String packageName, int userId) {
|
||||
final int uid = mPackageManagerInternal.getPackageUid(
|
||||
packageName,
|
||||
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES,
|
||||
userId);
|
||||
return uid;
|
||||
}
|
||||
|
||||
private SparseArray<SparseArray<String>> getAllPackages() {
|
||||
final SparseArray<SparseArray<String>> allPackages = new SparseArray<>();
|
||||
final int[] allUsers = LocalServices.getService(UserManagerInternal.class).getUserIds();
|
||||
@ -1004,7 +994,7 @@ public class BlobStoreManagerService extends SystemService {
|
||||
return allPackages;
|
||||
}
|
||||
|
||||
AtomicFile prepareSessionsIndexFile() {
|
||||
private AtomicFile prepareSessionsIndexFile() {
|
||||
final File file = BlobStoreConfig.prepareSessionIndexFile();
|
||||
if (file == null) {
|
||||
return null;
|
||||
@ -1012,7 +1002,7 @@ public class BlobStoreManagerService extends SystemService {
|
||||
return new AtomicFile(file, "session_index" /* commitLogTag */);
|
||||
}
|
||||
|
||||
AtomicFile prepareBlobsIndexFile() {
|
||||
private AtomicFile prepareBlobsIndexFile() {
|
||||
final File file = BlobStoreConfig.prepareBlobsIndexFile();
|
||||
if (file == null) {
|
||||
return null;
|
||||
@ -1037,9 +1027,7 @@ public class BlobStoreManagerService extends SystemService {
|
||||
writeBlobSessionsAsync();
|
||||
|
||||
// Remove the package from the committer and leasee list
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
|
||||
getUserBlobsLocked(UserHandle.getUserId(uid));
|
||||
userBlobs.entrySet().removeIf(entry -> {
|
||||
mBlobsMap.entrySet().removeIf(entry -> {
|
||||
final BlobMetadata blobMetadata = entry.getValue();
|
||||
final boolean isACommitter = blobMetadata.isACommitter(packageName, uid);
|
||||
if (isACommitter) {
|
||||
@ -1074,14 +1062,15 @@ public class BlobStoreManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
|
||||
mBlobsMap.removeReturnOld(userId);
|
||||
if (userBlobs != null) {
|
||||
for (int i = 0, count = userBlobs.size(); i < count; ++i) {
|
||||
final BlobMetadata blobMetadata = userBlobs.valueAt(i);
|
||||
mBlobsMap.entrySet().removeIf(entry -> {
|
||||
final BlobMetadata blobMetadata = entry.getValue();
|
||||
blobMetadata.removeDataForUser(userId);
|
||||
if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
|
||||
deleteBlobLocked(blobMetadata);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (LOGV) {
|
||||
Slog.v(TAG, "Removed blobs data in user " + userId);
|
||||
}
|
||||
@ -1114,22 +1103,19 @@ public class BlobStoreManagerService extends SystemService {
|
||||
}
|
||||
|
||||
// Cleanup any stale blobs.
|
||||
for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
|
||||
userBlobs.entrySet().removeIf(entry -> {
|
||||
final BlobMetadata blobMetadata = entry.getValue();
|
||||
mBlobsMap.entrySet().removeIf(entry -> {
|
||||
final BlobMetadata blobMetadata = entry.getValue();
|
||||
|
||||
// Remove expired leases
|
||||
blobMetadata.removeExpiredLeases();
|
||||
// Remove expired leases
|
||||
blobMetadata.removeExpiredLeases();
|
||||
|
||||
if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
|
||||
deleteBlobLocked(blobMetadata);
|
||||
deletedBlobIds.add(blobMetadata.getBlobId());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
|
||||
deleteBlobLocked(blobMetadata);
|
||||
deletedBlobIds.add(blobMetadata.getBlobId());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
writeBlobsInfoAsync();
|
||||
|
||||
// Cleanup any stale sessions.
|
||||
@ -1195,34 +1181,34 @@ public class BlobStoreManagerService extends SystemService {
|
||||
|
||||
void runClearAllBlobs(@UserIdInt int userId) {
|
||||
synchronized (mBlobsLock) {
|
||||
for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
|
||||
final int blobUserId = mBlobsMap.keyAt(i);
|
||||
if (userId != UserHandle.USER_ALL && userId != blobUserId) {
|
||||
continue;
|
||||
mBlobsMap.entrySet().removeIf(entry -> {
|
||||
final BlobMetadata blobMetadata = entry.getValue();
|
||||
if (userId == UserHandle.USER_ALL) {
|
||||
mActiveBlobIds.remove(blobMetadata.getBlobId());
|
||||
return true;
|
||||
}
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
|
||||
for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
|
||||
mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId());
|
||||
blobMetadata.removeDataForUser(userId);
|
||||
if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) {
|
||||
mActiveBlobIds.remove(blobMetadata.getBlobId());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (userId == UserHandle.USER_ALL) {
|
||||
mBlobsMap.clear();
|
||||
} else {
|
||||
mBlobsMap.remove(userId);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
writeBlobsInfoAsync();
|
||||
}
|
||||
}
|
||||
|
||||
void deleteBlob(@NonNull BlobHandle blobHandle, @UserIdInt int userId) {
|
||||
synchronized (mBlobsLock) {
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
|
||||
final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
|
||||
final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
|
||||
if (blobMetadata == null) {
|
||||
return;
|
||||
}
|
||||
deleteBlobLocked(blobMetadata);
|
||||
userBlobs.remove(blobHandle);
|
||||
blobMetadata.removeDataForUser(userId);
|
||||
if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) {
|
||||
deleteBlobLocked(blobMetadata);
|
||||
mBlobsMap.remove(blobHandle);
|
||||
}
|
||||
writeBlobsInfoAsync();
|
||||
}
|
||||
}
|
||||
@ -1235,11 +1221,12 @@ public class BlobStoreManagerService extends SystemService {
|
||||
|
||||
boolean isBlobAvailable(long blobId, int userId) {
|
||||
synchronized (mBlobsLock) {
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
|
||||
for (BlobMetadata blobMetadata : userBlobs.values()) {
|
||||
if (blobMetadata.getBlobId() == blobId) {
|
||||
return true;
|
||||
for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) {
|
||||
final BlobMetadata blobMetadata = mBlobsMap.valueAt(i);
|
||||
if (blobMetadata.getBlobId() != blobId) {
|
||||
continue;
|
||||
}
|
||||
return blobMetadata.hasACommitterInUser(userId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1274,27 +1261,22 @@ public class BlobStoreManagerService extends SystemService {
|
||||
|
||||
@GuardedBy("mBlobsLock")
|
||||
private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
|
||||
for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
|
||||
final int userId = mBlobsMap.keyAt(i);
|
||||
if (!dumpArgs.shouldDumpUser(userId)) {
|
||||
fout.println("List of blobs (" + mBlobsMap.size() + "):");
|
||||
fout.increaseIndent();
|
||||
for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) {
|
||||
final BlobMetadata blobMetadata = mBlobsMap.valueAt(i);
|
||||
if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) {
|
||||
continue;
|
||||
}
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
|
||||
fout.println("List of blobs in user #"
|
||||
+ userId + " (" + userBlobs.size() + "):");
|
||||
fout.println("Blob #" + blobMetadata.getBlobId());
|
||||
fout.increaseIndent();
|
||||
for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
|
||||
final BlobMetadata blobMetadata = userBlobs.valueAt(j);
|
||||
if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) {
|
||||
continue;
|
||||
}
|
||||
fout.println("Blob #" + blobMetadata.getBlobId());
|
||||
fout.increaseIndent();
|
||||
blobMetadata.dump(fout, dumpArgs);
|
||||
fout.decreaseIndent();
|
||||
}
|
||||
blobMetadata.dump(fout, dumpArgs);
|
||||
fout.decreaseIndent();
|
||||
}
|
||||
if (mBlobsMap.isEmpty()) {
|
||||
fout.println("<empty>");
|
||||
}
|
||||
fout.decreaseIndent();
|
||||
}
|
||||
|
||||
private class BlobStorageStatsAugmenter implements StorageStatsAugmenter {
|
||||
@ -1308,13 +1290,12 @@ public class BlobStoreManagerService extends SystemService {
|
||||
}
|
||||
}, userId);
|
||||
|
||||
forEachBlobInUser(blobMetadata -> {
|
||||
if (blobMetadata.isALeasee(packageName)) {
|
||||
if (!blobMetadata.hasOtherLeasees(packageName) || !callerHasStatsPermission) {
|
||||
blobsDataSize.getAndAdd(blobMetadata.getSize());
|
||||
}
|
||||
forEachBlob(blobMetadata -> {
|
||||
if (blobMetadata.shouldAttributeToLeasee(packageName, userId,
|
||||
callerHasStatsPermission)) {
|
||||
blobsDataSize.getAndAdd(blobMetadata.getSize());
|
||||
}
|
||||
}, userId);
|
||||
});
|
||||
|
||||
stats.dataSize += blobsDataSize.get();
|
||||
}
|
||||
@ -1330,13 +1311,12 @@ public class BlobStoreManagerService extends SystemService {
|
||||
}
|
||||
}, userId);
|
||||
|
||||
forEachBlobInUser(blobMetadata -> {
|
||||
if (blobMetadata.isALeasee(uid)) {
|
||||
if (!blobMetadata.hasOtherLeasees(uid) || !callerHasStatsPermission) {
|
||||
blobsDataSize.getAndAdd(blobMetadata.getSize());
|
||||
}
|
||||
forEachBlob(blobMetadata -> {
|
||||
if (blobMetadata.shouldAttributeToLeasee(uid,
|
||||
callerHasStatsPermission)) {
|
||||
blobsDataSize.getAndAdd(blobMetadata.getSize());
|
||||
}
|
||||
}, userId);
|
||||
});
|
||||
|
||||
stats.dataSize += blobsDataSize.get();
|
||||
}
|
||||
@ -1352,13 +1332,26 @@ public class BlobStoreManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
private void forEachBlobInUser(Consumer<BlobMetadata> consumer, int userId) {
|
||||
synchronized (mBlobsLock) {
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
|
||||
for (int i = 0, count = userBlobs.size(); i < count; ++i) {
|
||||
final BlobMetadata blobMetadata = userBlobs.valueAt(i);
|
||||
consumer.accept(blobMetadata);
|
||||
}
|
||||
private void forEachBlob(Consumer<BlobMetadata> consumer) {
|
||||
synchronized (mBlobsMap) {
|
||||
forEachBlobLocked(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mBlobsMap")
|
||||
private void forEachBlobLocked(Consumer<BlobMetadata> consumer) {
|
||||
for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) {
|
||||
final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx);
|
||||
consumer.accept(blobMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mBlobsMap")
|
||||
private void forEachBlobLocked(BiConsumer<BlobHandle, BlobMetadata> consumer) {
|
||||
for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) {
|
||||
final BlobHandle blobHandle = mBlobsMap.keyAt(blobIdx);
|
||||
final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx);
|
||||
consumer.accept(blobHandle, blobMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1886,15 +1879,7 @@ public class BlobStoreManagerService extends SystemService {
|
||||
}
|
||||
|
||||
private int pullBlobData(int atomTag, List<StatsEvent> data) {
|
||||
synchronized (mBlobsLock) {
|
||||
for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
|
||||
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
|
||||
for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
|
||||
final BlobMetadata blob = userBlobs.valueAt(j);
|
||||
data.add(blob.dumpAsStatsEvent(atomTag));
|
||||
}
|
||||
}
|
||||
}
|
||||
forEachBlob(blobMetadata -> data.add(blobMetadata.dumpAsStatsEvent(atomTag)));
|
||||
return StatsManager.PULL_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -56,12 +56,12 @@ import android.os.storage.StorageManager;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.util.ExceptionUtils;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
import com.android.server.blob.BlobStoreManagerService.DumpArgs;
|
||||
|
@ -35,7 +35,6 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.UserHandle;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.LongSparseArray;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
@ -68,7 +67,6 @@ public class BlobStoreManagerServiceTest {
|
||||
private File mBlobsDir;
|
||||
|
||||
private LongSparseArray<BlobStoreSession> mUserSessions;
|
||||
private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs;
|
||||
|
||||
private static final String TEST_PKG1 = "com.example1";
|
||||
private static final String TEST_PKG2 = "com.example2";
|
||||
@ -99,10 +97,8 @@ public class BlobStoreManagerServiceTest {
|
||||
mHandler = new TestHandler(Looper.getMainLooper());
|
||||
mService = new BlobStoreManagerService(mContext, new TestInjector());
|
||||
mUserSessions = new LongSparseArray<>();
|
||||
mUserBlobs = new ArrayMap<>();
|
||||
|
||||
mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId());
|
||||
mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId());
|
||||
}
|
||||
|
||||
@After
|
||||
@ -147,7 +143,7 @@ public class BlobStoreManagerServiceTest {
|
||||
final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1,
|
||||
blobHandle1, true /* hasLeases */);
|
||||
doReturn(true).when(blobMetadata1).isACommitter(TEST_PKG1, TEST_UID1);
|
||||
mUserBlobs.put(blobHandle1, blobMetadata1);
|
||||
addBlob(blobHandle1, blobMetadata1);
|
||||
|
||||
final long blobId2 = 347;
|
||||
final File blobFile2 = mock(File.class);
|
||||
@ -156,7 +152,7 @@ public class BlobStoreManagerServiceTest {
|
||||
final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2,
|
||||
blobHandle2, false /* hasLeases */);
|
||||
doReturn(false).when(blobMetadata2).isACommitter(TEST_PKG1, TEST_UID1);
|
||||
mUserBlobs.put(blobHandle2, blobMetadata2);
|
||||
addBlob(blobHandle2, blobMetadata2);
|
||||
|
||||
final long blobId3 = 49875;
|
||||
final File blobFile3 = mock(File.class);
|
||||
@ -165,7 +161,7 @@ public class BlobStoreManagerServiceTest {
|
||||
final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3,
|
||||
blobHandle3, true /* hasLeases */);
|
||||
doReturn(true).when(blobMetadata3).isACommitter(TEST_PKG1, TEST_UID1);
|
||||
mUserBlobs.put(blobHandle3, blobMetadata3);
|
||||
addBlob(blobHandle3, blobMetadata3);
|
||||
|
||||
mService.addActiveIdsForTest(sessionId1, sessionId2, sessionId3, sessionId4,
|
||||
blobId1, blobId2, blobId3);
|
||||
@ -197,10 +193,10 @@ public class BlobStoreManagerServiceTest {
|
||||
verify(blobMetadata2).destroy();
|
||||
verify(blobMetadata3).destroy();
|
||||
|
||||
assertThat(mUserBlobs.size()).isEqualTo(1);
|
||||
assertThat(mUserBlobs.get(blobHandle1)).isNotNull();
|
||||
assertThat(mUserBlobs.get(blobHandle2)).isNull();
|
||||
assertThat(mUserBlobs.get(blobHandle3)).isNull();
|
||||
assertThat(mService.getBlobsCountForTest()).isEqualTo(1);
|
||||
assertThat(mService.getBlobForTest(blobHandle1)).isNotNull();
|
||||
assertThat(mService.getBlobForTest(blobHandle2)).isNull();
|
||||
assertThat(mService.getBlobForTest(blobHandle3)).isNull();
|
||||
|
||||
assertThat(mService.getActiveIdsForTest()).containsExactly(
|
||||
sessionId2, sessionId3, blobId1);
|
||||
@ -293,7 +289,7 @@ public class BlobStoreManagerServiceTest {
|
||||
"label1", System.currentTimeMillis() - 2000, "tag1");
|
||||
final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, blobHandle1,
|
||||
true /* hasLeases */);
|
||||
mUserBlobs.put(blobHandle1, blobMetadata1);
|
||||
addBlob(blobHandle1, blobMetadata1);
|
||||
|
||||
final long blobId2 = 78974;
|
||||
final File blobFile2 = mock(File.class);
|
||||
@ -301,7 +297,7 @@ public class BlobStoreManagerServiceTest {
|
||||
"label2", System.currentTimeMillis() + 30000, "tag2");
|
||||
final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, blobHandle2,
|
||||
true /* hasLeases */);
|
||||
mUserBlobs.put(blobHandle2, blobMetadata2);
|
||||
addBlob(blobHandle2, blobMetadata2);
|
||||
|
||||
final long blobId3 = 97;
|
||||
final File blobFile3 = mock(File.class);
|
||||
@ -309,7 +305,7 @@ public class BlobStoreManagerServiceTest {
|
||||
"label3", System.currentTimeMillis() + 4400000, "tag3");
|
||||
final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, blobHandle3,
|
||||
false /* hasLeases */);
|
||||
mUserBlobs.put(blobHandle3, blobMetadata3);
|
||||
addBlob(blobHandle3, blobMetadata3);
|
||||
|
||||
mService.addActiveIdsForTest(blobId1, blobId2, blobId3);
|
||||
|
||||
@ -321,8 +317,8 @@ public class BlobStoreManagerServiceTest {
|
||||
verify(blobMetadata2, never()).destroy();
|
||||
verify(blobMetadata3).destroy();
|
||||
|
||||
assertThat(mUserBlobs.size()).isEqualTo(1);
|
||||
assertThat(mUserBlobs.get(blobHandle2)).isNotNull();
|
||||
assertThat(mService.getBlobsCountForTest()).isEqualTo(1);
|
||||
assertThat(mService.getBlobForTest(blobHandle2)).isNotNull();
|
||||
|
||||
assertThat(mService.getActiveIdsForTest()).containsExactly(blobId2);
|
||||
assertThat(mService.getKnownIdsForTest()).containsExactly(blobId1, blobId2, blobId3);
|
||||
@ -336,21 +332,21 @@ public class BlobStoreManagerServiceTest {
|
||||
doReturn(size1).when(blobMetadata1).getSize();
|
||||
doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG1, TEST_UID1);
|
||||
doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG2, TEST_UID2);
|
||||
mUserBlobs.put(mock(BlobHandle.class), blobMetadata1);
|
||||
addBlob(mock(BlobHandle.class), blobMetadata1);
|
||||
|
||||
final BlobMetadata blobMetadata2 = mock(BlobMetadata.class);
|
||||
final long size2 = 89475;
|
||||
doReturn(size2).when(blobMetadata2).getSize();
|
||||
doReturn(false).when(blobMetadata2).isALeasee(TEST_PKG1, TEST_UID1);
|
||||
doReturn(true).when(blobMetadata2).isALeasee(TEST_PKG2, TEST_UID2);
|
||||
mUserBlobs.put(mock(BlobHandle.class), blobMetadata2);
|
||||
addBlob(mock(BlobHandle.class), blobMetadata2);
|
||||
|
||||
final BlobMetadata blobMetadata3 = mock(BlobMetadata.class);
|
||||
final long size3 = 328732;
|
||||
doReturn(size3).when(blobMetadata3).getSize();
|
||||
doReturn(true).when(blobMetadata3).isALeasee(TEST_PKG1, TEST_UID1);
|
||||
doReturn(false).when(blobMetadata3).isALeasee(TEST_PKG2, TEST_UID2);
|
||||
mUserBlobs.put(mock(BlobHandle.class), blobMetadata3);
|
||||
addBlob(mock(BlobHandle.class), blobMetadata3);
|
||||
|
||||
// Verify usage is calculated correctly
|
||||
assertThat(mService.getTotalUsageBytesLocked(TEST_UID1, TEST_PKG1))
|
||||
@ -388,6 +384,11 @@ public class BlobStoreManagerServiceTest {
|
||||
return blobMetadata;
|
||||
}
|
||||
|
||||
private void addBlob(BlobHandle blobHandle, BlobMetadata blobMetadata) {
|
||||
doReturn(blobHandle).when(blobMetadata).getBlobHandle();
|
||||
mService.addBlobLocked(blobMetadata);
|
||||
}
|
||||
|
||||
private class TestHandler extends Handler {
|
||||
TestHandler(Looper looper) {
|
||||
super(looper);
|
||||
|
@ -23,6 +23,7 @@ import android.app.blob.BlobStoreManager;
|
||||
import android.app.blob.LeaseInfo;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.FileUtils;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
@ -56,6 +57,16 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeToSession(BlobStoreManager.Session session, ParcelFileDescriptor input)
|
||||
throws IOException {
|
||||
try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) {
|
||||
try (FileOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(
|
||||
session.openWrite(0, -1))) {
|
||||
FileUtils.copy(in, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeToSession(BlobStoreManager.Session session, ParcelFileDescriptor input,
|
||||
long lengthBytes) throws IOException {
|
||||
try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user