diff options
18 files changed, 956 insertions, 99 deletions
diff --git a/Android.bp b/Android.bp index 1a73e9d323ff..ff6210626ecc 100644 --- a/Android.bp +++ b/Android.bp @@ -1115,6 +1115,7 @@ filegroup { "core/java/android/os/incremental/IStorageLoadingProgressListener.aidl", "core/java/android/os/incremental/IncrementalNewFileParams.aidl", "core/java/android/os/incremental/IStorageHealthListener.aidl", + "core/java/android/os/incremental/PerUidReadTimeouts.aidl", "core/java/android/os/incremental/StorageHealthCheckParams.aidl", ], path: "core/java", diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index ca92ad5deae6..7db5a80b6cf9 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -21,6 +21,7 @@ import android.content.pm.IDataLoaderStatusListener; import android.os.incremental.IncrementalNewFileParams; import android.os.incremental.IStorageLoadingProgressListener; import android.os.incremental.IStorageHealthListener; +import android.os.incremental.PerUidReadTimeouts; import android.os.incremental.StorageHealthCheckParams; /** @hide */ @@ -40,7 +41,8 @@ interface IIncrementalService { int createStorage(in @utf8InCpp String path, in DataLoaderParamsParcel params, int createMode, in IDataLoaderStatusListener statusListener, in StorageHealthCheckParams healthCheckParams, - in IStorageHealthListener healthListener); + in IStorageHealthListener healthListener, + in PerUidReadTimeouts[] perUidReadTimeouts); int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode); /** @@ -123,7 +125,7 @@ interface IIncrementalService { /** * Permanently disable readlogs reporting for a storage given its ID. */ - void disableReadLogs(int storageId); + void disallowReadLogs(int storageId); /** * Setting up native library directories and extract native libs onto a storage if needed. diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java index 284c2df2ee7b..59292baa110c 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -69,7 +69,8 @@ public final class IncrementalFileStorages { @Nullable IDataLoaderStatusListener statusListener, @Nullable StorageHealthCheckParams healthCheckParams, @Nullable IStorageHealthListener healthListener, - List<InstallationFileParcel> addedFiles) throws IOException { + @NonNull List<InstallationFileParcel> addedFiles, + @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException { // TODO(b/136132412): validity check if session should not be incremental IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService( Context.INCREMENTAL_SERVICE); @@ -80,7 +81,7 @@ public final class IncrementalFileStorages { final IncrementalFileStorages result = new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams, statusListener, healthCheckParams, - healthListener); + healthListener, perUidReadTimeouts); for (InstallationFileParcel file : addedFiles) { if (file.location == LOCATION_DATA_APP) { try { @@ -105,7 +106,8 @@ public final class IncrementalFileStorages { @NonNull DataLoaderParams dataLoaderParams, @Nullable IDataLoaderStatusListener statusListener, @Nullable StorageHealthCheckParams healthCheckParams, - @Nullable IStorageHealthListener healthListener) throws IOException { + @Nullable IStorageHealthListener healthListener, + @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException { try { mStageDir = stageDir; mIncrementalManager = incrementalManager; @@ -124,7 +126,7 @@ public final class IncrementalFileStorages { mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(), dataLoaderParams, IncrementalManager.CREATE_MODE_CREATE | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false, - statusListener, healthCheckParams, healthListener); + statusListener, healthCheckParams, healthListener, perUidReadTimeouts); if (mDefaultStorage == null) { throw new IOException( "Couldn't create incremental storage at " + stageDir); @@ -163,8 +165,8 @@ public final class IncrementalFileStorages { /** * Permanently disables readlogs. */ - public void disableReadLogs() { - mDefaultStorage.disableReadLogs(); + public void disallowReadLogs() { + mDefaultStorage.disallowReadLogs(); } /** diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index fb47ef04b231..4b9327021cb0 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -40,6 +40,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Objects; /** * Provides operations to open or create an IncrementalStorage, using IIncrementalService @@ -104,10 +105,14 @@ public final class IncrementalManager { boolean autoStartDataLoader, @Nullable IDataLoaderStatusListener statusListener, @Nullable StorageHealthCheckParams healthCheckParams, - @Nullable IStorageHealthListener healthListener) { + @Nullable IStorageHealthListener healthListener, + @NonNull PerUidReadTimeouts[] perUidReadTimeouts) { + Objects.requireNonNull(path); + Objects.requireNonNull(params); + Objects.requireNonNull(perUidReadTimeouts); try { final int id = mService.createStorage(path, params.getData(), createMode, - statusListener, healthCheckParams, healthListener); + statusListener, healthCheckParams, healthListener, perUidReadTimeouts); if (id < 0) { return null; } diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index b913faf9cc83..5b688bbd0655 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -432,9 +432,9 @@ public final class IncrementalStorage { /** * Permanently disable readlogs collection. */ - public void disableReadLogs() { + public void disallowReadLogs() { try { - mService.disableReadLogs(mId); + mService.disallowReadLogs(mId); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/os/incremental/PerUidReadTimeouts.aidl b/core/java/android/os/incremental/PerUidReadTimeouts.aidl new file mode 100644 index 000000000000..84f30a6c3aaf --- /dev/null +++ b/core/java/android/os/incremental/PerUidReadTimeouts.aidl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 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 android.os.incremental; + +/** + * Max value is ~1hr = 3600s = 3600000ms = 3600000000us + * @hide + */ +parcelable PerUidReadTimeouts { + /** UID to apply these timeouts to */ + int uid; + + /** + * Min time to read any block. Note that this doesn't apply to reads + * which are satisfied from the page cache. + */ + long minTimeUs; + + /** + * Min time to satisfy a pending read. Must be >= min_time_us. Any + * pending read which is filled before this time will be delayed so + * that the total read time >= this value. + */ + long minPendingTimeUs; + + /** + * Max time to satisfy a pending read before the read times out. + * Must be >= min_pending_time_us + */ + long maxPendingTimeUs; +} diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java index 520871ff40c8..4f986bd5276b 100644 --- a/services/core/java/com/android/server/pm/DumpState.java +++ b/services/core/java/com/android/server/pm/DumpState.java @@ -44,6 +44,7 @@ public final class DumpState { public static final int DUMP_APEX = 1 << 25; public static final int DUMP_QUERIES = 1 << 26; public static final int DUMP_KNOWN_PACKAGES = 1 << 27; + public static final int DUMP_PER_UID_READ_TIMEOUTS = 1 << 28; public static final int OPTION_SHOW_FILTERS = 1 << 0; public static final int OPTION_DUMP_ALL_COMPONENTS = 1 << 1; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index f97a5ee45346..3d04b5607922 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -111,6 +111,7 @@ import android.os.UserHandle; import android.os.incremental.IStorageHealthListener; import android.os.incremental.IncrementalFileStorages; import android.os.incremental.IncrementalManager; +import android.os.incremental.PerUidReadTimeouts; import android.os.incremental.StorageHealthCheckParams; import android.os.storage.StorageManager; import android.provider.Settings.Secure; @@ -3006,7 +3007,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isInstallerShell = (mInstallerUid == Process.SHELL_UID); if (isInstallerShell && isIncrementalInstallation() && mIncrementalFileStorages != null) { if (!packageLite.debuggable && !packageLite.profilableByShell) { - mIncrementalFileStorages.disableReadLogs(); + mIncrementalFileStorages.disallowReadLogs(); } } } @@ -3720,12 +3721,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { }; if (!manualStartAndDestroy) { + final PerUidReadTimeouts[] perUidReadTimeouts = mPm.getPerUidReadTimeouts(); + final StorageHealthCheckParams healthCheckParams = new StorageHealthCheckParams(); healthCheckParams.blockedTimeoutMs = INCREMENTAL_STORAGE_BLOCKED_TIMEOUT_MS; healthCheckParams.unhealthyTimeoutMs = INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS; healthCheckParams.unhealthyMonitoringMs = INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS; + final boolean systemDataLoader = params.getComponentName().getPackageName() == SYSTEM_DATA_LOADER_PACKAGE; + final IStorageHealthListener healthListener = new IStorageHealthListener.Stub() { @Override public void onHealthStatus(int storageId, int status) { @@ -3760,7 +3765,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext, stageDir, - params, statusListener, healthCheckParams, healthListener, addedFiles); + params, statusListener, healthCheckParams, healthListener, addedFiles, + perUidReadTimeouts); return false; } catch (IOException e) { throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(), diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 27008d824dd2..4467b5110d08 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -107,6 +107,7 @@ import static android.os.incremental.IncrementalManager.isIncrementalPath; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; import static android.os.storage.StorageManager.FLAG_STORAGE_DE; import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL; +import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; import static com.android.internal.annotations.VisibleForTesting.Visibility; import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE; @@ -287,6 +288,7 @@ import android.os.UserManager; import android.os.incremental.IStorageHealthListener; import android.os.incremental.IncrementalManager; import android.os.incremental.IncrementalStorage; +import android.os.incremental.PerUidReadTimeouts; import android.os.incremental.StorageHealthCheckParams; import android.os.storage.DiskInfo; import android.os.storage.IStorageManager; @@ -512,6 +514,7 @@ public class PackageManagerService extends IPackageManager.Stub public static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE; public static final boolean DEBUG_CACHES = false; public static final boolean TRACE_CACHES = false; + private static final boolean DEBUG_PER_UID_READ_TIMEOUTS = false; // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService // and PackageDexOptimizer. All these classes have their own flag to allow switching a single @@ -647,6 +650,24 @@ public class PackageManagerService extends IPackageManager.Stub private static final long DEFAULT_ENABLE_ROLLBACK_TIMEOUT_MILLIS = 10 * 1000; /** + * Default IncFs timeouts. Maximum values in IncFs is 1hr. + * + * <p>If flag value is empty, the default value will be assigned. + * + * Flag type: {@code String} + * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE + */ + private static final String PROPERTY_INCFS_DEFAULT_TIMEOUTS = "incfs_default_timeouts"; + + /** + * Known digesters with optional timeouts. + * + * Flag type: {@code String} + * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE + */ + private static final String PROPERTY_KNOWN_DIGESTERS_LIST = "known_digesters_list"; + + /** * The default response for package verification timeout. * * This can be either PackageManager.VERIFICATION_ALLOW or @@ -909,6 +930,11 @@ public class PackageManagerService extends IPackageManager.Stub final private ArrayList<IPackageChangeObserver> mPackageChangeObservers = new ArrayList<>(); + // Cached parsed flag value. Invalidated on each flag change. + private PerUidReadTimeouts[] mPerUidReadTimeoutsCache; + + private static final PerUidReadTimeouts[] EMPTY_PER_UID_READ_TIMEOUTS_ARRAY = {}; + /** * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors. * @@ -23738,6 +23764,17 @@ public class PackageManagerService extends IPackageManager.Stub mInstallerService.restoreAndApplyStagedSessionIfNeeded(); mExistingPackages = null; + + // Clear cache on flags changes. + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(), + properties -> { + final Set<String> keyset = properties.getKeyset(); + if (keyset.contains(PROPERTY_INCFS_DEFAULT_TIMEOUTS) || keyset.contains( + PROPERTY_KNOWN_DIGESTERS_LIST)) { + mPerUidReadTimeoutsCache = null; + } + }); } public void waitForAppDataPrepared() { @@ -23828,6 +23865,7 @@ public class PackageManagerService extends IPackageManager.Stub pw.println(" v[erifiers]: print package verifier info"); pw.println(" d[omain-preferred-apps]: print domains preferred apps"); pw.println(" i[ntent-filter-verifiers]|ifv: print intent filter verifier info"); + pw.println(" t[imeouts]: print read timeouts for known digesters"); pw.println(" version: print database version info"); pw.println(" write: write current settings now"); pw.println(" installs: details about install sessions"); @@ -23982,6 +24020,8 @@ public class PackageManagerService extends IPackageManager.Stub dumpState.setDump(DumpState.DUMP_SERVICE_PERMISSIONS); } else if ("known-packages".equals(cmd)) { dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES); + } else if ("t".equals(cmd) || "timeouts".equals(cmd)) { + dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS); } else if ("write".equals(cmd)) { synchronized (mLock) { writeSettingsLPrTEMP(); @@ -24380,6 +24420,25 @@ public class PackageManagerService extends IPackageManager.Stub if (!checkin && dumpState.isDumping(DumpState.DUMP_APEX)) { mApexManager.dump(pw, packageName); } + + if (!checkin && dumpState.isDumping(DumpState.DUMP_PER_UID_READ_TIMEOUTS) + && packageName == null) { + pw.println(); + pw.println("Per UID read timeouts:"); + pw.println(" Default timeouts flag: " + getDefaultTimeouts()); + pw.println(" Known digesters list flag: " + getKnownDigestersList()); + + PerUidReadTimeouts[] items = getPerUidReadTimeouts(); + pw.println(" Timeouts (" + items.length + "):"); + for (PerUidReadTimeouts item : items) { + pw.print(" ("); + pw.print("uid=" + item.uid + ", "); + pw.print("minTimeUs=" + item.minTimeUs + ", "); + pw.print("minPendingTimeUs=" + item.minPendingTimeUs + ", "); + pw.print("maxPendingTimeUs=" + item.maxPendingTimeUs); + pw.println(")"); + } + } } //TODO: b/111402650 @@ -27967,6 +28026,88 @@ public class PackageManagerService extends IPackageManager.Stub SystemClock.sleep(durationMs); } } + + private static String getDefaultTimeouts() { + return DeviceConfig.getString(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, + PROPERTY_INCFS_DEFAULT_TIMEOUTS, ""); + } + + private static String getKnownDigestersList() { + return DeviceConfig.getString(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, + PROPERTY_KNOWN_DIGESTERS_LIST, ""); + } + + /** + * Returns the array containing per-uid timeout configuration. + * This is derived from DeviceConfig flags. + */ + public @NonNull PerUidReadTimeouts[] getPerUidReadTimeouts() { + PerUidReadTimeouts[] result = mPerUidReadTimeoutsCache; + if (result == null) { + result = parsePerUidReadTimeouts(); + mPerUidReadTimeoutsCache = result; + } + return result; + } + + private @NonNull PerUidReadTimeouts[] parsePerUidReadTimeouts() { + final String defaultTimeouts = getDefaultTimeouts(); + final String knownDigestersList = getKnownDigestersList(); + final List<PerPackageReadTimeouts> perPackageReadTimeouts = + PerPackageReadTimeouts.parseDigestersList(defaultTimeouts, knownDigestersList); + + if (perPackageReadTimeouts.size() == 0) { + return EMPTY_PER_UID_READ_TIMEOUTS_ARRAY; + } + + final int[] allUsers = mInjector.getUserManagerService().getUserIds(); + + List<PerUidReadTimeouts> result = new ArrayList<>(perPackageReadTimeouts.size()); + synchronized (mLock) { + for (int i = 0, size = perPackageReadTimeouts.size(); i < size; ++i) { + final PerPackageReadTimeouts perPackage = perPackageReadTimeouts.get(i); + final PackageSetting ps = mSettings.mPackages.get(perPackage.packageName); + if (ps == null) { + if (DEBUG_PER_UID_READ_TIMEOUTS) { + Slog.i(TAG, "PerUidReadTimeouts: package not found = " + + perPackage.packageName); + } + continue; + } + final AndroidPackage pkg = ps.getPkg(); + if (pkg.getLongVersionCode() < perPackage.versionCodes.minVersionCode + || pkg.getLongVersionCode() > perPackage.versionCodes.maxVersionCode) { + if (DEBUG_PER_UID_READ_TIMEOUTS) { + Slog.i(TAG, "PerUidReadTimeouts: version code is not in range = " + + perPackage.packageName + ":" + pkg.getLongVersionCode()); + } + continue; + } + if (perPackage.sha256certificate != null + && !pkg.getSigningDetails().hasSha256Certificate( + perPackage.sha256certificate)) { + if (DEBUG_PER_UID_READ_TIMEOUTS) { + Slog.i(TAG, "PerUidReadTimeouts: invalid certificate = " + + perPackage.packageName + ":" + pkg.getLongVersionCode()); + } + continue; + } + for (int userId : allUsers) { + if (!ps.getInstalled(userId)) { + continue; + } + final int uid = UserHandle.getUid(userId, ps.appId); + final PerUidReadTimeouts perUid = new PerUidReadTimeouts(); + perUid.uid = uid; + perUid.minTimeUs = perPackage.timeouts.minTimeUs; + perUid.minPendingTimeUs = perPackage.timeouts.minPendingTimeUs; + perUid.maxPendingTimeUs = perPackage.timeouts.maxPendingTimeUs; + result.add(perUid); + } + } + } + return result.toArray(new PerUidReadTimeouts[result.size()]); + } } interface PackageSender { diff --git a/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java new file mode 100644 index 000000000000..3b306a850b64 --- /dev/null +++ b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2021 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.pm; + +import android.annotation.NonNull;; +import android.text.TextUtils; + +import com.android.internal.util.HexDump; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +class PerPackageReadTimeouts { + static long tryParseLong(String str, long defaultValue) { + try { + return Long.parseLong(str); + } catch (NumberFormatException nfe) { + return defaultValue; + } + } + + static byte[] tryParseSha256(String str) { + if (TextUtils.isEmpty(str)) { + return null; + } + try { + return HexDump.hexStringToByteArray(str); + } catch (RuntimeException e) { + return null; + } + } + + static class Timeouts { + public final long minTimeUs; + public final long minPendingTimeUs; + public final long maxPendingTimeUs; + + // 3600000000us == 1hr + public static final Timeouts DEFAULT = new Timeouts(3600000000L, 3600000000L, 3600000000L); + + private Timeouts(long minTimeUs, long minPendingTimeUs, long maxPendingTimeUs) { + this.minTimeUs = minTimeUs; + this.minPendingTimeUs = minPendingTimeUs; + this.maxPendingTimeUs = maxPendingTimeUs; + } + + static Timeouts parse(String timeouts) { + String[] splits = timeouts.split(":", 3); + if (splits.length != 3) { + return DEFAULT; + } + final long minTimeUs = tryParseLong(splits[0], DEFAULT.minTimeUs); + final long minPendingTimeUs = tryParseLong(splits[1], DEFAULT.minPendingTimeUs); + final long maxPendingTimeUs = tryParseLong(splits[2], DEFAULT.maxPendingTimeUs); + if (0 <= minTimeUs && minTimeUs <= minPendingTimeUs + && minPendingTimeUs <= maxPendingTimeUs) { + // validity check + return new Timeouts(minTimeUs, minPendingTimeUs, maxPendingTimeUs); + } + return DEFAULT; + } + } + + static class VersionCodes { + public final long minVersionCode; + public final long maxVersionCode; + + public static final VersionCodes ALL_VERSION_CODES = new VersionCodes(Long.MIN_VALUE, + Long.MAX_VALUE); + + private VersionCodes(long minVersionCode, long maxVersionCode) { + this.minVersionCode = minVersionCode; + this.maxVersionCode = maxVersionCode; + } + + static VersionCodes parse(String codes) { + if (TextUtils.isEmpty(codes)) { + return ALL_VERSION_CODES; + } + String[] splits = codes.split("-", 2); + switch (splits.length) { + case 1: { + // single version code + try { + final long versionCode = Long.parseLong(splits[0]); + return new VersionCodes(versionCode, versionCode); + } catch (NumberFormatException nfe) { + return ALL_VERSION_CODES; + } + } + case 2: { + final long minVersionCode = tryParseLong(splits[0], + ALL_VERSION_CODES.minVersionCode); + final long maxVersionCode = tryParseLong(splits[1], + ALL_VERSION_CODES.maxVersionCode); + if (minVersionCode <= maxVersionCode) { + return new VersionCodes(minVersionCode, maxVersionCode); + } + break; + } + } + return ALL_VERSION_CODES; + } + } + + public final String packageName; + public final byte[] sha256certificate; + public final VersionCodes versionCodes; + public final Timeouts timeouts; + + private PerPackageReadTimeouts(String packageName, byte[] sha256certificate, + VersionCodes versionCodes, Timeouts timeouts) { + this.packageName = packageName; + this.sha256certificate = sha256certificate; + this.versionCodes = versionCodes; + this.timeouts = timeouts; + } + + @SuppressWarnings("fallthrough") + static PerPackageReadTimeouts parse(String timeoutsStr, VersionCodes defaultVersionCodes, + Timeouts defaultTimeouts) { + String packageName = null; + byte[] sha256certificate = null; + VersionCodes versionCodes = defaultVersionCodes; + Timeouts timeouts = defaultTimeouts; + + final String[] splits = timeoutsStr.split(":", 4); + switch (splits.length) { + case 4: + timeouts = Timeouts.parse(splits[3]); + // fall through + case 3: + versionCodes = VersionCodes.parse(splits[2]); + // fall through + case 2: + sha256certificate = tryParseSha256(splits[1]); + // fall through + case 1: + packageName = splits[0]; + break; + default: + return null; + } + if (TextUtils.isEmpty(packageName)) { + return null; + } + + return new PerPackageReadTimeouts(packageName, sha256certificate, versionCodes, + timeouts); + } + + static @NonNull List<PerPackageReadTimeouts> parseDigestersList(String defaultTimeoutsStr, + String knownDigestersList) { + if (TextUtils.isEmpty(knownDigestersList)) { + return Collections.emptyList(); + } + + final VersionCodes defaultVersionCodes = VersionCodes.ALL_VERSION_CODES; + final Timeouts defaultTimeouts = Timeouts.parse(defaultTimeoutsStr); + + String[] packages = knownDigestersList.split(","); + List<PerPackageReadTimeouts> result = new ArrayList<>(packages.length); + for (int i = 0, size = packages.length; i < size; ++i) { + PerPackageReadTimeouts timeouts = PerPackageReadTimeouts.parse(packages[i], + defaultVersionCodes, defaultTimeouts); + if (timeouts != null) { + result.add(timeouts); + } + } + return result; + } +} diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index a31aac96eb48..d2244286450b 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -122,13 +122,14 @@ binder::Status BinderIncrementalService::createStorage( const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener, const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams, const ::android::sp<::android::os::incremental::IStorageHealthListener>& healthListener, + const ::std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts, int32_t* _aidl_return) { *_aidl_return = mImpl.createStorage(path, const_cast<content::pm::DataLoaderParamsParcel&&>(params), android::incremental::IncrementalService::CreateOptions(createMode), statusListener, const_cast<StorageHealthCheckParams&&>(healthCheckParams), - healthListener); + healthListener, perUidReadTimeouts); return ok(); } @@ -164,8 +165,8 @@ binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) { return ok(); } -binder::Status BinderIncrementalService::disableReadLogs(int32_t storageId) { - mImpl.disableReadLogs(storageId); +binder::Status BinderIncrementalService::disallowReadLogs(int32_t storageId) { + mImpl.disallowReadLogs(storageId); return ok(); } @@ -254,7 +255,7 @@ binder::Status BinderIncrementalService::isFileFullyLoaded(int32_t storageId, binder::Status BinderIncrementalService::getLoadingProgress(int32_t storageId, float* _aidl_return) { - *_aidl_return = mImpl.getLoadingProgress(storageId); + *_aidl_return = mImpl.getLoadingProgress(storageId).getProgress(); return ok(); } diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index 8afa0f7bb117..9a4537a15f31 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -45,6 +45,7 @@ public: const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener, const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams, const ::android::sp<IStorageHealthListener>& healthListener, + const ::std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts, int32_t* _aidl_return) final; binder::Status createLinkedStorage(const std::string& path, int32_t otherStorageId, int32_t createMode, int32_t* _aidl_return) final; @@ -77,7 +78,7 @@ public: std::vector<uint8_t>* _aidl_return) final; binder::Status startLoading(int32_t storageId, bool* _aidl_return) final; binder::Status deleteStorage(int32_t storageId) final; - binder::Status disableReadLogs(int32_t storageId) final; + binder::Status disallowReadLogs(int32_t storageId) final; binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath, const std::string& libDirRelativePath, const std::string& abi, bool extractNativeLibs, diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index eb6b325050eb..45c9ad9b344b 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -63,6 +63,10 @@ struct Constants { static constexpr auto libSuffix = ".so"sv; static constexpr auto blockSize = 4096; static constexpr auto systemPackage = "android"sv; + + static constexpr auto progressUpdateInterval = 1000ms; + static constexpr auto perUidTimeoutOffset = progressUpdateInterval * 2; + static constexpr auto minPerUidTimeout = progressUpdateInterval * 3; }; static const Constants& constants() { @@ -350,7 +354,8 @@ void IncrementalService::onDump(int fd) { dprintf(fd, " storages (%d): {\n", int(mnt.storages.size())); for (auto&& [storageId, storage] : mnt.storages) { dprintf(fd, " [%d] -> [%s] (%d %% loaded) \n", storageId, storage.name.c_str(), - (int)(getLoadingProgressFromPath(mnt, storage.name.c_str()) * 100)); + (int)(getLoadingProgressFromPath(mnt, storage.name.c_str()).getProgress() * + 100)); } dprintf(fd, " }\n"); @@ -419,12 +424,11 @@ auto IncrementalService::getStorageSlotLocked() -> MountMap::iterator { } } -StorageId IncrementalService::createStorage(std::string_view mountPoint, - content::pm::DataLoaderParamsParcel&& dataLoaderParams, - CreateOptions options, - const DataLoaderStatusListener& statusListener, - StorageHealthCheckParams&& healthCheckParams, - const StorageHealthListener& healthListener) { +StorageId IncrementalService::createStorage( + std::string_view mountPoint, content::pm::DataLoaderParamsParcel&& dataLoaderParams, + CreateOptions options, const DataLoaderStatusListener& statusListener, + StorageHealthCheckParams&& healthCheckParams, const StorageHealthListener& healthListener, + const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) { LOG(INFO) << "createStorage: " << mountPoint << " | " << int(options); if (!path::isAbsolute(mountPoint)) { LOG(ERROR) << "path is not absolute: " << mountPoint; @@ -553,13 +557,14 @@ StorageId IncrementalService::createStorage(std::string_view mountPoint, if (auto err = addBindMount(*ifs, storageIt->first, storageIt->second.name, std::string(storageIt->second.name), std::move(mountNorm), bk, l); err < 0) { - LOG(ERROR) << "adding bind mount failed: " << -err; + LOG(ERROR) << "Adding bind mount failed: " << -err; return kInvalidStorageId; } // Done here as well, all data structures are in good state. secondCleanupOnFailure.release(); + // DataLoader. auto dataLoaderStub = prepareDataLoader(*ifs, std::move(dataLoaderParams), &statusListener, std::move(healthCheckParams), &healthListener); CHECK(dataLoaderStub); @@ -567,6 +572,11 @@ StorageId IncrementalService::createStorage(std::string_view mountPoint, mountIt->second = std::move(ifs); l.unlock(); + // Per Uid timeouts. + if (!perUidReadTimeouts.empty()) { + setUidReadTimeouts(mountId, perUidReadTimeouts); + } + if (mSystemReady.load(std::memory_order_relaxed) && !dataLoaderStub->requestCreate()) { // failed to create data loader LOG(ERROR) << "initializeDataLoader() failed"; @@ -634,17 +644,17 @@ StorageId IncrementalService::findStorageId(std::string_view path) const { return it->second->second.storage; } -void IncrementalService::disableReadLogs(StorageId storageId) { +void IncrementalService::disallowReadLogs(StorageId storageId) { std::unique_lock l(mLock); const auto ifs = getIfsLocked(storageId); if (!ifs) { - LOG(ERROR) << "disableReadLogs failed, invalid storageId: " << storageId; + LOG(ERROR) << "disallowReadLogs failed, invalid storageId: " << storageId; return; } - if (!ifs->readLogsEnabled()) { + if (!ifs->readLogsAllowed()) { return; } - ifs->disableReadLogs(); + ifs->disallowReadLogs(); l.unlock(); const auto metadata = constants().readLogsDisabledMarkerName; @@ -669,7 +679,7 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog const auto& params = ifs->dataLoaderStub->params(); if (enableReadLogs) { - if (!ifs->readLogsEnabled()) { + if (!ifs->readLogsAllowed()) { LOG(ERROR) << "setStorageParams failed, readlogs disabled for storageId: " << storageId; return -EPERM; } @@ -704,7 +714,12 @@ binder::Status IncrementalService::applyStorageParams(IncFsMount& ifs, bool enab } std::lock_guard l(mMountOperationLock); - return mVold->setIncFsMountOptions(control, enableReadLogs); + const auto status = mVold->setIncFsMountOptions(control, enableReadLogs); + if (status.isOk()) { + // Store enabled state. + ifs.setReadLogsEnabled(enableReadLogs); + } + return status; } void IncrementalService::deleteStorage(StorageId storageId) { @@ -1052,6 +1067,74 @@ bool IncrementalService::startLoading(StorageId storage) const { return true; } +void IncrementalService::setUidReadTimeouts( + StorageId storage, const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) { + using microseconds = std::chrono::microseconds; + using milliseconds = std::chrono::milliseconds; + + auto maxPendingTimeUs = microseconds(0); + for (const auto& timeouts : perUidReadTimeouts) { + maxPendingTimeUs = std::max(maxPendingTimeUs, microseconds(timeouts.maxPendingTimeUs)); + } + if (maxPendingTimeUs < Constants::minPerUidTimeout) { + return; + } + + const auto ifs = getIfs(storage); + if (!ifs) { + return; + } + + if (auto err = mIncFs->setUidReadTimeouts(ifs->control, perUidReadTimeouts); err < 0) { + LOG(ERROR) << "Setting read timeouts failed: " << -err; + return; + } + + const auto timeout = std::chrono::duration_cast<milliseconds>(maxPendingTimeUs) - + Constants::perUidTimeoutOffset; + updateUidReadTimeouts(storage, Clock::now() + timeout); +} + +void IncrementalService::clearUidReadTimeouts(StorageId storage) { + const auto ifs = getIfs(storage); + if (!ifs) { + return; + } + + mIncFs->setUidReadTimeouts(ifs->control, {}); +} + +void IncrementalService::updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit) { + // Reached maximum timeout. + if (Clock::now() >= timeLimit) { + return clearUidReadTimeouts(storage); + } + + // Still loading? + const auto progress = getLoadingProgress(storage); + if (progress.isError()) { + // Something is wrong, abort. + return clearUidReadTimeouts(storage); + } + + if (progress.started() && progress.fullyLoaded()) { + // Fully loaded, check readLogs collection. + const auto ifs = getIfs(storage); + if (!ifs->readLogsEnabled()) { + return clearUidReadTimeouts(storage); + } + } + + const auto timeLeft = timeLimit - Clock::now(); + if (timeLeft < Constants::progressUpdateInterval) { + // Don't bother. + return clearUidReadTimeouts(storage); + } + + addTimedJob(*mTimedQueue, storage, Constants::progressUpdateInterval, + [this, storage, timeLimit]() { updateUidReadTimeouts(storage, timeLimit); }); +} + std::unordered_set<std::string_view> IncrementalService::adoptMountedInstances() { std::unordered_set<std::string_view> mountedRootNames; mIncFs->listExistingMounts([this, &mountedRootNames](auto root, auto backingDir, auto binds) { @@ -1125,7 +1208,7 @@ std::unordered_set<std::string_view> IncrementalService::adoptMountedInstances() // Check if marker file present. if (checkReadLogsDisabledMarker(root)) { - ifs->disableReadLogs(); + ifs->disallowReadLogs(); } std::vector<std::pair<std::string, metadata::BindPoint>> permanentBindPoints; @@ -1301,7 +1384,7 @@ bool IncrementalService::mountExistingImage(std::string_view root) { // Check if marker file present. if (checkReadLogsDisabledMarker(mountTarget)) { - ifs->disableReadLogs(); + ifs->disallowReadLogs(); } // DataLoader params @@ -1705,7 +1788,7 @@ int IncrementalService::setFileContent(const IfsMountPtr& ifs, const incfs::File return 0; } -int IncrementalService::isFileFullyLoaded(StorageId storage, const std::string& path) const { +int IncrementalService::isFileFullyLoaded(StorageId storage, std::string_view filePath) const { std::unique_lock l(mLock); const auto ifs = getIfsLocked(storage); if (!ifs) { @@ -1718,7 +1801,7 @@ int IncrementalService::isFileFullyLoaded(StorageId storage, const std::string& return -EINVAL; } l.unlock(); - return isFileFullyLoadedFromPath(*ifs, path); + return isFileFullyLoadedFromPath(*ifs, filePath); } int IncrementalService::isFileFullyLoadedFromPath(const IncFsMount& ifs, @@ -1736,25 +1819,26 @@ int IncrementalService::isFileFullyLoadedFromPath(const IncFsMount& ifs, return totalBlocks - filledBlocks; } -float IncrementalService::getLoadingProgress(StorageId storage) const { +IncrementalService::LoadingProgress IncrementalService::getLoadingProgress( + StorageId storage) const { std::unique_lock l(mLock); const auto ifs = getIfsLocked(storage); if (!ifs) { LOG(ERROR) << "getLoadingProgress failed, invalid storageId: " << storage; - return -EINVAL; + return {-EINVAL, -EINVAL}; } const auto storageInfo = ifs->storages.find(storage); if (storageInfo == ifs->storages.end()) { LOG(ERROR) << "getLoadingProgress failed, no storage: " << storage; - return -EINVAL; + return {-EINVAL, -EINVAL}; } l.unlock(); return getLoadingProgressFromPath(*ifs, storageInfo->second.name); } -float IncrementalService::getLoadingProgressFromPath(const IncFsMount& ifs, - std::string_view storagePath) const { - size_t totalBlocks = 0, filledBlocks = 0; +IncrementalService::LoadingProgress IncrementalService::getLoadingProgressFromPath( + const IncFsMount& ifs, std::string_view storagePath) const { + ssize_t totalBlocks = 0, filledBlocks = 0; const auto filePaths = mFs->listFilesRecursive(storagePath); for (const auto& filePath : filePaths) { const auto [filledBlocksCount, totalBlocksCount] = @@ -1762,33 +1846,29 @@ float IncrementalService::getLoadingProgressFromPath(const IncFsMount& ifs, if (filledBlocksCount < 0) { LOG(ERROR) << "getLoadingProgress failed to get filled blocks count for: " << filePath << " errno: " << filledBlocksCount; - return filledBlocksCount; + return {filledBlocksCount, filledBlocksCount}; } totalBlocks += totalBlocksCount; filledBlocks += filledBlocksCount; } - if (totalBlocks == 0) { - // No file in the storage or files are empty; regarded as fully loaded - return 1; - } - return (float)filledBlocks / (float)totalBlocks; + return {filledBlocks, totalBlocks}; } bool IncrementalService::updateLoadingProgress( StorageId storage, const StorageLoadingProgressListener& progressListener) { const auto progress = getLoadingProgress(storage); - if (progress < 0) { + if (progress.isError()) { // Failed to get progress from incfs, abort. return false; } - progressListener->onStorageLoadingProgressChanged(storage, progress); - if (progress > 1 - 0.001f) { + progressListener->onStorageLoadingProgressChanged(storage, progress.getProgress()); + if (progress.fullyLoaded()) { // Stop updating progress once it is fully loaded return true; } - static constexpr auto kProgressUpdateInterval = 1000ms; - addTimedJob(*mProgressUpdateJobQueue, storage, kProgressUpdateInterval /* repeat after 1s */, + addTimedJob(*mProgressUpdateJobQueue, storage, + Constants::progressUpdateInterval /* repeat after 1s */, [storage, progressListener, this]() { updateLoadingProgress(storage, progressListener); }); diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index eb69470c97a7..306612159412 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -23,6 +23,7 @@ #include <android/os/incremental/BnIncrementalServiceConnector.h> #include <android/os/incremental/BnStorageHealthListener.h> #include <android/os/incremental/BnStorageLoadingProgressListener.h> +#include <android/os/incremental/PerUidReadTimeouts.h> #include <android/os/incremental/StorageHealthCheckParams.h> #include <binder/IAppOpsCallback.h> #include <utils/String16.h> @@ -69,6 +70,8 @@ using StorageHealthListener = ::android::sp<IStorageHealthListener>; using IStorageLoadingProgressListener = ::android::os::incremental::IStorageLoadingProgressListener; using StorageLoadingProgressListener = ::android::sp<IStorageLoadingProgressListener>; +using PerUidReadTimeouts = ::android::os::incremental::PerUidReadTimeouts; + class IncrementalService final { public: explicit IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir); @@ -98,7 +101,23 @@ public: }; enum StorageFlags { - ReadLogsEnabled = 1, + ReadLogsAllowed = 1 << 0, + ReadLogsEnabled = 1 << 1, + }; + + struct LoadingProgress { + ssize_t filledBlocks; + ssize_t totalBlocks; + + bool isError() const { return totalBlocks < 0; } + bool started() const { return totalBlocks > 0; } + bool fullyLoaded() const { return !isError() && (totalBlocks == filledBlocks); } + + float getProgress() const { + return totalBlocks < 0 + ? totalBlocks + : totalBlocks > 0 ? double(filledBlocks) / double(totalBlocks) : 1.f; + } }; static FileId idFromMetadata(std::span<const uint8_t> metadata); @@ -114,7 +133,8 @@ public: content::pm::DataLoaderParamsParcel&& dataLoaderParams, CreateOptions options, const DataLoaderStatusListener& statusListener, StorageHealthCheckParams&& healthCheckParams, - const StorageHealthListener& healthListener); + const StorageHealthListener& healthListener, + const std::vector<PerUidReadTimeouts>& perUidReadTimeouts); StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage, CreateOptions options = CreateOptions::Default); StorageId openStorage(std::string_view path); @@ -123,7 +143,7 @@ public: int unbind(StorageId storage, std::string_view target); void deleteStorage(StorageId storage); - void disableReadLogs(StorageId storage); + void disallowReadLogs(StorageId storage); int setStorageParams(StorageId storage, bool enableReadLogs); int makeFile(StorageId storage, std::string_view path, int mode, FileId id, @@ -135,8 +155,8 @@ public: std::string_view newPath); int unlink(StorageId storage, std::string_view path); - int isFileFullyLoaded(StorageId storage, const std::string& path) const; - float getLoadingProgress(StorageId storage) const; + int isFileFullyLoaded(StorageId storage, std::string_view filePath) const; + LoadingProgress getLoadingProgress(StorageId storage) const; bool registerLoadingProgressListener(StorageId storage, const StorageLoadingProgressListener& progressListener); bool unregisterLoadingProgressListener(StorageId storage); @@ -282,7 +302,7 @@ private: const std::string root; Control control; /*const*/ MountId mountId; - int32_t flags = StorageFlags::ReadLogsEnabled; + int32_t flags = StorageFlags::ReadLogsAllowed; StorageMap storages; BindMap bindPoints; DataLoaderStubPtr dataLoaderStub; @@ -301,7 +321,15 @@ private: StorageMap::iterator makeStorage(StorageId id); - void disableReadLogs() { flags &= ~StorageFlags::ReadLogsEnabled; } + void disallowReadLogs() { flags &= ~StorageFlags::ReadLogsAllowed; } + int32_t readLogsAllowed() const { return (flags & StorageFlags::ReadLogsAllowed); } + + void setReadLogsEnabled(bool value) { + if (value) + flags |= StorageFlags::ReadLogsEnabled; + else + flags &= ~StorageFlags::ReadLogsEnabled; + } int32_t readLogsEnabled() const { return (flags & StorageFlags::ReadLogsEnabled); } static void cleanupFilesystem(std::string_view root); @@ -313,6 +341,11 @@ private: static bool perfLoggingEnabled(); + void setUidReadTimeouts(StorageId storage, + const std::vector<PerUidReadTimeouts>& perUidReadTimeouts); + void clearUidReadTimeouts(StorageId storage); + void updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit); + std::unordered_set<std::string_view> adoptMountedInstances(); void mountExistingImages(const std::unordered_set<std::string_view>& mountedRootNames); bool mountExistingImage(std::string_view root); @@ -355,7 +388,7 @@ private: binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs); int isFileFullyLoadedFromPath(const IncFsMount& ifs, std::string_view filePath) const; - float getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const; + LoadingProgress getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const; int setFileContent(const IfsMountPtr& ifs, const incfs::FileId& fileId, std::string_view debugFilePath, std::span<const uint8_t> data) const; diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index dfe9684779fe..b1521b0d4e27 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -206,6 +206,11 @@ public: std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final { return incfs::waitForPendingReads(control, timeout, pendingReadsBuffer); } + ErrorCode setUidReadTimeouts(const Control& control, + const std::vector<android::os::incremental::PerUidReadTimeouts>& + perUidReadTimeouts) const final { + return -ENOTSUP; + } }; static JNIEnv* getOrAttachJniEnv(JavaVM* jvm); diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index f2d00735bc44..fad8d67e0da7 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -21,6 +21,7 @@ #include <android/content/pm/FileSystemControlParcel.h> #include <android/content/pm/IDataLoader.h> #include <android/content/pm/IDataLoaderStatusListener.h> +#include <android/os/incremental/PerUidReadTimeouts.h> #include <binder/IAppOpsCallback.h> #include <binder/IServiceManager.h> #include <binder/Status.h> @@ -103,6 +104,10 @@ public: virtual WaitResult waitForPendingReads( const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0; + virtual ErrorCode setUidReadTimeouts( + const Control& control, + const std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts) + const = 0; }; class AppOpsManagerWrapper { diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index 9b8cf4084bf1..02eaa2fe07b1 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -42,6 +42,7 @@ using testing::NiceMock; using namespace android::incfs; using namespace android::content::pm; +using PerUidReadTimeouts = android::os::incremental::PerUidReadTimeouts; namespace android::os::incremental { @@ -307,6 +308,9 @@ public: MOCK_CONST_METHOD3(waitForPendingReads, WaitResult(const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer)); + MOCK_CONST_METHOD2(setUidReadTimeouts, + ErrorCode(const Control& control, + const std::vector<PerUidReadTimeouts>& perUidReadTimeouts)); MockIncFs() { ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); } @@ -665,7 +669,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -676,7 +680,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -689,7 +693,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -703,7 +707,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageBindMountFails) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -721,7 +725,7 @@ TEST_F(IncrementalServiceTest, testCreateStoragePrepareDataLoaderFails) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -735,7 +739,7 @@ TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); mIncrementalService->deleteStorage(storageId); } @@ -750,7 +754,7 @@ TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); // Simulated crash/other connection breakage. mDataLoaderManager->setDataLoaderStatusDestroyed(); @@ -767,7 +771,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); mDataLoaderManager->setDataLoaderStatusCreated(); ASSERT_TRUE(mIncrementalService->startLoading(storageId)); @@ -785,7 +789,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_TRUE(mIncrementalService->startLoading(storageId)); mDataLoaderManager->setDataLoaderStatusCreated(); @@ -802,7 +806,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); mDataLoaderManager->setDataLoaderStatusUnavailable(); } @@ -823,7 +827,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); mDataLoaderManager->setDataLoaderStatusUnavailable(); ASSERT_NE(nullptr, mLooper->mCallback); @@ -877,7 +881,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, std::move(params), listener); + {}, std::move(params), listener, {}); ASSERT_GE(storageId, 0); // Healthy state, registered for pending reads. @@ -972,7 +976,7 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_GE(mDataLoader->setStorageParams(true), 0); } @@ -993,11 +997,11 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndDisabled) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_GE(mDataLoader->setStorageParams(true), 0); // Now disable. - mIncrementalService->disableReadLogs(storageId); + mIncrementalService->disallowReadLogs(storageId); ASSERT_EQ(mDataLoader->setStorageParams(true), -EPERM); } @@ -1019,7 +1023,7 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChang TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_GE(mDataLoader->setStorageParams(true), 0); ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get()); @@ -1038,7 +1042,7 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionFails) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_LT(mDataLoader->setStorageParams(true), 0); } @@ -1057,7 +1061,7 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_LT(mDataLoader->setStorageParams(true), 0); } @@ -1066,7 +1070,7 @@ TEST_F(IncrementalServiceTest, testMakeDirectory) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); std::string dir_path("test"); // Expecting incfs to call makeDir on a path like: @@ -1085,7 +1089,7 @@ TEST_F(IncrementalServiceTest, testMakeDirectories) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); auto first = "first"sv; auto second = "second"sv; auto third = "third"sv; @@ -1108,7 +1112,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithNoFile) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); } @@ -1119,7 +1123,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithFailedRanges) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1); ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); } @@ -1131,7 +1135,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccessWithEmptyRanges) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1); ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); } @@ -1143,7 +1147,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccess) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1); ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); } @@ -1155,8 +1159,8 @@ TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithNoFile) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); - ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId)); + {}, {}, {}, {}); + ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId).getProgress()); } TEST_F(IncrementalServiceTest, testGetLoadingProgressFailsWithFailedRanges) { @@ -1166,9 +1170,9 @@ TEST_F(IncrementalServiceTest, testGetLoadingProgressFailsWithFailedRanges) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1); - ASSERT_EQ(-1, mIncrementalService->getLoadingProgress(storageId)); + ASSERT_EQ(-1, mIncrementalService->getLoadingProgress(storageId).getProgress()); } TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithEmptyRanges) { @@ -1178,9 +1182,9 @@ TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithEmptyRanges) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(3); - ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId)); + ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId).getProgress()); } TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccess) { @@ -1190,9 +1194,9 @@ TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccess) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(3); - ASSERT_EQ(0.5, mIncrementalService->getLoadingProgress(storageId)); + ASSERT_EQ(0.5, mIncrementalService->getLoadingProgress(storageId).getProgress()); } TEST_F(IncrementalServiceTest, testRegisterLoadingProgressListenerSuccess) { @@ -1202,7 +1206,7 @@ TEST_F(IncrementalServiceTest, testRegisterLoadingProgressListenerSuccess) { TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); sp<NiceMock<MockStorageLoadingProgressListener>> listener{ new NiceMock<MockStorageLoadingProgressListener>}; NiceMock<MockStorageLoadingProgressListener>* listenerMock = listener.get(); @@ -1227,7 +1231,7 @@ TEST_F(IncrementalServiceTest, testRegisterLoadingProgressListenerFailsToGetProg TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew, - {}, {}, {}); + {}, {}, {}, {}); sp<NiceMock<MockStorageLoadingProgressListener>> listener{ new NiceMock<MockStorageLoadingProgressListener>}; NiceMock<MockStorageLoadingProgressListener>* listenerMock = listener.get(); @@ -1242,9 +1246,10 @@ TEST_F(IncrementalServiceTest, testRegisterStorageHealthListenerSuccess) { NiceMock<MockStorageHealthListener>* newListenerMock = newListener.get(); TemporaryDir tempDir; - int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), - IncrementalService::CreateOptions::CreateNew, - {}, StorageHealthCheckParams{}, listener); + int storageId = + mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, {}, + StorageHealthCheckParams{}, listener, {}); ASSERT_GE(storageId, 0); StorageHealthCheckParams newParams; newParams.blockedTimeoutMs = 10000; @@ -1313,4 +1318,123 @@ TEST_F(IncrementalServiceTest, testRegisterStorageHealthListenerSuccess) { mTimedQueue->clearJob(storageId); } +static std::vector<PerUidReadTimeouts> createPerUidTimeouts( + std::initializer_list<std::tuple<int, int, int, int>> tuples) { + std::vector<PerUidReadTimeouts> result; + for (auto&& tuple : tuples) { + result.emplace_back(); + auto& timeouts = result.back(); + timeouts.uid = std::get<0>(tuple); + timeouts.minTimeUs = std::get<1>(tuple); + timeouts.minPendingTimeUs = std::get<2>(tuple); + timeouts.maxPendingTimeUs = std::get<3>(tuple); + } + return result; +} + +static ErrorCode checkPerUidTimeouts(const Control& control, + const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) { + std::vector<PerUidReadTimeouts> expected = + createPerUidTimeouts({{0, 1, 2, 3}, {1, 2, 3, 4}, {2, 3, 4, 100000000}}); + EXPECT_EQ(expected, perUidReadTimeouts); + return 0; +} + +static ErrorCode checkPerUidTimeoutsEmpty( + const Control& control, const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) { + EXPECT_EQ(0u, perUidReadTimeouts.size()); + return 0; +} + +TEST_F(IncrementalServiceTest, testPerUidTimeoutsTooShort) { + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1); + EXPECT_CALL(*mDataLoader, start(_)).Times(0); + EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); + EXPECT_CALL(*mIncFs, setUidReadTimeouts(_, _)).Times(0); + EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(0); + EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); + TemporaryDir tempDir; + int storageId = + mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, {}, {}, + {}, + createPerUidTimeouts( + {{0, 1, 2, 3}, {1, 2, 3, 4}, {2, 3, 4, 5}})); + ASSERT_GE(storageId, 0); +} + +TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { + mVold->setIncFsMountOptionsSuccess(); + mAppOpsManager->checkPermissionSuccess(); + mFs->hasFiles(); + + EXPECT_CALL(*mIncFs, setUidReadTimeouts(_, _)) + // First call. + .WillOnce(Invoke(&checkPerUidTimeouts)) + // Fully loaded and no readlogs. + .WillOnce(Invoke(&checkPerUidTimeoutsEmpty)); + EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(3); + + // Empty storage. + mIncFs->countFilledBlocksEmpty(); + + TemporaryDir tempDir; + int storageId = + mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, {}, {}, + {}, + createPerUidTimeouts({{0, 1, 2, 3}, + {1, 2, 3, 4}, + {2, 3, 4, 100000000}})); + ASSERT_GE(storageId, 0); + + { + // Timed callback present -> 0 progress. + ASSERT_EQ(storageId, mTimedQueue->mId); + ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1)); + const auto timedCallback = mTimedQueue->mWhat; + mTimedQueue->clearJob(storageId); + + // Still loading. + mIncFs->countFilledBlocksSuccess(); + + // Call it again. + timedCallback(); + } + + { + // Still present -> 0.5 progress. + ASSERT_EQ(storageId, mTimedQueue->mId); + ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1)); + const auto timedCallback = mTimedQueue->mWhat; + mTimedQueue->clearJob(storageId); + + // Fully loaded but readlogs collection enabled. + mIncFs->countFilledBlocksFullyLoaded(); + ASSERT_GE(mDataLoader->setStorageParams(true), 0); + + // Call it again. + timedCallback(); + } + + { + // Still present -> fully loaded + readlogs. + ASSERT_EQ(storageId, mTimedQueue->mId); + ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1)); + const auto timedCallback = mTimedQueue->mWhat; + mTimedQueue->clearJob(storageId); + + // Now disable readlogs. + ASSERT_GE(mDataLoader->setStorageParams(false), 0); + + // Call it again. + timedCallback(); + } + + // No callbacks anymore -> fully loaded and no readlogs. + ASSERT_EQ(mTimedQueue->mAfter, Milliseconds()); +} + } // namespace android::os::incremental diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java index d54a40e58af1..c010e1995446 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java @@ -29,6 +29,10 @@ import android.util.SparseArray; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.HexDump; +import com.android.server.pm.PerPackageReadTimeouts.Timeouts; +import com.android.server.pm.PerPackageReadTimeouts.VersionCodes; + import com.google.android.collect.Lists; import org.junit.After; @@ -45,6 +49,7 @@ import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +// atest PackageManagerServiceTest // runtest -c com.android.server.pm.PackageManagerServiceTest frameworks-services // bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest @RunWith(AndroidJUnit4.class) @@ -182,6 +187,219 @@ public class PackageManagerServiceTest { } } + @Test + public void testTimeouts() { + Timeouts defaults = Timeouts.parse("3600000001:3600000002:3600000003"); + Assert.assertEquals(3600000001L, defaults.minTimeUs); + Assert.assertEquals(3600000002L, defaults.minPendingTimeUs); + Assert.assertEquals(3600000003L, defaults.maxPendingTimeUs); + + Timeouts empty = Timeouts.parse(""); + Assert.assertEquals(3600000000L, empty.minTimeUs); + Assert.assertEquals(3600000000L, empty.minPendingTimeUs); + Assert.assertEquals(3600000000L, empty.maxPendingTimeUs); + + Timeouts partial0 = Timeouts.parse("10000::"); + Assert.assertEquals(10000L, partial0.minTimeUs); + Assert.assertEquals(3600000000L, partial0.minPendingTimeUs); + Assert.assertEquals(3600000000L, partial0.maxPendingTimeUs); + + Timeouts partial1 = Timeouts.parse("10000:10001:"); + Assert.assertEquals(10000L, partial1.minTimeUs); + Assert.assertEquals(10001L, partial1.minPendingTimeUs); + Assert.assertEquals(3600000000L, partial1.maxPendingTimeUs); + + Timeouts fullDefault = Timeouts.parse("3600000000:3600000000:3600000000"); + Assert.assertEquals(3600000000L, fullDefault.minTimeUs); + Assert.assertEquals(3600000000L, fullDefault.minPendingTimeUs); + Assert.assertEquals(3600000000L, fullDefault.maxPendingTimeUs); + + Timeouts full = Timeouts.parse("10000:10001:10002"); + Assert.assertEquals(10000L, full.minTimeUs); + Assert.assertEquals(10001L, full.minPendingTimeUs); + Assert.assertEquals(10002L, full.maxPendingTimeUs); + + Timeouts invalid0 = Timeouts.parse(":10000"); + Assert.assertEquals(3600000000L, invalid0.minTimeUs); + Assert.assertEquals(3600000000L, invalid0.minPendingTimeUs); + Assert.assertEquals(3600000000L, invalid0.maxPendingTimeUs); + + Timeouts invalid1 = Timeouts.parse(":10000::"); + Assert.assertEquals(3600000000L, invalid1.minTimeUs); + Assert.assertEquals(3600000000L, invalid1.minPendingTimeUs); + Assert.assertEquals(3600000000L, invalid1.maxPendingTimeUs); + + Timeouts invalid2 = Timeouts.parse("10000:10001:abcd"); + Assert.assertEquals(10000L, invalid2.minTimeUs); + Assert.assertEquals(10001L, invalid2.minPendingTimeUs); + Assert.assertEquals(3600000000L, invalid2.maxPendingTimeUs); + + Timeouts invalid3 = Timeouts.parse(":10000:"); + Assert.assertEquals(3600000000L, invalid3.minTimeUs); + Assert.assertEquals(3600000000L, invalid3.minPendingTimeUs); + Assert.assertEquals(3600000000L, invalid3.maxPendingTimeUs); + + Timeouts invalid4 = Timeouts.parse("abcd:10001:10002"); + Assert.assertEquals(3600000000L, invalid4.minTimeUs); + Assert.assertEquals(3600000000L, invalid4.minPendingTimeUs); + Assert.assertEquals(3600000000L, invalid4.maxPendingTimeUs); + + Timeouts invalid5 = Timeouts.parse("::1000000000000000000000000"); + Assert.assertEquals(3600000000L, invalid5.minTimeUs); + Assert.assertEquals(3600000000L, invalid5.minPendingTimeUs); + Assert.assertEquals(3600000000L, invalid5.maxPendingTimeUs); + + Timeouts invalid6 = Timeouts.parse("-10000:10001:10002"); + Assert.assertEquals(3600000000L, invalid6.minTimeUs); + Assert.assertEquals(3600000000L, invalid6.minPendingTimeUs); + Assert.assertEquals(3600000000L, invalid6.maxPendingTimeUs); + } + + @Test + public void testVersionCodes() { + final VersionCodes defaults = VersionCodes.parse(""); + Assert.assertEquals(Long.MIN_VALUE, defaults.minVersionCode); + Assert.assertEquals(Long.MAX_VALUE, defaults.maxVersionCode); + + VersionCodes single = VersionCodes.parse("191000070"); + Assert.assertEquals(191000070, single.minVersionCode); + Assert.assertEquals(191000070, single.maxVersionCode); + + VersionCodes single2 = VersionCodes.parse("191000070-191000070"); + Assert.assertEquals(191000070, single2.minVersionCode); + Assert.assertEquals(191000070, single2.maxVersionCode); + + VersionCodes upto = VersionCodes.parse("-191000070"); + Assert.assertEquals(Long.MIN_VALUE, upto.minVersionCode); + Assert.assertEquals(191000070, upto.maxVersionCode); + + VersionCodes andabove = VersionCodes.parse("191000070-"); + Assert.assertEquals(191000070, andabove.minVersionCode); + Assert.assertEquals(Long.MAX_VALUE, andabove.maxVersionCode); + + VersionCodes range = VersionCodes.parse("191000070-201000070"); + Assert.assertEquals(191000070, range.minVersionCode); + Assert.assertEquals(201000070, range.maxVersionCode); + + VersionCodes invalid0 = VersionCodes.parse("201000070-191000070"); + Assert.assertEquals(Long.MIN_VALUE, invalid0.minVersionCode); + Assert.assertEquals(Long.MAX_VALUE, invalid0.maxVersionCode); + + VersionCodes invalid1 = VersionCodes.parse("abcd-191000070"); + Assert.assertEquals(Long.MIN_VALUE, invalid1.minVersionCode); + Assert.assertEquals(191000070, invalid1.maxVersionCode); + + VersionCodes invalid2 = VersionCodes.parse("abcd"); + Assert.assertEquals(Long.MIN_VALUE, invalid2.minVersionCode); + Assert.assertEquals(Long.MAX_VALUE, invalid2.maxVersionCode); + + VersionCodes invalid3 = VersionCodes.parse("191000070-abcd"); + Assert.assertEquals(191000070, invalid3.minVersionCode); + Assert.assertEquals(Long.MAX_VALUE, invalid3.maxVersionCode); + } + + @Test + public void testPerPackageReadTimeouts() { + final String sha256 = "336faefc91bb2dddf9b21829106fbc607b862132fecd273e1b6b3ea55f09d4e1"; + final VersionCodes defVCs = VersionCodes.parse(""); + final Timeouts defTs = Timeouts.parse("3600000001:3600000002:3600000003"); + + PerPackageReadTimeouts empty = PerPackageReadTimeouts.parse("", defVCs, defTs); + Assert.assertNull(empty); + + PerPackageReadTimeouts packageOnly = PerPackageReadTimeouts.parse("package.com", defVCs, + defTs); + Assert.assertEquals("package.com", packageOnly.packageName); + Assert.assertEquals(null, packageOnly.sha256certificate); + Assert.assertEquals(Long.MIN_VALUE, packageOnly.versionCodes.minVersionCode); + Assert.assertEquals(Long.MAX_VALUE, packageOnly.versionCodes.maxVersionCode); + Assert.assertEquals(3600000001L, packageOnly.timeouts.minTimeUs); + Assert.assertEquals(3600000002L, packageOnly.timeouts.minPendingTimeUs); + Assert.assertEquals(3600000003L, packageOnly.timeouts.maxPendingTimeUs); + + PerPackageReadTimeouts packageHash = PerPackageReadTimeouts.parse( + "package.com:" + sha256, defVCs, defTs); + Assert.assertEquals("package.com", packageHash.packageName); + Assert.assertEquals(sha256, bytesToHexString(packageHash.sha256certificate)); + Assert.assertEquals(Long.MIN_VALUE, packageHash.versionCodes.minVersionCode); + Assert.assertEquals(Long.MAX_VALUE, packageHash.versionCodes.maxVersionCode); + Assert.assertEquals(3600000001L, packageHash.timeouts.minTimeUs); + Assert.assertEquals(3600000002L, packageHash.timeouts.minPendingTimeUs); + Assert.assertEquals(3600000003L, packageHash.timeouts.maxPendingTimeUs); + + PerPackageReadTimeouts packageVersionCode = PerPackageReadTimeouts.parse( + "package.com::191000070", defVCs, defTs); + Assert.assertEquals("package.com", packageVersionCode.packageName); + Assert.assertEquals(null, packageVersionCode.sha256certificate); + Assert.assertEquals(191000070, packageVersionCode.versionCodes.minVersionCode); + Assert.assertEquals(191000070, packageVersionCode.versionCodes.maxVersionCode); + Assert.assertEquals(3600000001L, packageVersionCode.timeouts.minTimeUs); + Assert.assertEquals(3600000002L, packageVersionCode.timeouts.minPendingTimeUs); + Assert.assertEquals(3600000003L, packageVersionCode.timeouts.maxPendingTimeUs); + + PerPackageReadTimeouts full = PerPackageReadTimeouts.parse( + "package.com:" + sha256 + ":191000070-201000070:10001:10002:10003", defVCs, defTs); + Assert.assertEquals("package.com", full.packageName); + Assert.assertEquals(sha256, bytesToHexString(full.sha256certificate)); + Assert.assertEquals(191000070, full.versionCodes.minVersionCode); + Assert.assertEquals(201000070, full.versionCodes.maxVersionCode); + Assert.assertEquals(10001L, full.timeouts.minTimeUs); + Assert.assertEquals(10002L, full.timeouts.minPendingTimeUs); + Assert.assertEquals(10003L, full.timeouts.maxPendingTimeUs); + } + + @Test + public void testGetPerPackageReadTimeouts() { + Assert.assertEquals(0, getPerPackageReadTimeouts(null).length); + Assert.assertEquals(0, getPerPackageReadTimeouts("").length); + Assert.assertEquals(0, getPerPackageReadTimeouts(",,,,").length); + + final String sha256 = "0fae93f1a7925b4c68bbea80ad3eaa41acfc9bc6f10bf1054f5d93a2bd556093"; + + PerPackageReadTimeouts[] singlePackage = getPerPackageReadTimeouts( + "package.com:" + sha256 + ":191000070-201000070:10001:10002:10003"); + Assert.assertEquals(1, singlePackage.length); + Assert.assertEquals("package.com", singlePackage[0].packageName); + Assert.assertEquals(sha256, bytesToHexString(singlePackage[0].sha256certificate)); + Assert.assertEquals(191000070, singlePackage[0].versionCodes.minVersionCode); + Assert.assertEquals(201000070, singlePackage[0].versionCodes.maxVersionCode); + Assert.assertEquals(10001L, singlePackage[0].timeouts.minTimeUs); + Assert.assertEquals(10002L, singlePackage[0].timeouts.minPendingTimeUs); + Assert.assertEquals(10003L, singlePackage[0].timeouts.maxPendingTimeUs); + + PerPackageReadTimeouts[] multiPackage = getPerPackageReadTimeouts("package.com:" + sha256 + + ":191000070-201000070:10001:10002:10003,package1.com::123456"); + Assert.assertEquals(2, multiPackage.length); + Assert.assertEquals("package.com", multiPackage[0].packageName); + Assert.assertEquals(sha256, bytesToHexString(multiPackage[0].sha256certificate)); + Assert.assertEquals(191000070, multiPackage[0].versionCodes.minVersionCode); + Assert.assertEquals(201000070, multiPackage[0].versionCodes.maxVersionCode); + Assert.assertEquals(10001L, multiPackage[0].timeouts.minTimeUs); + Assert.assertEquals(10002L, multiPackage[0].timeouts.minPendingTimeUs); + Assert.assertEquals(10003L, multiPackage[0].timeouts.maxPendingTimeUs); + Assert.assertEquals("package1.com", multiPackage[1].packageName); + Assert.assertEquals(null, multiPackage[1].sha256certificate); + Assert.assertEquals(123456, multiPackage[1].versionCodes.minVersionCode); + Assert.assertEquals(123456, multiPackage[1].versionCodes.maxVersionCode); + Assert.assertEquals(3600000001L, multiPackage[1].timeouts.minTimeUs); + Assert.assertEquals(3600000002L, multiPackage[1].timeouts.minPendingTimeUs); + Assert.assertEquals(3600000003L, multiPackage[1].timeouts.maxPendingTimeUs); + } + + private static PerPackageReadTimeouts[] getPerPackageReadTimeouts(String knownDigestersList) { + final String defaultTimeouts = "3600000001:3600000002:3600000003"; + List<PerPackageReadTimeouts> result = PerPackageReadTimeouts.parseDigestersList( + defaultTimeouts, knownDigestersList); + if (result == null) { + return null; + } + return result.toArray(new PerPackageReadTimeouts[result.size()]); + } + + private static String bytesToHexString(byte[] bytes) { + return HexDump.toHexString(bytes, 0, bytes.length, /*upperCase=*/ false); + } + private List<Integer> getKnownPackageIdsList() throws IllegalAccessException { final ArrayList<Integer> knownPackageIds = new ArrayList<>(); final Field[] allFields = PackageManagerInternal.class.getDeclaredFields(); |