Merge "Add a limit on how much data an app can acquire a lease on." into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
cd629dd39a
@@ -242,7 +242,7 @@ class BlobMetadata {
|
|||||||
return hasOtherLeasees(null, uid);
|
return hasOtherLeasees(null, uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isALeasee(@Nullable String packageName, int uid) {
|
boolean isALeasee(@Nullable String packageName, int uid) {
|
||||||
synchronized (mMetadataLock) {
|
synchronized (mMetadataLock) {
|
||||||
// Check if the package is a leasee of the data blob.
|
// Check if the package is a leasee of the data blob.
|
||||||
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
|
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
|
||||||
|
|||||||
@@ -15,12 +15,23 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.server.blob;
|
package com.android.server.blob;
|
||||||
|
|
||||||
|
import static android.provider.DeviceConfig.NAMESPACE_BLOBSTORE;
|
||||||
|
import static android.text.format.Formatter.FLAG_IEC_UNITS;
|
||||||
|
import static android.text.format.Formatter.formatFileSize;
|
||||||
|
import static android.util.TimeUtils.formatDuration;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.provider.DeviceConfig;
|
||||||
|
import android.provider.DeviceConfig.Properties;
|
||||||
|
import android.util.DataUnit;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import com.android.internal.util.IndentingPrintWriter;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -55,6 +66,76 @@ class BlobStoreConfig {
|
|||||||
*/
|
*/
|
||||||
public static final long SESSION_EXPIRY_TIMEOUT_MILLIS = TimeUnit.DAYS.toMillis(7);
|
public static final long SESSION_EXPIRY_TIMEOUT_MILLIS = TimeUnit.DAYS.toMillis(7);
|
||||||
|
|
||||||
|
public static class DeviceConfigProperties {
|
||||||
|
/**
|
||||||
|
* Denotes how low the limit for the amount of data, that an app will be allowed to acquire
|
||||||
|
* a lease on, can be.
|
||||||
|
*/
|
||||||
|
public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
|
||||||
|
"total_bytes_per_app_limit_floor";
|
||||||
|
public static final long DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
|
||||||
|
DataUnit.MEBIBYTES.toBytes(300); // 300 MiB
|
||||||
|
public static long TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
|
||||||
|
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denotes the maximum amount of data an app can acquire a lease on, in terms of fraction
|
||||||
|
* of total disk space.
|
||||||
|
*/
|
||||||
|
public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
|
||||||
|
"total_bytes_per_app_limit_fraction";
|
||||||
|
public static final float DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION = 0.01f;
|
||||||
|
public static float TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
|
||||||
|
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION;
|
||||||
|
|
||||||
|
static void refresh(Properties properties) {
|
||||||
|
if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
properties.getKeyset().forEach(key -> {
|
||||||
|
switch (key) {
|
||||||
|
case KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR:
|
||||||
|
TOTAL_BYTES_PER_APP_LIMIT_FLOOR = properties.getLong(key,
|
||||||
|
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR);
|
||||||
|
break;
|
||||||
|
case KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION:
|
||||||
|
TOTAL_BYTES_PER_APP_LIMIT_FRACTION = properties.getFloat(key,
|
||||||
|
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Slog.wtf(TAG, "Unknown key in device config properties: " + key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dump(IndentingPrintWriter fout, Context context) {
|
||||||
|
final String dumpFormat = "%s: [cur: %s, def: %s]";
|
||||||
|
fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
|
||||||
|
formatFileSize(context, TOTAL_BYTES_PER_APP_LIMIT_FLOOR, FLAG_IEC_UNITS),
|
||||||
|
formatFileSize(context, DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
|
||||||
|
FLAG_IEC_UNITS)));
|
||||||
|
fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
|
||||||
|
TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
|
||||||
|
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initialize(Context context) {
|
||||||
|
DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_BLOBSTORE,
|
||||||
|
context.getMainExecutor(),
|
||||||
|
properties -> DeviceConfigProperties.refresh(properties));
|
||||||
|
DeviceConfigProperties.refresh(DeviceConfig.getProperties(NAMESPACE_BLOBSTORE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum amount of data that an app can acquire a lease on.
|
||||||
|
*/
|
||||||
|
public static long getAppDataBytesLimit() {
|
||||||
|
final long totalBytesLimit = (long) (Environment.getDataSystemDirectory().getTotalSpace()
|
||||||
|
* DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FRACTION);
|
||||||
|
return Math.max(DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FLOOR, totalBytesLimit);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static File prepareBlobFile(long sessionId) {
|
public static File prepareBlobFile(long sessionId) {
|
||||||
final File blobsDir = prepareBlobsDir();
|
final File blobsDir = prepareBlobsDir();
|
||||||
@@ -123,4 +204,21 @@ class BlobStoreConfig {
|
|||||||
public static File getBlobStoreRootDir() {
|
public static File getBlobStoreRootDir() {
|
||||||
return new File(Environment.getDataSystemDirectory(), ROOT_DIR_NAME);
|
return new File(Environment.getDataSystemDirectory(), ROOT_DIR_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void dump(IndentingPrintWriter fout, Context context) {
|
||||||
|
fout.println("XML current version: " + XML_VERSION_CURRENT);
|
||||||
|
|
||||||
|
fout.println("Idle job ID: " + IDLE_JOB_ID);
|
||||||
|
fout.println("Idle job period: " + formatDuration(IDLE_JOB_PERIOD_MILLIS));
|
||||||
|
|
||||||
|
fout.println("Session expiry timeout: " + formatDuration(SESSION_EXPIRY_TIMEOUT_MILLIS));
|
||||||
|
|
||||||
|
fout.println("Total bytes per app limit: " + formatFileSize(context,
|
||||||
|
getAppDataBytesLimit(), FLAG_IEC_UNITS));
|
||||||
|
|
||||||
|
fout.println("Device config properties:");
|
||||||
|
fout.increaseIndent();
|
||||||
|
DeviceConfigProperties.dump(fout, context);
|
||||||
|
fout.decreaseIndent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,7 +188,9 @@ public class BlobStoreManagerService extends SystemService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBootPhase(int phase) {
|
public void onBootPhase(int phase) {
|
||||||
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
|
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
|
||||||
|
BlobStoreConfig.initialize(mContext);
|
||||||
|
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
|
||||||
synchronized (mBlobsLock) {
|
synchronized (mBlobsLock) {
|
||||||
final SparseArray<SparseArray<String>> allPackages = getAllPackages();
|
final SparseArray<SparseArray<String>> allPackages = getAllPackages();
|
||||||
readBlobSessionsLocked(allPackages);
|
readBlobSessionsLocked(allPackages);
|
||||||
@@ -381,6 +383,11 @@ public class BlobStoreManagerService extends SystemService {
|
|||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Lease expiry cannot be later than blobs expiry time");
|
"Lease expiry cannot be later than blobs expiry time");
|
||||||
}
|
}
|
||||||
|
if (getTotalUsageBytesLocked(callingUid, callingPackage)
|
||||||
|
+ blobMetadata.getSize() > BlobStoreConfig.getAppDataBytesLimit()) {
|
||||||
|
throw new IllegalStateException("Total amount of data with an active lease"
|
||||||
|
+ " is exceeding the max limit");
|
||||||
|
}
|
||||||
blobMetadata.addLeasee(callingPackage, callingUid,
|
blobMetadata.addLeasee(callingPackage, callingUid,
|
||||||
descriptionResId, description, leaseExpiryTimeMillis);
|
descriptionResId, description, leaseExpiryTimeMillis);
|
||||||
if (LOGV) {
|
if (LOGV) {
|
||||||
@@ -391,6 +398,18 @@ public class BlobStoreManagerService extends SystemService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@GuardedBy("mBlobsLock")
|
||||||
|
long getTotalUsageBytesLocked(int callingUid, String callingPackage) {
|
||||||
|
final AtomicLong totalBytes = new AtomicLong(0);
|
||||||
|
forEachBlobInUser((blobMetadata) -> {
|
||||||
|
if (blobMetadata.isALeasee(callingPackage, callingUid)) {
|
||||||
|
totalBytes.getAndAdd(blobMetadata.getSize());
|
||||||
|
}
|
||||||
|
}, UserHandle.getUserId(callingUid));
|
||||||
|
return totalBytes.get();
|
||||||
|
}
|
||||||
|
|
||||||
private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
|
private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
|
||||||
String callingPackage) {
|
String callingPackage) {
|
||||||
synchronized (mBlobsLock) {
|
synchronized (mBlobsLock) {
|
||||||
@@ -1236,8 +1255,10 @@ public class BlobStoreManagerService extends SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
synchronized (mBlobsLock) {
|
synchronized (mBlobsLock) {
|
||||||
|
if (dumpArgs.shouldDumpAllSections()) {
|
||||||
fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId);
|
fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId);
|
||||||
fout.println();
|
fout.println();
|
||||||
|
}
|
||||||
|
|
||||||
if (dumpArgs.shouldDumpSessions()) {
|
if (dumpArgs.shouldDumpSessions()) {
|
||||||
dumpSessionsLocked(fout, dumpArgs);
|
dumpSessionsLocked(fout, dumpArgs);
|
||||||
@@ -1248,6 +1269,14 @@ public class BlobStoreManagerService extends SystemService {
|
|||||||
fout.println();
|
fout.println();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dumpArgs.shouldDumpConfig()) {
|
||||||
|
fout.println("BlobStore config:");
|
||||||
|
fout.increaseIndent();
|
||||||
|
BlobStoreConfig.dump(fout, mContext);
|
||||||
|
fout.decreaseIndent();
|
||||||
|
fout.println();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1260,14 +1289,16 @@ public class BlobStoreManagerService extends SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static final class DumpArgs {
|
static final class DumpArgs {
|
||||||
|
private static final int FLAG_DUMP_SESSIONS = 1 << 0;
|
||||||
|
private static final int FLAG_DUMP_BLOBS = 1 << 1;
|
||||||
|
private static final int FLAG_DUMP_CONFIG = 1 << 2;
|
||||||
|
|
||||||
|
private int mSelectedSectionFlags;
|
||||||
private boolean mDumpFull;
|
private boolean mDumpFull;
|
||||||
private final ArrayList<String> mDumpPackages = new ArrayList<>();
|
private final ArrayList<String> mDumpPackages = new ArrayList<>();
|
||||||
private final ArrayList<Integer> mDumpUids = new ArrayList<>();
|
private final ArrayList<Integer> mDumpUids = new ArrayList<>();
|
||||||
private final ArrayList<Integer> mDumpUserIds = new ArrayList<>();
|
private final ArrayList<Integer> mDumpUserIds = new ArrayList<>();
|
||||||
private final ArrayList<Long> mDumpBlobIds = new ArrayList<>();
|
private final ArrayList<Long> mDumpBlobIds = new ArrayList<>();
|
||||||
private boolean mDumpOnlySelectedSections;
|
|
||||||
private boolean mDumpSessions;
|
|
||||||
private boolean mDumpBlobs;
|
|
||||||
private boolean mDumpHelp;
|
private boolean mDumpHelp;
|
||||||
|
|
||||||
public boolean shouldDumpSession(String packageName, int uid, long blobId) {
|
public boolean shouldDumpSession(String packageName, int uid, long blobId) {
|
||||||
@@ -1286,18 +1317,41 @@ public class BlobStoreManagerService extends SystemService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean shouldDumpAllSections() {
|
||||||
|
return mSelectedSectionFlags == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void allowDumpSessions() {
|
||||||
|
mSelectedSectionFlags |= FLAG_DUMP_SESSIONS;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean shouldDumpSessions() {
|
public boolean shouldDumpSessions() {
|
||||||
if (!mDumpOnlySelectedSections) {
|
if (shouldDumpAllSections()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return mDumpSessions;
|
return (mSelectedSectionFlags & FLAG_DUMP_SESSIONS) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void allowDumpBlobs() {
|
||||||
|
mSelectedSectionFlags |= FLAG_DUMP_BLOBS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean shouldDumpBlobs() {
|
public boolean shouldDumpBlobs() {
|
||||||
if (!mDumpOnlySelectedSections) {
|
if (shouldDumpAllSections()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return mDumpBlobs;
|
return (mSelectedSectionFlags & FLAG_DUMP_BLOBS) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void allowDumpConfig() {
|
||||||
|
mSelectedSectionFlags |= FLAG_DUMP_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldDumpConfig() {
|
||||||
|
if (shouldDumpAllSections()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (mSelectedSectionFlags & FLAG_DUMP_CONFIG) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean shouldDumpBlob(long blobId) {
|
public boolean shouldDumpBlob(long blobId) {
|
||||||
@@ -1334,11 +1388,11 @@ public class BlobStoreManagerService extends SystemService {
|
|||||||
dumpArgs.mDumpFull = true;
|
dumpArgs.mDumpFull = true;
|
||||||
}
|
}
|
||||||
} else if ("--sessions".equals(opt)) {
|
} else if ("--sessions".equals(opt)) {
|
||||||
dumpArgs.mDumpOnlySelectedSections = true;
|
dumpArgs.allowDumpSessions();
|
||||||
dumpArgs.mDumpSessions = true;
|
|
||||||
} else if ("--blobs".equals(opt)) {
|
} else if ("--blobs".equals(opt)) {
|
||||||
dumpArgs.mDumpOnlySelectedSections = true;
|
dumpArgs.allowDumpBlobs();
|
||||||
dumpArgs.mDumpBlobs = true;
|
} else if ("--config".equals(opt)) {
|
||||||
|
dumpArgs.allowDumpConfig();
|
||||||
} else if ("--package".equals(opt) || "-p".equals(opt)) {
|
} else if ("--package".equals(opt) || "-p".equals(opt)) {
|
||||||
dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName"));
|
dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName"));
|
||||||
} else if ("--uid".equals(opt) || "-u".equals(opt)) {
|
} else if ("--uid".equals(opt) || "-u".equals(opt)) {
|
||||||
@@ -1397,6 +1451,8 @@ public class BlobStoreManagerService extends SystemService {
|
|||||||
printWithIndent(pw, "Dump only the sessions info");
|
printWithIndent(pw, "Dump only the sessions info");
|
||||||
pw.println("--blobs");
|
pw.println("--blobs");
|
||||||
printWithIndent(pw, "Dump only the committed blobs info");
|
printWithIndent(pw, "Dump only the committed blobs info");
|
||||||
|
pw.println("--config");
|
||||||
|
printWithIndent(pw, "Dump only the config values");
|
||||||
pw.println("--package | -p [package-name]");
|
pw.println("--package | -p [package-name]");
|
||||||
printWithIndent(pw, "Dump blobs info associated with the given package");
|
printWithIndent(pw, "Dump blobs info associated with the given package");
|
||||||
pw.println("--uid | -u [uid]");
|
pw.println("--uid | -u [uid]");
|
||||||
|
|||||||
@@ -9131,6 +9131,7 @@ package android.provider {
|
|||||||
field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
|
field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
|
||||||
field public static final String NAMESPACE_AUTOFILL = "autofill";
|
field public static final String NAMESPACE_AUTOFILL = "autofill";
|
||||||
field public static final String NAMESPACE_BIOMETRICS = "biometrics";
|
field public static final String NAMESPACE_BIOMETRICS = "biometrics";
|
||||||
|
field public static final String NAMESPACE_BLOBSTORE = "blobstore";
|
||||||
field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
|
field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
|
||||||
field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
|
field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
|
||||||
field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
|
field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
|
||||||
|
|||||||
@@ -110,6 +110,14 @@ public final class DeviceConfig {
|
|||||||
@TestApi
|
@TestApi
|
||||||
public static final String NAMESPACE_AUTOFILL = "autofill";
|
public static final String NAMESPACE_AUTOFILL = "autofill";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespace for blobstore feature that allows apps to share data blobs.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@SystemApi
|
||||||
|
public static final String NAMESPACE_BLOBSTORE = "blobstore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Namespace for all networking connectivity related features.
|
* Namespace for all networking connectivity related features.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ message ConfigSettingsProto {
|
|||||||
repeated SettingProto systemui_settings = 20;
|
repeated SettingProto systemui_settings = 20;
|
||||||
repeated SettingProto telephony_settings = 21;
|
repeated SettingProto telephony_settings = 21;
|
||||||
repeated SettingProto textclassifier_settings = 22;
|
repeated SettingProto textclassifier_settings = 22;
|
||||||
|
repeated SettingProto blobstore_settings = 23;
|
||||||
|
|
||||||
message NamespaceProto {
|
message NamespaceProto {
|
||||||
optional string namespace = 1;
|
optional string namespace = 1;
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ class SettingsProtoDumpUtil {
|
|||||||
ConfigSettingsProto.APP_COMPAT_SETTINGS);
|
ConfigSettingsProto.APP_COMPAT_SETTINGS);
|
||||||
namespaceToFieldMap.put(DeviceConfig.NAMESPACE_AUTOFILL,
|
namespaceToFieldMap.put(DeviceConfig.NAMESPACE_AUTOFILL,
|
||||||
ConfigSettingsProto.AUTOFILL_SETTINGS);
|
ConfigSettingsProto.AUTOFILL_SETTINGS);
|
||||||
|
namespaceToFieldMap.put(DeviceConfig.NAMESPACE_BLOBSTORE,
|
||||||
|
ConfigSettingsProto.BLOBSTORE_SETTINGS);
|
||||||
namespaceToFieldMap.put(DeviceConfig.NAMESPACE_CONNECTIVITY,
|
namespaceToFieldMap.put(DeviceConfig.NAMESPACE_CONNECTIVITY,
|
||||||
ConfigSettingsProto.CONNECTIVITY_SETTINGS);
|
ConfigSettingsProto.CONNECTIVITY_SETTINGS);
|
||||||
namespaceToFieldMap.put(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
|
namespaceToFieldMap.put(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.android.server.blob;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.fail;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
import android.provider.DeviceConfig;
|
||||||
|
import android.util.DataUnit;
|
||||||
|
|
||||||
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.server.testables.TestableDeviceConfig;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@SmallTest
|
||||||
|
@Presubmit
|
||||||
|
public class BlobStoreConfigTest {
|
||||||
|
private static final long TIMEOUT_UPDATE_PROPERTIES_MS = 1_000;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TestableDeviceConfig.TestableDeviceConfigRule
|
||||||
|
mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
BlobStoreConfig.initialize(mContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAppDataBytesLimit() throws Exception {
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLOBSTORE,
|
||||||
|
BlobStoreConfig.DeviceConfigProperties.KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
|
||||||
|
String.valueOf(DataUnit.MEBIBYTES.toBytes(1000)),
|
||||||
|
false /* makeDefault */);
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLOBSTORE,
|
||||||
|
BlobStoreConfig.DeviceConfigProperties.KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
|
||||||
|
String.valueOf(0.002f),
|
||||||
|
false /* makeDefault */);
|
||||||
|
waitForListenerToHandle();
|
||||||
|
assertThat(BlobStoreConfig.getAppDataBytesLimit()).isEqualTo(
|
||||||
|
DataUnit.MEBIBYTES.toBytes(1000));
|
||||||
|
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLOBSTORE,
|
||||||
|
BlobStoreConfig.DeviceConfigProperties.KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
|
||||||
|
String.valueOf(DataUnit.MEBIBYTES.toBytes(100)),
|
||||||
|
false /* makeDefault */);
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLOBSTORE,
|
||||||
|
BlobStoreConfig.DeviceConfigProperties.KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
|
||||||
|
String.valueOf(0.1f),
|
||||||
|
false /* makeDefault */);
|
||||||
|
waitForListenerToHandle();
|
||||||
|
final long expectedLimit = (long) (Environment.getDataDirectory().getTotalSpace() * 0.1f);
|
||||||
|
assertThat(BlobStoreConfig.getAppDataBytesLimit()).isEqualTo(expectedLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForListenerToHandle() throws Exception {
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
mContext.getMainExecutor().execute(latch::countDown);
|
||||||
|
if (!latch.await(TIMEOUT_UPDATE_PROPERTIES_MS, TimeUnit.MILLISECONDS)) {
|
||||||
|
fail("Timed out waiting for properties to get updated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -303,6 +303,37 @@ public class BlobStoreManagerServiceTest {
|
|||||||
assertThat(mService.getKnownIdsForTest()).containsExactly(blobId1, blobId2, blobId3);
|
assertThat(mService.getKnownIdsForTest()).containsExactly(blobId1, blobId2, blobId3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetTotalUsageBytes() throws Exception {
|
||||||
|
// Setup blobs
|
||||||
|
final BlobMetadata blobMetadata1 = mock(BlobMetadata.class);
|
||||||
|
final long size1 = 4567;
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Verify usage is calculated correctly
|
||||||
|
assertThat(mService.getTotalUsageBytesLocked(TEST_UID1, TEST_PKG1))
|
||||||
|
.isEqualTo(size1 + size3);
|
||||||
|
assertThat(mService.getTotalUsageBytesLocked(TEST_UID2, TEST_PKG2))
|
||||||
|
.isEqualTo(size1 + size2);
|
||||||
|
}
|
||||||
|
|
||||||
private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid,
|
private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid,
|
||||||
long sessionId, File sessionFile) {
|
long sessionId, File sessionFile) {
|
||||||
return createBlobStoreSessionMock(ownerPackageName, ownerUid, sessionId, sessionFile,
|
return createBlobStoreSessionMock(ownerPackageName, ownerUid, sessionId, sessionFile,
|
||||||
|
|||||||
@@ -24,15 +24,18 @@ import static org.mockito.ArgumentMatchers.anyFloat;
|
|||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.anyLong;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.nullable;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
import android.provider.DeviceConfig.Properties;
|
import android.provider.DeviceConfig.Properties;
|
||||||
|
import android.util.ArrayMap;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
|
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
|
||||||
|
|
||||||
import org.junit.rules.TestRule;
|
import org.junit.rules.TestRule;
|
||||||
|
import org.mockito.ArgumentMatchers;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
@@ -109,6 +112,27 @@ public final class TestableDeviceConfig implements StaticMockFixture {
|
|||||||
String name = invocationOnMock.getArgument(1);
|
String name = invocationOnMock.getArgument(1);
|
||||||
return mKeyValueMap.get(getKey(namespace, name));
|
return mKeyValueMap.get(getKey(namespace, name));
|
||||||
}).when(() -> DeviceConfig.getProperty(anyString(), anyString()));
|
}).when(() -> DeviceConfig.getProperty(anyString(), anyString()));
|
||||||
|
|
||||||
|
doAnswer((Answer<Properties>) invocationOnMock -> {
|
||||||
|
String namespace = invocationOnMock.getArgument(0);
|
||||||
|
final int varargStartIdx = 1;
|
||||||
|
Map<String, String> keyValues = new ArrayMap<>();
|
||||||
|
if (invocationOnMock.getArguments().length == varargStartIdx) {
|
||||||
|
mKeyValueMap.entrySet().forEach(entry -> {
|
||||||
|
Pair<String, String> nameSpaceAndName = getNameSpaceAndName(entry.getKey());
|
||||||
|
if (!nameSpaceAndName.first.equals(namespace)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
keyValues.put(nameSpaceAndName.second.toLowerCase(), entry.getValue());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (int i = varargStartIdx; i < invocationOnMock.getArguments().length; ++i) {
|
||||||
|
String name = invocationOnMock.getArgument(i);
|
||||||
|
keyValues.put(name.toLowerCase(), mKeyValueMap.get(getKey(namespace, name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getProperties(namespace, keyValues);
|
||||||
|
}).when(() -> DeviceConfig.getProperties(anyString(), ArgumentMatchers.<String>any()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,15 +148,25 @@ public final class TestableDeviceConfig implements StaticMockFixture {
|
|||||||
return namespace + "/" + name;
|
return namespace + "/" + name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Pair<String, String> getNameSpaceAndName(String key) {
|
||||||
|
final String[] values = key.split("/");
|
||||||
|
return Pair.create(values[0], values[1]);
|
||||||
|
}
|
||||||
|
|
||||||
private Properties getProperties(String namespace, String name, String value) {
|
private Properties getProperties(String namespace, String name, String value) {
|
||||||
|
return getProperties(namespace, Collections.singletonMap(name.toLowerCase(), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Properties getProperties(String namespace, Map<String, String> keyValues) {
|
||||||
Properties properties = Mockito.mock(Properties.class);
|
Properties properties = Mockito.mock(Properties.class);
|
||||||
when(properties.getNamespace()).thenReturn(namespace);
|
when(properties.getNamespace()).thenReturn(namespace);
|
||||||
when(properties.getKeyset()).thenReturn(Collections.singleton(name));
|
when(properties.getKeyset()).thenReturn(keyValues.keySet());
|
||||||
when(properties.getBoolean(anyString(), anyBoolean())).thenAnswer(
|
when(properties.getBoolean(anyString(), anyBoolean())).thenAnswer(
|
||||||
invocation -> {
|
invocation -> {
|
||||||
String key = invocation.getArgument(0);
|
String key = invocation.getArgument(0);
|
||||||
boolean defaultValue = invocation.getArgument(1);
|
boolean defaultValue = invocation.getArgument(1);
|
||||||
if (name.equalsIgnoreCase(key) && value != null) {
|
final String value = keyValues.get(key.toLowerCase());
|
||||||
|
if (value != null) {
|
||||||
return Boolean.parseBoolean(value);
|
return Boolean.parseBoolean(value);
|
||||||
} else {
|
} else {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
@@ -143,7 +177,8 @@ public final class TestableDeviceConfig implements StaticMockFixture {
|
|||||||
invocation -> {
|
invocation -> {
|
||||||
String key = invocation.getArgument(0);
|
String key = invocation.getArgument(0);
|
||||||
float defaultValue = invocation.getArgument(1);
|
float defaultValue = invocation.getArgument(1);
|
||||||
if (name.equalsIgnoreCase(key) && value != null) {
|
final String value = keyValues.get(key.toLowerCase());
|
||||||
|
if (value != null) {
|
||||||
try {
|
try {
|
||||||
return Float.parseFloat(value);
|
return Float.parseFloat(value);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
@@ -158,7 +193,8 @@ public final class TestableDeviceConfig implements StaticMockFixture {
|
|||||||
invocation -> {
|
invocation -> {
|
||||||
String key = invocation.getArgument(0);
|
String key = invocation.getArgument(0);
|
||||||
int defaultValue = invocation.getArgument(1);
|
int defaultValue = invocation.getArgument(1);
|
||||||
if (name.equalsIgnoreCase(key) && value != null) {
|
final String value = keyValues.get(key.toLowerCase());
|
||||||
|
if (value != null) {
|
||||||
try {
|
try {
|
||||||
return Integer.parseInt(value);
|
return Integer.parseInt(value);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
@@ -173,7 +209,8 @@ public final class TestableDeviceConfig implements StaticMockFixture {
|
|||||||
invocation -> {
|
invocation -> {
|
||||||
String key = invocation.getArgument(0);
|
String key = invocation.getArgument(0);
|
||||||
long defaultValue = invocation.getArgument(1);
|
long defaultValue = invocation.getArgument(1);
|
||||||
if (name.equalsIgnoreCase(key) && value != null) {
|
final String value = keyValues.get(key.toLowerCase());
|
||||||
|
if (value != null) {
|
||||||
try {
|
try {
|
||||||
return Long.parseLong(value);
|
return Long.parseLong(value);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
@@ -184,11 +221,12 @@ public final class TestableDeviceConfig implements StaticMockFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
when(properties.getString(anyString(), anyString())).thenAnswer(
|
when(properties.getString(anyString(), nullable(String.class))).thenAnswer(
|
||||||
invocation -> {
|
invocation -> {
|
||||||
String key = invocation.getArgument(0);
|
String key = invocation.getArgument(0);
|
||||||
String defaultValue = invocation.getArgument(1);
|
String defaultValue = invocation.getArgument(1);
|
||||||
if (name.equalsIgnoreCase(key) && value != null) {
|
final String value = keyValues.get(key.toLowerCase());
|
||||||
|
if (value != null) {
|
||||||
return value;
|
return value;
|
||||||
} else {
|
} else {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import android.app.ActivityThread;
|
import android.app.ActivityThread;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
|
import android.provider.DeviceConfig.Properties;
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
@@ -90,6 +91,39 @@ public class TestableDeviceConfigTest {
|
|||||||
assertThat(result).isEqualTo(newValue);
|
assertThat(result).isEqualTo(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getProperties_empty() {
|
||||||
|
String newKey = "key2";
|
||||||
|
String newValue = "value2";
|
||||||
|
DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
|
||||||
|
Properties properties = DeviceConfig.getProperties(sNamespace);
|
||||||
|
assertThat(properties.getString(sKey, null)).isEqualTo(sValue);
|
||||||
|
assertThat(properties.getString(newKey, null)).isNull();
|
||||||
|
|
||||||
|
DeviceConfig.setProperty(sNamespace, newKey, newValue, false);
|
||||||
|
properties = DeviceConfig.getProperties(sNamespace);
|
||||||
|
assertThat(properties.getString(sKey, null)).isEqualTo(sValue);
|
||||||
|
assertThat(properties.getString(newKey, null)).isEqualTo(newValue);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getProperties() {
|
||||||
|
Properties properties = DeviceConfig.getProperties(sNamespace, sKey);
|
||||||
|
assertThat(properties.getString(sKey, null)).isNull();
|
||||||
|
|
||||||
|
DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
|
||||||
|
properties = DeviceConfig.getProperties(sNamespace, sKey);
|
||||||
|
assertThat(properties.getString(sKey, null)).isEqualTo(sValue);
|
||||||
|
|
||||||
|
String newKey = "key2";
|
||||||
|
String newValue = "value2";
|
||||||
|
DeviceConfig.setProperty(sNamespace, newKey, newValue, false);
|
||||||
|
properties = DeviceConfig.getProperties(sNamespace, sKey, newKey);
|
||||||
|
assertThat(properties.getString(sKey, null)).isEqualTo(sValue);
|
||||||
|
assertThat(properties.getString(newKey, null)).isEqualTo(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testListener() throws InterruptedException {
|
public void testListener() throws InterruptedException {
|
||||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user