diff options
99 files changed, 2410 insertions, 511 deletions
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt index a3e05dc7cd85..47cf17cb1d4c 100644 --- a/apex/appsearch/framework/api/current.txt +++ b/apex/appsearch/framework/api/current.txt @@ -149,7 +149,8 @@ package android.app.appsearch { method public void remove(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); method public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec); - method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>); + method @Deprecated public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>); + method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>); } public interface BatchResultCallback<KeyType, ValueType> { diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java index ce4aad1f0c1e..bf733ed77442 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -104,6 +104,18 @@ public final class AppSearchSession implements Closeable { } /** + * TODO(b/181887768): This method exists only for dogfooder transition and must be removed. + * @deprecated This method exists only for dogfooder transition and must be removed. + */ + @Deprecated + public void setSchema( + @NonNull SetSchemaRequest request, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) { + setSchema(request, callbackExecutor, callbackExecutor, callback); + } + + /** * Sets the schema that represents the organizational structure of data within the AppSearch * database. * @@ -113,7 +125,9 @@ public final class AppSearchSession implements Closeable { * no-op call. * * @param request the schema to set or update the AppSearch database to. - * @param executor Executor on which to invoke the callback. + * @param workExecutor Executor on which to schedule heavy client-side background work such as + * transforming documents. + * @param callbackExecutor Executor on which to invoke the callback. * @param callback Callback to receive errors resulting from setting the schema. If the * operation succeeds, the callback will be invoked with {@code null}. */ @@ -121,10 +135,12 @@ public final class AppSearchSession implements Closeable { // exposed. public void setSchema( @NonNull SetSchemaRequest request, - @NonNull @CallbackExecutor Executor executor, + @NonNull Executor workExecutor, + @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) { Objects.requireNonNull(request); - Objects.requireNonNull(executor); + Objects.requireNonNull(workExecutor); + Objects.requireNonNull(callbackExecutor); Objects.requireNonNull(callback); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); List<Bundle> schemaBundles = new ArrayList<>(request.getSchemas().size()); @@ -153,10 +169,12 @@ public final class AppSearchSession implements Closeable { request.getVersion(), new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { - executor.execute(() -> { + callbackExecutor.execute(() -> { if (result.isSuccess()) { callback.accept( // TODO(b/177266929) implement Migration in platform. + // TODO(b/183177268): once migration is implemented, run + // it on workExecutor. AppSearchResult.newSuccessfulResult( new SetSchemaResponse.Builder().build())); } else { @@ -332,8 +350,7 @@ public final class AppSearchSession implements Closeable { // Translate successful results for (Map.Entry<String, Bundle> bundleEntry : - (Set<Map.Entry<String, Bundle>>) - result.getSuccesses().entrySet()) { + ((Map<String, Bundle>) result.getSuccesses()).entrySet()) { GenericDocument document; try { document = new GenericDocument(bundleEntry.getValue()); @@ -352,8 +369,8 @@ public final class AppSearchSession implements Closeable { // Translate failed results for (Map.Entry<String, AppSearchResult<Bundle>> bundleEntry : - (Set<Map.Entry<String, AppSearchResult<Bundle>>>) - result.getFailures().entrySet()) { + ((Map<String, AppSearchResult<Bundle>>) + result.getFailures()).entrySet()) { documentResultBuilder.setFailure( bundleEntry.getKey(), bundleEntry.getValue().getResultCode(), diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java index bc3064155d34..f0de4962ad3c 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java @@ -89,7 +89,7 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { @NonNull public ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request) { SettableFuture<AppSearchResult<SetSchemaResponse>> future = SettableFuture.create(); - mAppSearchSession.setSchema(request, mExecutor, future::set); + mAppSearchSession.setSchema(request, mExecutor, mExecutor, future::set); return Futures.transformAsync(future, this::transformResult, mExecutor); } diff --git a/core/api/current.txt b/core/api/current.txt index 11f521565e9e..6abb3f7d8c1a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -37485,6 +37485,7 @@ package android.service.autofill { method public void onDisconnected(); method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback); method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback); + method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback); field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final String SERVICE_META_DATA = "android.autofill"; } @@ -37760,6 +37761,22 @@ package android.service.autofill { field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR; } + public final class SavedDatasetsInfo { + ctor public SavedDatasetsInfo(@NonNull String, @IntRange(from=0) int); + method @IntRange(from=0) public int getCount(); + method @NonNull public String getType(); + field public static final String TYPE_OTHER = "other"; + field public static final String TYPE_PASSWORDS = "passwords"; + } + + public interface SavedDatasetsInfoCallback { + method public void onError(int); + method public void onSuccess(@NonNull java.util.Set<android.service.autofill.SavedDatasetsInfo>); + field public static final int ERROR_NEEDS_USER_ACTION = 2; // 0x2 + field public static final int ERROR_OTHER = 0; // 0x0 + field public static final int ERROR_UNSUPPORTED = 1; // 0x1 + } + public final class TextValueSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer { ctor public TextValueSanitizer(@NonNull java.util.regex.Pattern, @NonNull String); method public int describeContents(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 55dd8b2d81b4..2094e981f68e 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -92,6 +92,7 @@ package android { field public static final String CREATE_USERS = "android.permission.CREATE_USERS"; field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER"; field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER"; + field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS"; field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE"; field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT"; field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED"; @@ -311,6 +312,7 @@ package android { field public static final int hotwordDetectionService = 16844326; // 0x1010626 field public static final int isVrOnly = 16844152; // 0x1010578 field public static final int minExtensionVersion = 16844305; // 0x1010611 + field public static final int playHomeTransitionSound = 16844358; // 0x1010646 field public static final int requiredSystemPropertyName = 16844133; // 0x1010565 field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566 field public static final int sdkVersion = 16844304; // 0x1010610 @@ -7575,9 +7577,11 @@ package android.net.netstats.provider { method public void notifyAlertReached(); method public void notifyLimitReached(); method public void notifyStatsUpdated(int, @NonNull android.net.NetworkStats, @NonNull android.net.NetworkStats); + method public void notifyWarningReached(); method public abstract void onRequestStatsUpdate(int); method public abstract void onSetAlert(long); method public abstract void onSetLimit(@NonNull String, long); + method public void onSetWarningAndLimit(@NonNull String, long, long); field public static final int QUOTA_UNLIMITED = -1; // 0xffffffff } @@ -12301,6 +12305,20 @@ package android.telephony.data { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.EpsBearerQosSessionAttributes> CREATOR; } + public final class NrQosSessionAttributes implements android.os.Parcelable android.net.QosSessionAttributes { + method public int describeContents(); + method public int get5Qi(); + method public long getAveragingWindow(); + method public long getGuaranteedDownlinkBitRate(); + method public long getGuaranteedUplinkBitRate(); + method public long getMaxDownlinkBitRate(); + method public long getMaxUplinkBitRate(); + method public int getQfi(); + method @NonNull public java.util.List<java.net.InetSocketAddress> getRemoteAddresses(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.NrQosSessionAttributes> CREATOR; + } + public abstract class QualifiedNetworksService extends android.app.Service { ctor public QualifiedNetworksService(); method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 746a0b6f8e30..97ad48c96829 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -233,6 +233,8 @@ package android.app { field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time"; field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls"; + field public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera"; + field public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone"; field public static final String OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = "android:use_icc_auth_with_device_identifier"; field public static final int OP_COARSE_LOCATION = 0; // 0x0 field public static final int OP_RECORD_AUDIO = 27; // 0x1b @@ -1931,6 +1933,17 @@ package android.os.vibrator { package android.permission { + public final class PermGroupUsage { + ctor public PermGroupUsage(@NonNull String, int, @NonNull String, long, boolean, boolean, @Nullable CharSequence); + method @Nullable public CharSequence getAttribution(); + method public long getLastAccess(); + method @NonNull public String getPackageName(); + method @NonNull public String getPermGroupName(); + method public int getUid(); + method public boolean isActive(); + method public boolean isPhoneCall(); + } + public final class PermissionControllerManager { method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void countPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, @Nullable android.os.Handler); method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler); @@ -1945,6 +1958,10 @@ package android.permission { method public void onGetAppPermissions(@NonNull java.util.List<android.permission.RuntimePermissionPresentationInfo>); } + public final class PermissionManager { + method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(); + } + } package android.print { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index a6aa28effe00..7e4af1ad7952 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1561,12 +1561,14 @@ public class AppOpsManager { * * @hide */ + @TestApi public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone"; /** * Phone call is using camera * * @hide */ + @TestApi public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera"; /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 117df02a2d7c..d3534f9bb3c7 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -872,8 +872,7 @@ public class DevicePolicyManager { * * The name is displayed only during provisioning. * - * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} - * or {@link #ACTION_PROVISION_FINANCED_DEVICE} + * <p>Use in an intent with action {@link #ACTION_PROVISION_FINANCED_DEVICE} * * @hide */ diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index feb58a30e519..0952b3e1233c 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -550,9 +550,18 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public static final int FLAG_INHERIT_SHOW_WHEN_LOCKED = 0x1; /** + * Bit in {@link #privateFlags} indicating whether a home sound effect should be played if the + * home app moves to front after the activity with this flag set. + * Set from the {@link android.R.attr#playHomeTransitionSound} attribute. + * @hide + */ + public static final int PRIVATE_FLAG_HOME_TRANSITION_SOUND = 0x2; + + /** * Options that have been set in the activity declaration in the manifest. * These include: - * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED}. + * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED}, + * {@link #PRIVATE_FLAG_HOME_TRANSITION_SOUND}. * @hide */ public int privateFlags; diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java index d99c4109e5ad..ff6aaad09d09 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java @@ -149,7 +149,10 @@ public class ParsedActivityUtils { | flag(ActivityInfo.FLAG_TURN_SCREEN_ON, R.styleable.AndroidManifestActivity_turnScreenOn, sa) | flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa); - activity.privateFlags |= flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa); + activity.privateFlags |= flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED, + R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa) + | flag(ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND, + R.styleable.AndroidManifestActivity_playHomeTransitionSound, true, sa); activity.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode, ActivityInfo.COLOR_MODE_DEFAULT); activity.documentLaunchMode = sa.getInt(R.styleable.AndroidManifestActivity_documentLaunchMode, ActivityInfo.DOCUMENT_LAUNCH_NONE); diff --git a/core/java/android/net/netstats/provider/NetworkStatsProvider.java b/core/java/android/net/netstats/provider/NetworkStatsProvider.java index 65b336ad6fce..23fc06927ef9 100644 --- a/core/java/android/net/netstats/provider/NetworkStatsProvider.java +++ b/core/java/android/net/netstats/provider/NetworkStatsProvider.java @@ -147,10 +147,7 @@ public abstract class NetworkStatsProvider { /** * Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached. - * - * @hide */ - // TODO: Expose as system API. public void notifyWarningReached() { try { // Reuse the code path to notify warning reached with limit reached @@ -198,7 +195,6 @@ public abstract class NetworkStatsProvider { * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit. */ - // TODO: deprecate this once onSetWarningAndLimit is ready. public abstract void onSetLimit(@NonNull String iface, long quotaBytes); /** @@ -217,10 +213,7 @@ public abstract class NetworkStatsProvider { * there is no warning. * @param limitBytes the limit defined as the number of bytes, starting from zero and counting * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit. - * - * @hide */ - // TODO: Expose as system API. public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) { // Backward compatibility for those who didn't override this function. onSetLimit(iface, limitBytes); diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index 9518bf197fa0..85861bc1d2aa 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -60,14 +60,18 @@ public final class BatteryUsageStatsQuery implements Parcelable { */ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY = 2; + private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000; + private final int mFlags; @NonNull private final int[] mUserIds; + private final long mMaxStatsAgeMs; private BatteryUsageStatsQuery(@NonNull Builder builder) { mFlags = builder.mFlags; mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray() : new int[]{UserHandle.USER_ALL}; + mMaxStatsAgeMs = builder.mMaxStatsAgeMs; } @BatteryUsageStatsFlags @@ -94,10 +98,19 @@ public final class BatteryUsageStatsQuery implements Parcelable { return (mFlags & FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL) != 0; } + /** + * Returns the client's tolerance for stale battery stats. The data is allowed to be up to + * this many milliseconds out-of-date. + */ + public long getMaxStatsAge() { + return mMaxStatsAgeMs; + } + private BatteryUsageStatsQuery(Parcel in) { mFlags = in.readInt(); mUserIds = new int[in.readInt()]; in.readIntArray(mUserIds); + mMaxStatsAgeMs = in.readLong(); } @Override @@ -105,6 +118,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { dest.writeInt(mFlags); dest.writeInt(mUserIds.length); dest.writeIntArray(mUserIds); + dest.writeLong(mMaxStatsAgeMs); } @Override @@ -132,6 +146,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { public static final class Builder { private int mFlags; private IntArray mUserIds; + private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS; /** * Builds a read-only BatteryUsageStatsQuery object. @@ -170,5 +185,14 @@ public final class BatteryUsageStatsQuery implements Parcelable { mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL; return this; } + + /** + * Set the client's tolerance for stale battery stats. The data may be up to + * this many milliseconds out-of-date. + */ + public Builder setMaxStatsAgeMs(long maxStatsAgeMs) { + mMaxStatsAgeMs = maxStatsAgeMs; + return this; + } } } diff --git a/core/java/android/permission/PermGroupUsage.java b/core/java/android/permission/PermGroupUsage.java index c94c0ffd4652..440d6f269646 100644 --- a/core/java/android/permission/PermGroupUsage.java +++ b/core/java/android/permission/PermGroupUsage.java @@ -18,6 +18,7 @@ package android.permission; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; /** * Represents the usage of a permission group by an app. Supports package name, user, permission @@ -26,6 +27,7 @@ import android.annotation.Nullable; * * @hide */ +@TestApi public final class PermGroupUsage { private final String mPackageName; @@ -36,7 +38,19 @@ public final class PermGroupUsage { private final boolean mIsPhoneCall; private final CharSequence mAttribution; - PermGroupUsage(@NonNull String packageName, int uid, + /** + * + * @param packageName The package name of the using app + * @param uid The uid of the using app + * @param permGroupName The name of the permission group being used + * @param lastAccess The time of last access + * @param isActive Whether this is active + * @param isPhoneCall Whether this is a usage by the phone + * @param attribution An optional string attribution to show + * @hide + */ + @TestApi + public PermGroupUsage(@NonNull String packageName, int uid, @NonNull String permGroupName, long lastAccess, boolean isActive, boolean isPhoneCall, @Nullable CharSequence attribution) { this.mPackageName = packageName; @@ -48,30 +62,58 @@ public final class PermGroupUsage { this.mAttribution = attribution; } + /** + * @hide + */ + @TestApi public @NonNull String getPackageName() { return mPackageName; } + /** + * @hide + */ + @TestApi public int getUid() { return mUid; } + /** + * @hide + */ + @TestApi public @NonNull String getPermGroupName() { return mPermGroupName; } + /** + * @hide + */ + @TestApi public long getLastAccess() { return mLastAccess; } + /** + * @hide + */ + @TestApi public boolean isActive() { return mIsActive; } + /** + * @hide + */ + @TestApi public boolean isPhoneCall() { return mIsPhoneCall; } + /** + * @hide + */ + @TestApi public @Nullable CharSequence getAttribution() { return mAttribution; } @@ -80,6 +122,7 @@ public final class PermGroupUsage { public String toString() { return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " packageName: " + mPackageName + ", UID: " + mUid + ", permGroup: " - + mPermGroupName + ", isActive: " + mIsActive + ", attribution: " + mAttribution; + + mPermGroupName + ", lastAccess: " + mLastAccess + ", isActive: " + mIsActive + + ", attribution: " + mAttribution; } } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 177e422e7851..7669586cfc1f 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -26,6 +26,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityThread; @@ -851,6 +852,7 @@ public final class PermissionManager { * * @hide */ + @TestApi @NonNull @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS) public List<PermGroupUsage> getIndicatorAppOpUsageData() { diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 921911bbf479..2d6fa3c77966 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -71,13 +71,10 @@ public class PermissionUsageHelper { private static final String PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled"; /** How long after an access to show it as "recent" */ - private static final String RECENT_ACCESS_TIME_MS = "recent_acccess_time_ms"; + private static final String RECENT_ACCESS_TIME_MS = "recent_access_time_ms"; /** How long after an access to show it as "running" */ - private static final String RUNNING_ACCESS_TIME_MS = "running_acccess_time_ms"; - - /** The name of the expected voice IME subtype */ - private static final String VOICE_IME_SUBTYPE = "voice"; + private static final String RUNNING_ACCESS_TIME_MS = "running_access_time_ms"; private static final String SYSTEM_PKG = "android"; @@ -279,6 +276,10 @@ public class PermissionUsageHelper { opEntry.getAttributedOpEntries().get(attributionTag); long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags); + if (attrOpEntry.isRunning()) { + lastAccessTime = now; + } + if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) { continue; } diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 04a4ca47d305..13274c6f27ee 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -38,6 +38,8 @@ import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; +import com.android.internal.os.IResultReceiver; + /** * An {@code AutofillService} is a service used to automatically fill the contents of the screen * on behalf of a given user - for more information about autofill, read @@ -575,6 +577,20 @@ public abstract class AutofillService extends Service { */ public static final String SERVICE_META_DATA = "android.autofill"; + /** + * Name of the {@link IResultReceiver} extra used to return the primary result of a request. + * + * @hide + */ + public static final String EXTRA_RESULT = "result"; + + /** + * Name of the {@link IResultReceiver} extra used to return the error reason of a request. + * + * @hide + */ + public static final String EXTRA_ERROR = "error"; + private final IAutoFillService mInterface = new IAutoFillService.Stub() { @Override public void onConnectedStateChanged(boolean connected) { @@ -603,6 +619,14 @@ public abstract class AutofillService extends Service { AutofillService::onSaveRequest, AutofillService.this, request, new SaveCallback(callback))); } + + @Override + public void onSavedPasswordCountRequest(IResultReceiver receiver) { + mHandler.sendMessage(obtainMessage( + AutofillService::onSavedDatasetsInfoRequest, + AutofillService.this, + new SavedDatasetsInfoCallbackImpl(receiver, SavedDatasetsInfo.TYPE_PASSWORDS))); + } }; private Handler mHandler; @@ -673,6 +697,19 @@ public abstract class AutofillService extends Service { @NonNull SaveCallback callback); /** + * Called from system settings to display information about the datasets the user saved to this + * service. + * + * <p>There is no timeout for the request, but it's recommended to return the result within a + * few seconds, or the user may navigate away from the activity that would display the result. + * + * @param callback callback for responding to the request + */ + public void onSavedDatasetsInfoRequest(@NonNull SavedDatasetsInfoCallback callback) { + callback.onError(SavedDatasetsInfoCallback.ERROR_UNSUPPORTED); + } + + /** * Called when the Android system disconnects from the service. * * <p> At this point this service may no longer be an active {@link AutofillService}. diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index 23a1a3fee47c..d88e0945bdca 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -31,4 +31,5 @@ oneway interface IAutoFillService { void onConnectedStateChanged(boolean connected); void onFillRequest(in FillRequest request, in IFillCallback callback); void onSaveRequest(in SaveRequest request, in ISaveCallback callback); + void onSavedPasswordCountRequest(in IResultReceiver receiver); } diff --git a/core/java/android/service/autofill/SavedDatasetsInfo.java b/core/java/android/service/autofill/SavedDatasetsInfo.java new file mode 100644 index 000000000000..6a4d2b834cef --- /dev/null +++ b/core/java/android/service/autofill/SavedDatasetsInfo.java @@ -0,0 +1,186 @@ +/* + * 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 android.service.autofill; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.StringDef; + +import com.android.internal.util.DataClass; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A result returned from + * {@link AutofillService#onSavedDatasetsInfoRequest(SavedDatasetsInfoCallback)}. + */ +@DataClass( + genToString = true, + genHiddenConstDefs = true, + genEqualsHashCode = true) +public final class SavedDatasetsInfo { + + /** + * Any other type of datasets. + */ + public static final String TYPE_OTHER = "other"; + + /** + * Datasets such as login credentials. + */ + public static final String TYPE_PASSWORDS = "passwords"; + + /** + * The type of the datasets that this info is about. + */ + @NonNull + @Type + private final String mType; + + /** + * The number of datasets of {@link #getType() this type} that the user has saved to the + * service. + */ + @IntRange(from = 0) + private final int mCount; + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/SavedDatasetsInfo.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** @hide */ + @StringDef(prefix = "TYPE_", value = { + TYPE_OTHER, + TYPE_PASSWORDS + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Type {} + + /** + * Creates a new SavedDatasetsInfo. + * + * @param type + * The type of the datasets. + * @param count + * The number of datasets of this type that the user has saved to the service. + */ + @DataClass.Generated.Member + public SavedDatasetsInfo( + @NonNull @Type String type, + @IntRange(from = 0) int count) { + this.mType = type; + + if (!(java.util.Objects.equals(mType, TYPE_OTHER)) + && !(java.util.Objects.equals(mType, TYPE_PASSWORDS))) { + throw new java.lang.IllegalArgumentException( + "type was " + mType + " but must be one of: " + + "TYPE_OTHER(" + TYPE_OTHER + "), " + + "TYPE_PASSWORDS(" + TYPE_PASSWORDS + ")"); + } + + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mType); + this.mCount = count; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mCount, + "from", 0); + + // onConstructed(); // You can define this method to get a callback + } + + /** + * The type of the datasets. + */ + @DataClass.Generated.Member + public @NonNull @Type String getType() { + return mType; + } + + /** + * The number of datasets of this type that the user has saved to the service. + */ + @DataClass.Generated.Member + public @IntRange(from = 0) int getCount() { + return mCount; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "SavedDatasetsInfo { " + + "type = " + mType + ", " + + "count = " + mCount + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(SavedDatasetsInfo other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + SavedDatasetsInfo that = (SavedDatasetsInfo) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mType, that.mType) + && mCount == that.mCount; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mType); + _hash = 31 * _hash + mCount; + return _hash; + } + + @DataClass.Generated( + time = 1615325704446L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/service/autofill/SavedDatasetsInfo.java", + inputSignatures = "public static final java.lang.String TYPE_OTHER\npublic static final java.lang.String TYPE_PASSWORDS\nprivate final @android.annotation.NonNull @android.service.autofill.SavedDatasetsInfo.Type java.lang.String mType\nprivate final @android.annotation.IntRange int mCount\nclass SavedDatasetsInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/autofill/SavedDatasetsInfoCallback.java b/core/java/android/service/autofill/SavedDatasetsInfoCallback.java new file mode 100644 index 000000000000..a47105a23816 --- /dev/null +++ b/core/java/android/service/autofill/SavedDatasetsInfoCallback.java @@ -0,0 +1,72 @@ +/* + * 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 android.service.autofill; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Set; + +/** + * Handles the response to + * {@link AutofillService#onSavedDatasetsInfoRequest(SavedDatasetsInfoCallback)}. + * <p> + * Use {@link #onSuccess(Set)} to return the computed info about the datasets the user saved to this + * service. If there was an error querying the info, or if the service is unable to do so at this + * time (for example, if the user isn't logged in), call {@link #onError(int)}. + * <p> + * This callback can be used only once. + */ +public interface SavedDatasetsInfoCallback { + + /** @hide */ + @IntDef(prefix = {"ERROR_"}, value = { + ERROR_OTHER, + ERROR_UNSUPPORTED, + ERROR_NEEDS_USER_ACTION + }) + @Retention(RetentionPolicy.SOURCE) + @interface Error { + } + + /** + * The result could not be computed for any other reason. + */ + int ERROR_OTHER = 0; + /** + * The service does not support this request. + */ + int ERROR_UNSUPPORTED = 1; + /** + * The result cannot be computed until the user takes some action, such as setting up their + * account. + */ + int ERROR_NEEDS_USER_ACTION = 2; + + /** + * Successfully respond to the request with the info on each type of saved datasets. + */ + void onSuccess(@NonNull Set<SavedDatasetsInfo> results); + + /** + * Respond to the request with an error. System settings may display a suitable notice to the + * user. + */ + void onError(@Error int error); +} diff --git a/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java b/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java new file mode 100644 index 000000000000..b8a8cde12d3e --- /dev/null +++ b/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java @@ -0,0 +1,93 @@ +/* + * 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 android.service.autofill; + +import static android.service.autofill.AutofillService.EXTRA_ERROR; +import static android.service.autofill.AutofillService.EXTRA_RESULT; + +import static com.android.internal.util.Preconditions.checkArgumentInRange; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.os.Bundle; +import android.os.DeadObjectException; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.os.IResultReceiver; + +import java.util.Set; + +final class SavedDatasetsInfoCallbackImpl implements SavedDatasetsInfoCallback { + private static final String TAG = "AutofillService"; + + @NonNull + private final IResultReceiver mReceiver; + @NonNull + private final String mType; + + /** + * Creates a {@link SavedDatasetsInfoCallback} that returns the {@link + * SavedDatasetsInfo#getCount() number} of saved datasets of {@code type} to the {@code + * receiver}. + */ + SavedDatasetsInfoCallbackImpl(@NonNull IResultReceiver receiver, @NonNull String type) { + mReceiver = requireNonNull(receiver); + mType = requireNonNull(type); + } + + @Override + public void onSuccess(@NonNull Set<SavedDatasetsInfo> results) { + requireNonNull(results); + if (results.isEmpty()) { + send(1, null); + return; + } + int count = -1; + for (SavedDatasetsInfo info : results) { + if (mType.equals(info.getType())) { + count = info.getCount(); + } + } + if (count < 0) { + send(1, null); + return; + } + Bundle bundle = new Bundle(/* capacity= */ 1); + bundle.putInt(EXTRA_RESULT, count); + send(0, bundle); + } + + @Override + public void onError(@Error int error) { + checkArgumentInRange(error, ERROR_OTHER, ERROR_NEEDS_USER_ACTION, "error"); + Bundle bundle = new Bundle(/* capacity= */ 1); + bundle.putInt(EXTRA_ERROR, error); + send(1, bundle); + } + + private void send(int resultCode, Bundle bundle) { + try { + mReceiver.send(resultCode, bundle); + } catch (DeadObjectException e) { + Log.w(TAG, "Failed to send onSavedPasswordCountRequest result: " + e); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } +} diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index f1eef9fad8a1..82106b09ca5c 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1389,6 +1389,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // If we are using BLAST, merge the transaction with the viewroot buffer transaction. viewRoot.mergeWithNextTransaction(mRtTransaction, frameNumber); return; + } else { + mRtTransaction.apply(); } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index ec0a8d8ede9d..27e77bfe11dd 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -16525,6 +16525,8 @@ public class BatteryStatsImpl extends BatteryStats { // Pull the clock time. This may update the time and make a new history entry // if we had originally pulled a time before the RTC was set. getStartClockTime(); + + updateSystemServiceCallStats(); } public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) { diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index 4f99c94e0dc5..f8ae0c1858f8 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -21,9 +21,7 @@ import android.hardware.SensorManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.Bundle; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; @@ -88,29 +86,31 @@ public class BatteryUsageStatsProvider { } /** - * Returns snapshots of battery attribution data, one per supplied query. + * Returns true if the last update was too long ago for the tolerances specified + * by the supplied queries. */ - public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { - - // TODO(b/174186345): instead of BatteryStatsHelper, use PowerCalculators directly. - final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(mContext, - false /* collectBatteryBroadcast */); - batteryStatsHelper.create((Bundle) null); - final List<UserHandle> users = new ArrayList<>(); - for (int i = 0; i < queries.size(); i++) { + public boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries, + long lastUpdateTimeStampMs) { + long allowableStatsAge = Long.MAX_VALUE; + for (int i = queries.size() - 1; i >= 0; i--) { BatteryUsageStatsQuery query = queries.get(i); - for (int userId : query.getUserIds()) { - UserHandle userHandle = UserHandle.of(userId); - if (!users.contains(userHandle)) { - users.add(userHandle); - } - } + allowableStatsAge = Math.min(allowableStatsAge, query.getMaxStatsAge()); } - batteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, users); + return mStats.mClocks.elapsedRealtime() - lastUpdateTimeStampMs > allowableStatsAge; + } + + /** + * Returns snapshots of battery attribution data, one per supplied query. + */ + public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size()); - for (int i = 0; i < queries.size(); i++) { - results.add(getBatteryUsageStats(queries.get(i))); + synchronized (mStats) { + mStats.prepareForDumpLocked(); + + for (int i = 0; i < queries.size(); i++) { + results.add(getBatteryUsageStats(queries.get(i))); + } } return results; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 38ef9d2ea525..1d462bc5dd48 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4440,6 +4440,13 @@ <permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to disable system sound effects when the user exits one of + the application's activities. + <p>Not for use by third-party applications.</p> + @hide --> + <permission android:name="android.permission.DISABLE_SYSTEM_SOUND_EFFECTS" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to provide remote displays. <p>Not for use by third-party applications.</p> @hide --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 38e8f83ff767..dc4f52e980ce 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2862,6 +2862,13 @@ {@link android.content.Context#sendBroadcast(Intent, String)} being used. Multiple tags can be specified separated by '|'. --> <attr name="attributionTags"/> + <!-- Specifies whether a home sound effect should be played if the home app moves to + front after an activity with this flag set to <code>true</code>. + <p>The default value of this attribute is <code>true</code>. + <p>Also note that home sounds are only played if the device supports home sounds, + usually TVs. + <p>Requires permission {@code android.permission.DISABLE_SYSTEM_SOUND_EFFECTS}. --> + <attr name="playHomeTransitionSound" format="boolean"/> </declare-styleable> <!-- The <code>activity-alias</code> tag declares a new diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 3259cafb4f7e..16feb4fc7dac 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3093,6 +3093,8 @@ <public name="suppressesSpellChecker" /> <public name="usesPermissionFlags" /> <public name="requestOptimizedExternalStorageAccess" /> + <!-- @hide @SystemApi --> + <public name="playHomeTransitionSound" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e2974bce694c..c63a02b55081 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5664,6 +5664,15 @@ <!-- Error message. This text lets the user know that their current personal apps can't open this specific content. [CHAR LIMIT=NONE] --> <string name="resolver_no_personal_apps_available_resolve">No personal apps can open this content</string> + <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] --> + <string name="miniresolver_open_in_personal">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in personal profile?</string> + <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] --> + <string name="miniresolver_open_in_work">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in work profile?</string> + <!-- Button option. Open the link in the personal browser. [CHAR LIMIT=NONE] --> + <string name="miniresolver_use_personal_browser">Use personal browser</string> + <!-- Button option. Open the link in the work browser. [CHAR LIMIT=NONE] --> + <string name="miniresolver_use_work_browser">Use work browser</string> + <!-- Icc depersonalization related strings --> <!-- Label text for PIN entry widget on SIM Network Depersonalization panel [CHAR LIMIT=none] --> <string name="PERSOSUBSTATE_SIM_NETWORK_ENTRY">SIM network unlock PIN</string> diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java index 7ef1d5e426cc..6d9e2ea5acab 100644 --- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java @@ -51,7 +51,7 @@ public class AppSearchSessionUnitTest { CompletableFuture<AppSearchResult<SetSchemaResponse>> schemaFuture = new CompletableFuture<>(); mSearchSession.setSchema( - new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor, + new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor, mExecutor, schemaFuture::complete); schemaFuture.get().getResultValue(); diff --git a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java index 41cd4c562bd8..2833ea3f9ac0 100644 --- a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java +++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java @@ -49,7 +49,6 @@ import java.util.function.Consumer; */ @Presubmit @SmallTest -@FlakyTest(detail = "promote once confirmed flake-free") @RunWith(MockitoJUnitRunner.class) public class ViewGroupScrollCaptureTest { diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java index 0f591433a84e..d36f06ab683a 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java @@ -158,4 +158,22 @@ public class BatteryUsageStatsProviderTest { assertThat(item.time).isEqualTo(elapsedTimeMs); } + + @Test + public void shouldUpdateStats() { + Context context = InstrumentationRegistry.getContext(); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, + mStatsRule.getBatteryStats()); + + final List<BatteryUsageStatsQuery> queries = List.of( + new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(1000).build(), + new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(2000).build() + ); + + mStatsRule.setTime(10500, 0); + assertThat(provider.shouldUpdateStats(queries, 10000)).isFalse(); + + mStatsRule.setTime(11500, 0); + assertThat(provider.shouldUpdateStats(queries, 10000)).isTrue(); + } } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index cafda0966fc4..c48fd8b8b6ae 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -34,9 +34,11 @@ import android.graphics.fonts.FontFamily; import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; import android.graphics.fonts.SystemFonts; +import android.icu.util.ULocale; import android.os.Build; import android.os.ParcelFileDescriptor; import android.os.SharedMemory; +import android.os.SystemProperties; import android.os.Trace; import android.provider.FontRequest; import android.provider.FontsContract; @@ -45,6 +47,7 @@ import android.system.OsConstants; import android.text.FontConfig; import android.util.ArrayMap; import android.util.Base64; +import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; import android.util.SparseArray; @@ -1375,13 +1378,35 @@ public class Typeface { static { // Preload Roboto-Regular.ttf in Zygote for improving app launch performance. - // TODO: add new attribute to fonts.xml to preload fonts in Zygote. preloadFontFile("/system/fonts/Roboto-Regular.ttf"); + + String locale = SystemProperties.get("persist.sys.locale", "en-US"); + String script = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale)).getScript(); + + FontConfig config = SystemFonts.getSystemPreinstalledFontConfig(); + for (int i = 0; i < config.getFontFamilies().size(); ++i) { + FontConfig.FontFamily family = config.getFontFamilies().get(i); + boolean loadFamily = false; + for (int j = 0; j < family.getLocaleList().size(); ++j) { + String fontScript = ULocale.addLikelySubtags( + ULocale.forLocale(family.getLocaleList().get(j))).getScript(); + loadFamily = fontScript.equals(script); + if (loadFamily) { + break; + } + } + if (loadFamily) { + for (int j = 0; j < family.getFontList().size(); ++j) { + preloadFontFile(family.getFontList().get(j).getFile().getAbsolutePath()); + } + } + } } private static void preloadFontFile(String filePath) { File file = new File(filePath); if (file.exists()) { + Log.i(TAG, "Preloading " + file.getAbsolutePath()); nativeWarmUpCache(filePath); } } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 24d73efebe54..3616a4d66399 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -276,7 +276,11 @@ public class GradientDrawable extends Drawable { */ @Nullable public float[] getCornerRadii() { - return mGradientState.mRadiusArray.clone(); + float[] radii = mGradientState.mRadiusArray; + if (radii == null) { + return null; + } + return radii.clone(); } /** diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java index 5dd250c8239e..98b9584f7b4a 100644 --- a/graphics/java/android/graphics/drawable/RippleShader.java +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -60,17 +60,13 @@ final class RippleShader extends RuntimeShader { + " float d = distance(uv, xy);\n" + " return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);\n" + "}\n" - + "\n" - + "float getRingMask(vec2 frag, vec2 center, float r, float progress) {\n" - + " float dist = distance(frag, center);\n" - + " float expansion = r * .6;\n" - + " r = r * min(1.,progress);\n" - + " float minD = max(r - expansion, 0.);\n" - + " float maxD = r + expansion;\n" - + " if (dist > maxD || dist < minD) return .0;\n" - + " return min(maxD - dist, dist - minD) / expansion; \n" + + "float softRing(vec2 uv, vec2 xy, float radius, float progress, float blur) {\n" + + " float thickness = 0.2 * radius;\n" + + " float currentRadius = radius * progress;\n" + + " float circle_outer = softCircle(uv, xy, currentRadius + thickness, blur);\n" + + " float circle_inner = softCircle(uv, xy, currentRadius - thickness, blur);\n" + + " return clamp(circle_outer - circle_inner, 0., 1.);\n" + "}\n" - + "\n" + "float subProgress(float start, float end, float progress) {\n" + " float sub = clamp(progress, start, end);\n" + " return (sub - start) / (end - start); \n" @@ -80,8 +76,8 @@ final class RippleShader extends RuntimeShader { + " float fadeOutNoise = subProgress(0.375, 1., in_progress);\n" + " float fadeOutRipple = subProgress(0.375, 0.75, in_progress);\n" + " vec2 center = mix(in_touch, in_origin, fadeIn);\n" - + " float ring = getRingMask(p, center, in_maxRadius, fadeIn);\n" - + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n" + + " float ring = softRing(p, center, in_maxRadius, fadeIn, 0.45);\n" + + " float alpha = 1. - fadeOutNoise;\n" + " vec2 uv = p * in_resolutionScale;\n" + " vec2 densityUv = uv - mod(uv, in_noiseScale);\n" + " float sparkle = sparkles(densityUv, in_noisePhase) * ring * alpha;\n" @@ -137,8 +133,7 @@ final class RippleShader extends RuntimeShader { } public void setResolution(float w, float h, int density) { - float noiseScale = 0.8f; - float densityScale = density * DisplayMetrics.DENSITY_DEFAULT_SCALE * 0.5f * noiseScale; + float densityScale = density * DisplayMetrics.DENSITY_DEFAULT_SCALE * 0.5f; setUniform("in_resolutionScale", new float[] {1f / w, 1f / h}); setUniform("in_noiseScale", new float[] {densityScale / w, densityScale / h}); } diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 35b1c169f283..72cea0cacd12 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -139,4 +139,18 @@ public class AndroidKeyStoreMaintenance { return SYSTEM_ERROR; } } + + /** + * Informs Keystore 2.0 that an off body event was detected. + */ + public static void onDeviceOffBody() { + if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return; + try { + getService().onDeviceOffBody(); + } catch (Exception e) { + // TODO This fails open. This is not a regression with respect to keystore1 but it + // should get fixed. + Log.e(TAG, "Error while reporting device off body event.", e); + } + } } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index a08f390c9fd3..b05149ef75bc 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -1204,6 +1204,7 @@ public class KeyStore { * Notify keystore that the device went off-body. */ public void onDeviceOffBody() { + AndroidKeyStoreMaintenance.onDeviceOffBody(); try { mBinder.onDeviceOffBody(); } catch (RemoteException e) { diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java index 6ac3821d0f9c..75e248e06b2b 100644 --- a/keystore/java/android/security/KeyStore2.java +++ b/keystore/java/android/security/KeyStore2.java @@ -18,8 +18,7 @@ package android.security; import android.annotation.NonNull; import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; -import android.os.Build; +import android.compat.annotation.Disabled; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; @@ -86,7 +85,7 @@ public class KeyStore2 { * successfully conclude an operation. */ @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + @Disabled // See b/180133780 static final long KEYSTORE_OPERATION_CREATION_MAY_FAIL = 169897160L; // Never use mBinder directly, use KeyStore2.getService() instead or better yet diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index ade63e5b832c..5d9fad5b676e 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -45,7 +45,8 @@ sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { return SkColorSpace::MakeSRGB(); } -ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker) +ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker, + SkCodec::ZeroInitialized zeroInit) : mCodec(std::move(codec)) , mPeeker(std::move(peeker)) , mDecodeSize(mCodec->codec()->dimensions()) @@ -57,6 +58,7 @@ ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChu mTargetSize = swapWidthHeight() ? SkISize { mDecodeSize.height(), mDecodeSize.width() } : mDecodeSize; this->rewind(); + mOptions.fZeroInitialized = zeroInit; } ImageDecoder::~ImageDecoder() = default; @@ -446,10 +448,17 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { ALOGE("Failed to invert matrix!"); } } + + // Even if the client did not provide zero initialized memory, the + // memory we decode into is. + mOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized; } auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions); + // The next call to decode() may not provide zero initialized memory. + mOptions.fZeroInitialized = SkCodec::kNo_ZeroInitialized; + if (scale || handleOrigin || mCropRect) { SkBitmap scaledBm; if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) { diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h index cbfffd5e9291..cef2233fc371 100644 --- a/libs/hwui/hwui/ImageDecoder.h +++ b/libs/hwui/hwui/ImageDecoder.h @@ -34,8 +34,8 @@ public: std::unique_ptr<SkAndroidCodec> mCodec; sk_sp<SkPngChunkReader> mPeeker; - ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, - sk_sp<SkPngChunkReader> peeker = nullptr); + ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker = nullptr, + SkCodec::ZeroInitialized zeroInit = SkCodec::kNo_ZeroInitialized); ~ImageDecoder(); SkISize getSampledDimensions(int sampleSize) const; diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index ad7741b61e9f..f7b8c014be6e 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -141,7 +141,8 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream, } const bool isNinePatch = peeker->mPatch != nullptr; - ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker)); + ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker), + SkCodec::kYes_ZeroInitialized); return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID, reinterpret_cast<jlong>(decoder), decoder->width(), decoder->height(), animated, isNinePatch); diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index 9dcc391bd31b..f3f55333d207 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -232,8 +232,8 @@ package android.net { method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); method public final void sendNetworkScore(@IntRange(from=0, to=99) int); method public final void sendQosCallbackError(int, int); - method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes); - method public final void sendQosSessionLost(int, int); + method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes); + method public final void sendQosSessionLost(int, int, int); method public final void sendSocketKeepaliveEvent(int, int); method @Deprecated public void setLegacySubtype(int, @NonNull String); method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>); @@ -385,6 +385,7 @@ package android.net { method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR; field public static final int TYPE_EPS_BEARER = 1; // 0x1 + field public static final int TYPE_NR_BEARER = 2; // 0x2 } public interface QosSessionAttributes { diff --git a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl index c5464d32412b..cbd6193744b9 100644 --- a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl +++ b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl @@ -22,6 +22,7 @@ import android.net.NetworkInfo; import android.net.NetworkScore; import android.net.QosSession; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; /** * Interface for NetworkAgents to send network properties. @@ -37,6 +38,7 @@ oneway interface INetworkAgentRegistry { void sendSocketKeepaliveEvent(int slot, int reason); void sendUnderlyingNetworks(in @nullable List<Network> networks); void sendEpsQosSessionAvailable(int callbackId, in QosSession session, in EpsBearerQosSessionAttributes attributes); + void sendNrQosSessionAvailable(int callbackId, in QosSession session, in NrQosSessionAttributes attributes); void sendQosSessionLost(int qosCallbackId, in QosSession session); void sendQosCallbackError(int qosCallbackId, int exceptionType); } diff --git a/packages/Connectivity/framework/src/android/net/IQosCallback.aidl b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl index 91c75759f85c..c9735419f7dd 100644 --- a/packages/Connectivity/framework/src/android/net/IQosCallback.aidl +++ b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl @@ -19,6 +19,7 @@ package android.net; import android.os.Bundle; import android.net.QosSession; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; /** * AIDL interface for QosCallback @@ -29,6 +30,8 @@ oneway interface IQosCallback { void onQosEpsBearerSessionAvailable(in QosSession session, in EpsBearerQosSessionAttributes attributes); + void onNrQosSessionAvailable(in QosSession session, + in NrQosSessionAttributes attributes); void onQosSessionLost(in QosSession session); void onError(in int type); } diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java index b3d9616cead7..f7cd4f6954bc 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java +++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java @@ -32,6 +32,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -1160,29 +1161,37 @@ public abstract class NetworkAgent { /** - * Sends the attributes of Eps Bearer Qos Session back to the Application + * Sends the attributes of Qos Session back to the Application * * @param qosCallbackId the callback id that the session belongs to - * @param sessionId the unique session id across all Eps Bearer Qos Sessions - * @param attributes the attributes of the Eps Qos Session + * @param sessionId the unique session id across all Qos Sessions + * @param attributes the attributes of the Qos Session */ public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId, - @NonNull final EpsBearerQosSessionAttributes attributes) { + @NonNull final QosSessionAttributes attributes) { Objects.requireNonNull(attributes, "The attributes must be non-null"); - queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId, - new QosSession(sessionId, QosSession.TYPE_EPS_BEARER), - attributes)); + if (attributes instanceof EpsBearerQosSessionAttributes) { + queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER), + (EpsBearerQosSessionAttributes)attributes)); + } else if (attributes instanceof NrQosSessionAttributes) { + queueOrSendMessage(ra -> ra.sendNrQosSessionAvailable(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_NR_BEARER), + (NrQosSessionAttributes)attributes)); + } } /** - * Sends event that the Eps Qos Session was lost. + * Sends event that the Qos Session was lost. * * @param qosCallbackId the callback id that the session belongs to - * @param sessionId the unique session id across all Eps Bearer Qos Sessions + * @param sessionId the unique session id across all Qos Sessions + * @param qosSessionType the session type {@code QosSesson#QosSessionType} */ - public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) { + public final void sendQosSessionLost(final int qosCallbackId, + final int sessionId, final int qosSessionType) { queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId, - new QosSession(sessionId, QosSession.TYPE_EPS_BEARER))); + new QosSession(sessionId, qosSessionType))); } /** diff --git a/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java index bdb4ad68cd7b..de0fc243b43b 100644 --- a/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java +++ b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.NonNull; import android.annotation.Nullable; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import com.android.internal.annotations.VisibleForTesting; @@ -84,6 +85,25 @@ class QosCallbackConnection extends android.net.IQosCallback.Stub { } /** + * Called when either the {@link NrQosSessionAttributes} has changed or on the first time + * the attributes have become available. + * + * @param session the session that is now available + * @param attributes the corresponding attributes of session + */ + @Override + public void onNrQosSessionAvailable(@NonNull final QosSession session, + @NonNull final NrQosSessionAttributes attributes) { + + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + callback.onQosSessionAvailable(session, attributes); + } + }); + } + + /** * Called when the session is lost. * * @param session the session that was lost diff --git a/packages/Connectivity/framework/src/android/net/QosSession.java b/packages/Connectivity/framework/src/android/net/QosSession.java index 4f3bb77c5877..93f2ff2bf724 100644 --- a/packages/Connectivity/framework/src/android/net/QosSession.java +++ b/packages/Connectivity/framework/src/android/net/QosSession.java @@ -36,6 +36,11 @@ public final class QosSession implements Parcelable { */ public static final int TYPE_EPS_BEARER = 1; + /** + * The {@link QosSession} is a NR Session. + */ + public static final int TYPE_NR_BEARER = 2; + private final int mSessionId; private final int mSessionType; @@ -100,6 +105,7 @@ public final class QosSession implements Parcelable { */ @IntDef(value = { TYPE_EPS_BEARER, + TYPE_NR_BEARER, }) @interface QosSessionType {} diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp index 231babea97c2..dd9fc2c7c142 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp @@ -23,5 +23,6 @@ android_library { apex_available: [ "//apex_available:platform", "com.android.cellbroadcast", + "com.android.permission", ], } diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml new file mode 100644 index 000000000000..24d53ab84653 --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<androidx.coordinatorlayout.widget.CoordinatorLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/content_parent" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:transitionGroup="true"> + + <com.google.android.material.appbar.AppBarLayout + android:id="@+id/app_bar" + android:layout_width="match_parent" + android:layout_height="180dp" + android:theme="@style/Theme.CollapsingToolbar.Settings"> + + <com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout + android:id="@+id/collapsing_toolbar" + android:background="?android:attr/colorPrimary" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:maxLines="3" + app:contentScrim="?android:attr/colorPrimary" + app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed" + app:statusBarScrim="?android:attr/colorPrimary" + app:layout_scrollFlags="scroll|exitUntilCollapsed" + app:expandedTitleMarginStart="18dp" + app:expandedTitleMarginEnd="18dp" + app:toolbarId="@id/action_bar"> + + <Toolbar + android:id="@+id/action_bar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:theme="?android:attr/actionBarTheme" + app:layout_collapseMode="pin"/> + + </com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout> + </com.google.android.material.appbar.AppBarLayout> + + <FrameLayout + android:id="@+id/content_frame" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_behavior="@string/appbar_scrolling_view_behavior"/> +</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml index e376930645ce..c799b9962828 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml @@ -14,47 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. --> -<androidx.coordinatorlayout.widget.CoordinatorLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" +<!-- The main content view --> +<LinearLayout android:id="@+id/content_parent" + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:transitionGroup="true"> - - <com.google.android.material.appbar.AppBarLayout - android:id="@+id/app_bar" + android:fitsSystemWindows="true" + android:transitionGroup="true" + android:orientation="vertical"> + <Toolbar + android:id="@+id/action_bar" + style="?android:attr/actionBarStyle" android:layout_width="match_parent" - android:layout_height="180dp" - android:theme="@style/Theme.CollapsingToolbar.Settings"> - - <com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout - android:id="@+id/collapsing_toolbar" - android:background="?android:attr/colorPrimary" - android:layout_width="match_parent" - android:layout_height="match_parent" - app:maxLines="3" - app:contentScrim="?android:attr/colorPrimary" - app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed" - app:statusBarScrim="?android:attr/colorPrimary" - app:layout_scrollFlags="scroll|exitUntilCollapsed" - app:expandedTitleMarginStart="18dp" - app:expandedTitleMarginEnd="18dp" - app:toolbarId="@id/action_bar"> - - <Toolbar - android:id="@+id/action_bar" - android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" - android:theme="?android:attr/actionBarTheme" - app:layout_collapseMode="pin"/> - - </com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout> - </com.google.android.material.appbar.AppBarLayout> - + android:layout_height="wrap_content" + android:theme="?android:attr/actionBarTheme" /> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" - android:layout_height="wrap_content" - app:layout_behavior="@string/appbar_scrolling_view_behavior"/> -</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file + android:layout_height="match_parent"/> +</LinearLayout> diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml deleted file mode 100644 index c799b9962828..000000000000 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> -<!-- The main content view --> -<LinearLayout - android:id="@+id/content_parent" - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:fitsSystemWindows="true" - android:transitionGroup="true" - android:orientation="vertical"> - <Toolbar - android:id="@+id/action_bar" - style="?android:attr/actionBarStyle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:theme="?android:attr/actionBarTheme" /> - <FrameLayout - android:id="@+id/content_frame" - android:layout_width="match_parent" - android:layout_height="match_parent"/> -</LinearLayout> diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java index ad94cd0318a7..957bac742703 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java @@ -24,7 +24,6 @@ import android.view.ViewGroup; import android.widget.Toolbar; import androidx.annotation.Nullable; -import androidx.core.os.BuildCompat; import androidx.fragment.app.FragmentActivity; import com.google.android.material.appbar.CollapsingToolbarLayout; @@ -41,15 +40,8 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // TODO(b/181723278): Update the version check after SDK for S is finalized - // The collapsing toolbar is only supported if the android platform version is S or higher. - // Otherwise the regular action bar will be shown. - if (BuildCompat.isAtLeastS()) { - super.setContentView(R.layout.collapsing_toolbar_base_layout); - mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar); - } else { - super.setContentView(R.layout.toolbar_base_layout); - } + super.setContentView(R.layout.collapsing_toolbar_base_layout); + mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar); final Toolbar toolbar = findViewById(R.id.action_bar); setActionBar(toolbar); diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java new file mode 100644 index 000000000000..c4c74ffc719b --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java @@ -0,0 +1,78 @@ +/* + * 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.settingslib.collapsingtoolbar; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.Toolbar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.google.android.material.appbar.CollapsingToolbarLayout; + +/** + * A base fragment that has a collapsing toolbar layout for enabling the collapsing toolbar design. + */ +public abstract class CollapsingToolbarBaseFragment extends Fragment { + + @Nullable + private CollapsingToolbarLayout mCollapsingToolbarLayout; + @NonNull + private Toolbar mToolbar; + @NonNull + private FrameLayout mContentFrameLayout; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.collapsing_toolbar_base_layout, container, + false); + mCollapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar); + mToolbar = view.findViewById(R.id.action_bar); + mContentFrameLayout = view.findViewById(R.id.content_frame); + return view; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + requireActivity().setActionBar(mToolbar); + } + + /** + * Return the collapsing toolbar layout. + */ + @Nullable + public CollapsingToolbarLayout getCollapsingToolbarLayout() { + return mCollapsingToolbarLayout; + } + + /** + * Return the content frame layout. + */ + @NonNull + public FrameLayout getContentFrameLayout() { + return mContentFrameLayout; + } +} diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index be49e1f8c71e..886f98e55d67 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -124,6 +124,7 @@ <attr name="darkIconTheme" format="reference" /> <attr name="wallpaperTextColor" format="reference|color" /> <attr name="wallpaperTextColorSecondary" format="reference|color" /> + <attr name="wallpaperTextColorAccent" format="reference|color" /> <attr name="backgroundProtectedStyle" format="reference" /> <declare-styleable name="SmartReplyView"> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index af6df32a02b0..fbe7175b757f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -591,4 +591,12 @@ <!-- Determines whether to allow the nav bar handle to be forced to be opaque. --> <bool name="allow_force_nav_bar_handle_opaque">true</bool> + + <!-- Whether a transition of ACTIVITY_TYPE_DREAM to the home app should play a home sound + effect --> + <bool name="config_playHomeSoundAfterDream">false</bool> + + <!-- Whether a transition of ACTIVITY_TYPE_ASSISTANT to the home app should play a home sound + effect --> + <bool name="config_playHomeSoundAfterAssistant">false</bool> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 935f0259fe86..5e70f189da83 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -239,10 +239,8 @@ <string name="screenshot_edit_label">Edit</string> <!-- Content description indicating that tapping the element will allow editing the screenshot [CHAR LIMIT=NONE] --> <string name="screenshot_edit_description">Edit screenshot</string> - <!-- Label for UI element which allows scrolling and extending the screenshot to be taller [CHAR LIMIT=30] --> - <string name="screenshot_scroll_label">Scroll</string> - <!-- Content description UI element which allows scrolling and extending the screenshot to be taller [CHAR LIMIT=NONE] --> - <string name="screenshot_scroll_description">Scroll screenshot</string> + <!-- Label for UI element which allows the user to capture additional off-screen content in a screenshot. [CHAR LIMIT=30] --> + <string name="screenshot_scroll_label">Capture more</string> <!-- Content description indicating that tapping a button will dismiss the screenshots UI [CHAR LIMIT=NONE] --> <string name="screenshot_dismiss_description">Dismiss screenshot</string> <!-- Content description indicating that the view is a preview of the screenshot that was just taken [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4a661dcecce8..ecc1a5c831b8 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -321,6 +321,7 @@ <item name="darkIconTheme">@style/DualToneDarkTheme</item> <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item> <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item> + <item name="wallpaperTextColorAccent">@*android:color/system_accent1_100</item> <item name="android:colorError">@*android:color/error_color_material_dark</item> <item name="android:colorControlHighlight">@*android:color/primary_text_material_dark</item> <item name="*android:lockPatternStyle">@style/LockPatternStyle</item> @@ -337,6 +338,7 @@ <style name="Theme.SystemUI.Light"> <item name="wallpaperTextColor">@*android:color/primary_text_material_light</item> <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_light</item> + <item name="wallpaperTextColorAccent">@*android:color/system_accent2_600</item> <item name="android:colorError">@*android:color/error_color_material_light</item> <item name="android:colorControlHighlight">#40000000</item> <item name="shadowRadius">0</item> diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index 3f0e3eb84424..ab219f36bab3 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -131,7 +131,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie private void initColors() { mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(), - com.android.systemui.R.attr.wallpaperTextColor); + com.android.systemui.R.attr.wallpaperTextColorAccent); mView.setColors(mDozingColor, mLockScreenColor); mView.animateDoze(mIsDozing, false); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java index 378907c400d7..e2748437c0c6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java @@ -62,7 +62,8 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { mBgProtection = findViewById(R.id.udfps_keyguard_fp_bg); - mWallpaperTexColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); + mWallpaperTexColor = Utils.getColorAttrDefaultColor(mContext, + R.attr.wallpaperTextColorAccent); mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); } diff --git a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java index dd3d02a86672..31e49390b9b8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java +++ b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java @@ -16,38 +16,64 @@ package com.android.systemui.media.systemsounds; +import android.Manifest; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.media.AudioManager; +import android.util.Slog; +import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; import javax.inject.Inject; /** - * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a sound is played - * when the home task moves to front and the last task that moved to front was not the home task. + * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a + * {@link TaskStackChangeListener} is registered to play a home sound effect when conditions + * documented at {@link #handleTaskStackChanged} apply. */ @SysUISingleton public class HomeSoundEffectController extends SystemUI { + private static final String TAG = "HomeSoundEffectController"; private final AudioManager mAudioManager; private final TaskStackChangeListeners mTaskStackChangeListeners; + private final ActivityManagerWrapper mActivityManagerWrapper; + private final PackageManager mPm; + private final boolean mPlayHomeSoundAfterAssistant; + private final boolean mPlayHomeSoundAfterDream; // Initialize true because home sound should not be played when the system boots. private boolean mIsLastTaskHome = true; + // mLastHomePackageName could go out of sync in rare circumstances if launcher changes, + // but it's cheaper than the alternative and potential impact is low + private String mLastHomePackageName; + private @WindowConfiguration.ActivityType int mLastActivityType; + private boolean mLastActivityHasNoHomeSound = false; + private int mLastTaskId; @Inject public HomeSoundEffectController( Context context, AudioManager audioManager, - TaskStackChangeListeners taskStackChangeListeners) { + TaskStackChangeListeners taskStackChangeListeners, + ActivityManagerWrapper activityManagerWrapper, + PackageManager packageManager) { super(context); mAudioManager = audioManager; mTaskStackChangeListeners = taskStackChangeListeners; + mActivityManagerWrapper = activityManagerWrapper; + mPm = packageManager; + mPlayHomeSoundAfterAssistant = context.getResources().getBoolean( + R.bool.config_playHomeSoundAfterAssistant); + mPlayHomeSoundAfterDream = context.getResources().getBoolean( + R.bool.config_playHomeSoundAfterDream); } @Override @@ -56,27 +82,94 @@ public class HomeSoundEffectController extends SystemUI { mTaskStackChangeListeners.registerTaskStackListener( new TaskStackChangeListener() { @Override - public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { - handleHomeTaskMovedToFront(taskInfo); + public void onTaskStackChanged() { + ActivityManager.RunningTaskInfo currentTask = + mActivityManagerWrapper.getRunningTask(); + if (currentTask == null || currentTask.topActivityInfo == null) { + return; + } + handleTaskStackChanged(currentTask); } }); } } - private boolean isHomeTask(ActivityManager.RunningTaskInfo taskInfo) { - return taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME; + private boolean hasFlagNoSound(ActivityInfo activityInfo) { + if ((activityInfo.privateFlags & ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND) == 0) { + // Only allow flag if app has permission + if (mPm.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS, + activityInfo.packageName) == PackageManager.PERMISSION_GRANTED) { + return true; + } else { + Slog.w(TAG, + "Activity has flag playHomeTransition set to false but doesn't hold " + + "required permission " + + Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS); + return false; + } + } + return false; } /** - * To enable a home sound, check if the home app moves to front. + * The home sound is played if all of the following conditions are met: + * <ul> + * <li>The last task which moved to front was not home. This avoids playing the sound + * e.g. after FallbackHome transitions to home, another activity of the home app like a + * notification panel moved to front, or in case the home app crashed.</li> + * <li>The current activity which moved to front is home</li> + * <li>The topActivity of the last task has {@link android.R.attr#playHomeTransitionSound} set + * to <code>true</code>.</li> + * <li>The topActivity of the last task is not of type + * {@link WindowConfiguration#ACTIVITY_TYPE_ASSISTANT} if config_playHomeSoundAfterAssistant is + * set to <code>false</code> (default).</li> + * <li>The topActivity of the last task is not of type + * {@link WindowConfiguration#ACTIVITY_TYPE_DREAM} if config_playHomeSoundAfterDream is + * set to <code>false</code> (default).</li> + * </ul> */ - private void handleHomeTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { - boolean isCurrentTaskHome = isHomeTask(taskInfo); - // If the last task is home we don't want to play the home sound. This avoids playing - // the home sound after FallbackHome transitions to Home - if (!mIsLastTaskHome && isCurrentTaskHome) { + private boolean shouldPlayHomeSoundForCurrentTransition( + ActivityManager.RunningTaskInfo currentTask) { + boolean isHomeActivity = + currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME; + if (currentTask.taskId == mLastTaskId) { + return false; + } + if (mIsLastTaskHome || !isHomeActivity) { + return false; + } + if (mLastActivityHasNoHomeSound) { + return false; + } + if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_ASSISTANT + && !mPlayHomeSoundAfterAssistant) { + return false; + } + if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM + && !mPlayHomeSoundAfterDream) { + return false; + } + return true; + } + + private void updateLastTaskInfo(ActivityManager.RunningTaskInfo currentTask) { + mLastTaskId = currentTask.taskId; + mLastActivityType = currentTask.topActivityType; + mLastActivityHasNoHomeSound = hasFlagNoSound(currentTask.topActivityInfo); + boolean isHomeActivity = + currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME; + boolean isHomePackage = currentTask.topActivityInfo.packageName.equals( + mLastHomePackageName); + mIsLastTaskHome = isHomeActivity || isHomePackage; + if (isHomeActivity && !isHomePackage) { + mLastHomePackageName = currentTask.topActivityInfo.packageName; + } + } + + private void handleTaskStackChanged(ActivityManager.RunningTaskInfo frontTask) { + if (shouldPlayHomeSoundForCurrentTransition(frontTask)) { mAudioManager.playSoundEffect(AudioManager.FX_HOME); } - mIsLastTaskHome = isCurrentTaskHome; + updateLastTaskInfo(frontTask); } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index ddfa63a33149..709ccd4c2384 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -16,26 +16,18 @@ package com.android.systemui.wmshell; -import static android.os.Process.THREAD_PRIORITY_DISPLAY; -import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST; - -import android.animation.AnimationHandler; import android.app.ActivityTaskManager; import android.content.Context; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.os.Handler; -import android.os.HandlerThread; import android.view.IWindowManager; import android.view.WindowManager; -import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.R; import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.wm.shell.FullscreenTaskListener; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellCommandHandler; @@ -53,13 +45,11 @@ import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.FloatingContentCoordinator; -import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ChoreographerSfVsync; import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; @@ -99,110 +89,9 @@ import dagger.Provides; * dependencies that are device/form factor SystemUI implementation specific should go into their * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.) */ -@Module +@Module(includes = WMShellConcurrencyModule.class) public abstract class WMShellBaseModule { - /** - * Returns whether to enable a separate shell thread for the shell features. - */ - private static boolean enableShellMainThread(Context context) { - return context.getResources().getBoolean(R.bool.config_enableShellMainThread); - } - - // - // Shell Concurrency - Components used for managing threading in the Shell and SysUI - // - - /** - * Provide a SysUI main-thread Executor. - */ - @WMSingleton - @Provides - @Main - public static ShellExecutor provideSysUIMainExecutor(@Main Handler sysuiMainHandler) { - return new HandlerExecutor(sysuiMainHandler); - } - - /** - * Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe - * multiple types of messages, etc.) - */ - @WMSingleton - @Provides - @ShellMainThread - public static Handler provideShellMainHandler(Context context, @Main Handler sysuiMainHandler) { - if (enableShellMainThread(context)) { - HandlerThread mainThread = new HandlerThread("wmshell.main"); - mainThread.start(); - return mainThread.getThreadHandler(); - } - return sysuiMainHandler; - } - - /** - * Provide a Shell main-thread Executor. - */ - @WMSingleton - @Provides - @ShellMainThread - public static ShellExecutor provideShellMainExecutor(Context context, - @ShellMainThread Handler mainHandler, @Main ShellExecutor sysuiMainExecutor) { - if (enableShellMainThread(context)) { - return new HandlerExecutor(mainHandler); - } - return sysuiMainExecutor; - } - - /** - * Provide a Shell animation-thread Executor. - */ - @WMSingleton - @Provides - @ShellAnimationThread - public static ShellExecutor provideShellAnimationExecutor() { - HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim", - THREAD_PRIORITY_DISPLAY); - shellAnimationThread.start(); - return new HandlerExecutor(shellAnimationThread.getThreadHandler()); - } - - /** - * Provides a Shell splashscreen-thread Executor - */ - @WMSingleton - @Provides - @ShellSplashscreenThread - public static ShellExecutor provideSplashScreenExecutor() { - HandlerThread shellSplashscreenThread = new HandlerThread("wmshell.splashscreen", - THREAD_PRIORITY_TOP_APP_BOOST); - shellSplashscreenThread.start(); - return new HandlerExecutor(shellSplashscreenThread.getThreadHandler()); - } - - /** - * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on - * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on - * the Shell main-thread with the SF vsync. - */ - @WMSingleton - @Provides - @ChoreographerSfVsync - public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler( - @ShellMainThread ShellExecutor mainExecutor) { - try { - AnimationHandler handler = new AnimationHandler(); - mainExecutor.executeBlocking(() -> { - // This is called on the animation thread since it calls - // Choreographer.getSfInstance() which returns a thread-local Choreographer instance - // that uses the SF vsync - handler.setProvider(new SfVsyncFrameCallbackProvider()); - }); - return handler; - } catch (InterruptedException e) { - throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e); - } - } - // // Internal common - Components used internally by multiple shell features // diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java new file mode 100644 index 000000000000..61f50b5aae30 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java @@ -0,0 +1,163 @@ +/* + * 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 com.android.systemui.wmshell; + +import static android.os.Process.THREAD_PRIORITY_DISPLAY; +import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST; + +import android.animation.AnimationHandler; +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Trace; + +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.systemui.R; +import com.android.systemui.dagger.WMSingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.wm.shell.common.HandlerExecutor; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.annotations.ChoreographerSfVsync; +import com.android.wm.shell.common.annotations.ShellAnimationThread; +import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.common.annotations.ShellSplashscreenThread; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides basic concurrency-related dependencies from {@link com.android.wm.shell}, these + * dependencies are only accessible from components within the WM subcomponent. + */ +@Module +public abstract class WMShellConcurrencyModule { + + private static final int MSGQ_SLOW_DELIVERY_THRESHOLD_MS = 30; + private static final int MSGQ_SLOW_DISPATCH_THRESHOLD_MS = 30; + + /** + * Returns whether to enable a separate shell thread for the shell features. + */ + private static boolean enableShellMainThread(Context context) { + return context.getResources().getBoolean(R.bool.config_enableShellMainThread); + } + + // + // Shell Concurrency - Components used for managing threading in the Shell and SysUI + // + + /** + * Provide a SysUI main-thread Executor. + */ + @WMSingleton + @Provides + @Main + public static ShellExecutor provideSysUIMainExecutor(@Main Handler sysuiMainHandler) { + return new HandlerExecutor(sysuiMainHandler); + } + + /** + * Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe + * multiple types of messages, etc.) + */ + @WMSingleton + @Provides + @ShellMainThread + public static Handler provideShellMainHandler(Context context, @Main Handler sysuiMainHandler) { + if (enableShellMainThread(context)) { + HandlerThread mainThread = new HandlerThread("wmshell.main", THREAD_PRIORITY_DISPLAY); + mainThread.start(); + if (Build.IS_DEBUGGABLE) { + mainThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); + mainThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS, + MSGQ_SLOW_DELIVERY_THRESHOLD_MS); + } + return Handler.createAsync(mainThread.getLooper()); + } + return sysuiMainHandler; + } + + /** + * Provide a Shell main-thread Executor. + */ + @WMSingleton + @Provides + @ShellMainThread + public static ShellExecutor provideShellMainExecutor(Context context, + @ShellMainThread Handler mainHandler, @Main ShellExecutor sysuiMainExecutor) { + if (enableShellMainThread(context)) { + return new HandlerExecutor(mainHandler); + } + return sysuiMainExecutor; + } + + /** + * Provide a Shell animation-thread Executor. + */ + @WMSingleton + @Provides + @ShellAnimationThread + public static ShellExecutor provideShellAnimationExecutor() { + HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim", + THREAD_PRIORITY_DISPLAY); + shellAnimationThread.start(); + if (Build.IS_DEBUGGABLE) { + shellAnimationThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); + shellAnimationThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS, + MSGQ_SLOW_DELIVERY_THRESHOLD_MS); + } + return new HandlerExecutor(Handler.createAsync(shellAnimationThread.getLooper())); + } + + /** + * Provides a Shell splashscreen-thread Executor + */ + @WMSingleton + @Provides + @ShellSplashscreenThread + public static ShellExecutor provideSplashScreenExecutor() { + HandlerThread shellSplashscreenThread = new HandlerThread("wmshell.splashscreen", + THREAD_PRIORITY_TOP_APP_BOOST); + shellSplashscreenThread.start(); + return new HandlerExecutor(shellSplashscreenThread.getThreadHandler()); + } + + /** + * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on + * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on + * the Shell main-thread with the SF vsync. + */ + @WMSingleton + @Provides + @ChoreographerSfVsync + public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler( + @ShellMainThread ShellExecutor mainExecutor) { + try { + AnimationHandler handler = new AnimationHandler(); + mainExecutor.executeBlocking(() -> { + // This is called on the animation thread since it calls + // Choreographer.getSfInstance() which returns a thread-local Choreographer instance + // that uses the SF vsync + handler.setProvider(new SfVsyncFrameCallbackProvider()); + }); + return handler; + } catch (InterruptedException e) { + throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java index 3a77f7eec7f9..33a30e0bcc27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java @@ -21,16 +21,20 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.Manifest; import android.app.ActivityManager; import android.app.WindowConfiguration; -import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.media.AudioManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -41,30 +45,50 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; + @SmallTest @RunWith(AndroidJUnit4.class) public class HomeSoundEffectControllerTest extends SysuiTestCase { - private @Mock Context mContext; + private static final String HOME_PACKAGE_NAME = "com.android.apps.home"; + private static final String NON_HOME_PACKAGE_NAME = "com.android.apps.not.home"; + private static final int HOME_TASK_ID = 0; + private static final int NON_HOME_TASK_ID = 1; + private @Mock AudioManager mAudioManager; private @Mock TaskStackChangeListeners mTaskStackChangeListeners; - private @Mock ActivityManager.RunningTaskInfo mStandardActivityTaskInfo; - private @Mock ActivityManager.RunningTaskInfo mHomeActivityTaskInfo; - + private @Mock ActivityManagerWrapper mActivityManagerWrapper; + private @Mock PackageManager mPackageManager; + + private ActivityManager.RunningTaskInfo mTaskAStandardActivity; + private ActivityManager.RunningTaskInfo mTaskAExceptionActivity; + private ActivityManager.RunningTaskInfo mHomeTaskHomeActivity; + private ActivityManager.RunningTaskInfo mHomeTaskStandardActivity; + private ActivityManager.RunningTaskInfo mEmptyTask; private HomeSoundEffectController mController; private TaskStackChangeListener mTaskStackChangeListener; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - - doReturn(WindowConfiguration.ACTIVITY_TYPE_STANDARD).when( - mStandardActivityTaskInfo).getActivityType(); - doReturn(WindowConfiguration.ACTIVITY_TYPE_HOME).when( - mHomeActivityTaskInfo).getActivityType(); - + mTaskAStandardActivity = createRunningTaskInfo(NON_HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_STANDARD, NON_HOME_TASK_ID, + true /* playHomeTransitionSound */); + mTaskAExceptionActivity = createRunningTaskInfo(NON_HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_STANDARD, NON_HOME_TASK_ID, + false /* playHomeTransitionSound */); + mHomeTaskHomeActivity = createRunningTaskInfo(HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_HOME, HOME_TASK_ID, + true /* playHomeTransitionSound */); + mHomeTaskStandardActivity = createRunningTaskInfo(HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_STANDARD, HOME_TASK_ID, + true /* playHomeTransitionSound */); + mEmptyTask = new ActivityManager.RunningTaskInfo(); + mContext.setMockPackageManager(mPackageManager); + when(mPackageManager.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS, + NON_HOME_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_GRANTED); mController = new HomeSoundEffectController(mContext, mAudioManager, - mTaskStackChangeListeners); + mTaskStackChangeListeners, mActivityManagerWrapper, mPackageManager); } @Test @@ -73,43 +97,60 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { startController(true /* isHomeSoundEffectEnabled */); // And the home task moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + when(mActivityManagerWrapper.getRunningTask()).thenReturn(mHomeTaskHomeActivity); + mTaskStackChangeListener.onTaskStackChanged(); // Then no home sound effect should be played. verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); } + /** + * Task A (playHomeTransitionSound = true) -> HOME + * Expectation: Home sound is played + */ @Test public void testHomeSoundEffectPlayedWhenEnabled() { // When HomeSoundEffectController is started and the home sound effect is enabled, startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity); // And first a task different from the home task moves to front, - mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // And the home task moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // Then the home sound effect should be played. verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); } + /** + * Task A (playHomeTransitionSound = true) -> HOME -> HOME + * Expectation: Home sound is played once after HOME moves to front the first time + */ @Test public void testHomeSoundEffectNotPlayedTwiceInRow() { // When HomeSoundEffectController is started and the home sound effect is enabled, startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity, + mHomeTaskHomeActivity); + // And first a task different from the home task moves to front, - mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // And the home task moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // Then the home sound effect should be played. verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); // If the home task moves to front a second time in a row, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // Then no home sound effect should be played. verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME); @@ -121,7 +162,59 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { startController(true /* isHomeSoundEffectEnabled */); // And a standard, non-home task, moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo); + when(mActivityManagerWrapper.getRunningTask()).thenReturn(mTaskAStandardActivity); + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * Task A (playHomeTransitionSound = true) -> HOME -> HOME (activity type standard) + * Expectation: Home sound is played once after HOME moves to front + */ + @Test + public void testHomeSoundEffectNotPlayedWhenOtherHomeActivityMovesToFront() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity, + mHomeTaskStandardActivity); + + // And first a task different from the home task moves to front, + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + + // If the home task moves to front a second time in a row, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * Task A (playHomeTransitionSound = true) -> HOME (activity type standard) + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenOtherHomeActivityMovesToFrontOfOtherApp() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + + // And first a task different from the home task moves to front, + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskStandardActivity); + mTaskStackChangeListener.onTaskStackChanged(); + + // And an activity from the home package but not the home root activity moves to front + mTaskStackChangeListener.onTaskStackChanged(); // Then no home sound effect should be played. verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); @@ -138,6 +231,171 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { } /** + * Task A (playHomeTransitionSound = false) -> HOME + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenHomeActivityMovesToFrontAfterException() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + + // And first a task different from the home task moves to front, which has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code> + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAExceptionActivity, + mHomeTaskHomeActivity); + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played because the last package is an exception. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = true) -> Task A (playHomeTransitionSound = false) + * -> HOME + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenHomeActivityMovesToFrontAfterException2() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mTaskAExceptionActivity, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a different activity from the same task moves to front, which has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code> + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = false) -> Task A (playHomeTransitionSound = true) + * -> HOME + * Expectation: Home sound is played + */ + @Test + public void testHomeSoundEffectPlayedWhenHomeActivityMovesToFrontAfterException() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAExceptionActivity, + mTaskAStandardActivity, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front, + // the topActivity of this task has {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} + // set to <code>false</code> + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a different activity from the same task moves to front, which has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>true</code> + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = false) -> Task A (empty task, no top activity) + * -> HOME + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenEmptyTaskMovesToFrontAfterException() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAExceptionActivity, + mEmptyTask, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front, whose topActivity has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code> + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a task with no topActivity moves to front, + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = true) -> Task A (empty task, no top activity) + * -> HOME + * Expectation: Home sound is played + */ + @Test + public void testHomeSoundEffectPlayedWhenEmptyTaskMovesToFrontAfterStandardActivity() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mEmptyTask, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front, whose topActivity has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code> + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a task with no topActivity moves to front, + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = false, no permission) -> HOME + * Expectation: Home sound is played + */ + @Test + public void testHomeSoundEffectPlayedWhenFlagSetButPermissionNotGranted() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity); + when(mPackageManager.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS, + NON_HOME_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_DENIED); + + // And first a task different from the home task moves to front, whose topActivity has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>true</code>, + // but the app doesn't have the right permission granted + mTaskStackChangeListener.onTaskStackChanged(); + + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + } + + /** * Sets {@link AudioManager#isHomeSoundEffectEnabled()} and starts HomeSoundEffectController. * If the home sound effect is enabled, the registered TaskStackChangeListener is extracted. */ @@ -155,4 +413,20 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { mTaskStackChangeListener = listenerCaptor.getValue(); } } + + private ActivityManager.RunningTaskInfo createRunningTaskInfo(String packageName, + int activityType, int taskId, boolean playHomeTransitionSound) { + ActivityManager.RunningTaskInfo res = new ActivityManager.RunningTaskInfo(); + res.topActivityInfo = new ActivityInfo(); + res.topActivityInfo.packageName = packageName; + res.topActivityType = activityType; + res.taskId = taskId; + if (!playHomeTransitionSound) { + // set the flag to 0 + res.topActivityInfo.privateFlags &= ~ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND; + } else { + res.topActivityInfo.privateFlags |= ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND; + } + return res; + } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d2fd8ff3890e..98931a528d31 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1790,6 +1790,44 @@ public final class ActiveServices { } if (!ignoreForeground) { + if (r.mStartForegroundCount == 0) { + /* + If the service was started with startService(), not + startForegroundService(), and if startForeground() isn't called within + mFgsStartForegroundTimeoutMs, then we check the state of the app + (who owns the service, which is the app that called startForeground()) + again. If the app is in the foreground, or in any other cases where + FGS-starts are allowed, then we still allow the FGS to be started. + Otherwise, startForeground() would fail. + + If the service was started with startForegroundService(), then the service + must call startForeground() within a timeout anyway, so we don't need this + check. + */ + if (!r.fgRequired) { + final long delayMs = SystemClock.elapsedRealtime() - r.createRealTime; + if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) { + setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), + r.appInfo.uid, r.intent.getIntent(), r, false); + final String temp = "startForegroundDelayMs:" + delayMs; + if (r.mInfoAllowStartForeground != null) { + r.mInfoAllowStartForeground += "; " + temp; + } else { + r.mInfoAllowStartForeground = temp; + } + r.mLoggedInfoAllowStartForeground = false; + } + } + } else if (r.mStartForegroundCount >= 1) { + // The second or later time startForeground() is called after service is + // started. Check for app state again. + final long delayMs = SystemClock.elapsedRealtime() - + r.mLastSetFgsRestrictionTime; + if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) { + setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), + r.appInfo.uid, r.intent.getIntent(), r, false); + } + } logFgsBackgroundStart(r); if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) { final String msg = "Service.startForeground() not allowed due to " @@ -1843,6 +1881,7 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; + r.mStartForegroundCount++; if (!stopProcStatsOp) { ServiceState stracker = r.getTracker(); if (stracker != null) { @@ -1901,6 +1940,7 @@ public final class ActiveServices { decActiveForegroundAppLocked(smap, r); } r.isForeground = false; + resetFgsRestrictionLocked(r); ServiceState stracker = r.getTracker(); if (stracker != null) { stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), @@ -3892,6 +3932,7 @@ public final class ActiveServices { r.foregroundId = 0; r.foregroundNoti = null; r.mAllowWhileInUsePermissionInFgs = false; + r.mAllowStartForeground = REASON_DENIED; // Clear start entries. r.clearDeliveredStartsLocked(); @@ -5430,6 +5471,7 @@ public final class ActiveServices { private void setFgsRestrictionLocked(String callingPackage, int callingPid, int callingUid, Intent intent, ServiceRecord r, boolean allowBackgroundActivityStarts) { + r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime(); // Check DeviceConfig flag. if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) { r.mAllowWhileInUsePermissionInFgs = true; @@ -5450,6 +5492,15 @@ public final class ActiveServices { } } + void resetFgsRestrictionLocked(ServiceRecord r) { + r.mAllowWhileInUsePermissionInFgs = false; + r.mAllowStartForeground = REASON_DENIED; + r.mInfoAllowStartForeground = null; + r.mInfoTempFgsAllowListReason = null; + r.mLoggedInfoAllowStartForeground = false; + r.mLastSetFgsRestrictionTime = 0; + } + boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) { if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) { return true; @@ -5601,6 +5652,7 @@ public final class ActiveServices { + ",callingUid:" + tempAllowListReason.mCallingUid)) + ">" + "; targetSdkVersion:" + r.appInfo.targetSdkVersion + + "; startForegroundCount:" + r.mStartForegroundCount + "]"; if (!debugInfo.equals(r.mInfoAllowStartForeground)) { r.mLoggedInfoAllowStartForeground = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 5859cea2d284..f7abf6a3bfc2 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -96,6 +96,7 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_PROCESS_CRASH_COUNT_LIMIT = "process_crash_count_limit"; static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration"; static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration"; + static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; @@ -135,7 +136,7 @@ final class ActivityManagerConstants extends ContentObserver { private static final int DEFAULT_PROCESS_CRASH_COUNT_LIMIT = 12; private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 10 * 1000; private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000; - + private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000; // Flag stored in the DeviceConfig API. /** @@ -396,6 +397,12 @@ final class ActivityManagerConstants extends ContentObserver { */ volatile long mFgToBgFgsGraceDuration = DEFAULT_FG_TO_BG_FGS_GRACE_DURATION; + /** + * When service started from background, before the timeout it can be promoted to FGS by calling + * Service.startForeground(). + */ + volatile long mFgsStartForegroundTimeoutMs = DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS; + private final ActivityManagerService mService; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -586,6 +593,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_FG_TO_BG_FGS_GRACE_DURATION: updateFgToBgFgsGraceDuration(); break; + case KEY_FGS_START_FOREGROUND_TIMEOUT: + updateFgsStartForegroundTimeout(); + break; default: break; } @@ -869,6 +879,13 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_FG_TO_BG_FGS_GRACE_DURATION); } + private void updateFgsStartForegroundTimeout() { + mFgsStartForegroundTimeoutMs = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_FGS_START_FOREGROUND_TIMEOUT, + DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS); + } + private void updateImperceptibleKillExemptions() { IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear(); IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages); @@ -1071,6 +1088,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.println(mBootTimeTempAllowlistDuration); pw.print(" "); pw.print(KEY_FG_TO_BG_FGS_GRACE_DURATION); pw.print("="); pw.println(mFgToBgFgsGraceDuration); + pw.print(" "); pw.print(KEY_FGS_START_FOREGROUND_TIMEOUT); pw.print("="); + pw.println(mFgsStartForegroundTimeoutMs); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index e74c936af02d..5b2276ce250f 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -487,8 +487,10 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { Slog.wtf(TAG, "Error updating external stats: ", e); } - synchronized (BatteryExternalStatsWorker.this) { - mLastCollectionTimeStamp = SystemClock.elapsedRealtime(); + if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) { + synchronized (BatteryExternalStatsWorker.this) { + mLastCollectionTimeStamp = SystemClock.elapsedRealtime(); + } } } }; diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index c3f97adbd9c3..3b76021afc08 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -674,6 +674,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { mContext.enforceCallingPermission( android.Manifest.permission.BATTERY_STATS, null); + awaitCompletion(); + + if (mBatteryUsageStatsProvider.shouldUpdateStats(queries, + mWorker.getLastCollectionTimeStamp())) { + syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL); + } + return mBatteryUsageStatsProvider.getBatteryUsageStats(queries); } diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 9cd9902f4995..54c35123fbb9 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -165,9 +165,16 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // allow the service becomes foreground service? Service started from background may not be // allowed to become a foreground service. @PowerWhitelistManager.ReasonCode int mAllowStartForeground = REASON_DENIED; + // Debug info why mAllowStartForeground is allowed or denied. String mInfoAllowStartForeground; + // Debug info if mAllowStartForeground is allowed because of a temp-allowlist. FgsStartTempAllowList.TempFgsAllowListEntry mInfoTempFgsAllowListReason; + // Is the same mInfoAllowStartForeground string has been logged before? Used for dedup. boolean mLoggedInfoAllowStartForeground; + // The number of times Service.startForeground() is called; + int mStartForegroundCount; + // Last time mAllowWhileInUsePermissionInFgs or mAllowStartForeground is set. + long mLastSetFgsRestrictionTime; String stringName; // caching of toString @@ -439,6 +446,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.println(mRecentCallingUid); pw.print(prefix); pw.print("allowStartForeground="); pw.println(mAllowStartForeground); + pw.print(prefix); pw.print("startForegroundCount="); + pw.println(mStartForegroundCount); pw.print(prefix); pw.print("infoAllowStartForeground="); pw.println(mInfoAllowStartForeground); if (delayed) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 143a1cf0e094..31183cac50e1 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1641,6 +1641,13 @@ class UserController implements Handler.Callback { * PIN or pattern. */ private boolean maybeUnlockUser(final @UserIdInt int userId) { + if (mInjector.isFileEncryptedNativeOnly() && mLockPatternUtils.isSecure(userId)) { + // A token is needed, so don't bother trying to unlock without one. + // This keeps misleading error messages from being logged. + Slog.d(TAG, "Not unlocking user " + userId + + "'s CE storage yet because a credential token is needed"); + return false; + } // Try unlocking storage using empty token return unlockUserCleared(userId, null, null, null); } @@ -3101,5 +3108,11 @@ class UserController implements Handler.Callback { protected IStorageManager getStorageManager() { return IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); } + + // This is needed because isFileEncryptedNativeOnly is a static method, + // but it needs to be mocked out in tests. + protected boolean isFileEncryptedNativeOnly() { + return StorageManager.isFileEncryptedNativeOnly(); + } } } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 103ab957f312..a8cbcb5aa4f0 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -49,6 +49,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -633,7 +634,13 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { @Override public void sendEpsQosSessionAvailable(final int qosCallbackId, final QosSession session, final EpsBearerQosSessionAttributes attributes) { - mQosCallbackTracker.sendEventQosSessionAvailable(qosCallbackId, session, attributes); + mQosCallbackTracker.sendEventEpsQosSessionAvailable(qosCallbackId, session, attributes); + } + + @Override + public void sendNrQosSessionAvailable(final int qosCallbackId, final QosSession session, + final NrQosSessionAttributes attributes) { + mQosCallbackTracker.sendEventNrQosSessionAvailable(qosCallbackId, session, attributes); } @Override diff --git a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java index 0f5400d0f8e6..534dbe7699a7 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java @@ -27,6 +27,7 @@ import android.net.QosSession; import android.os.IBinder; import android.os.RemoteException; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.util.Log; import java.util.Objects; @@ -146,13 +147,23 @@ class QosCallbackAgentConnection implements IBinder.DeathRecipient { mNetworkAgentInfo.onQosCallbackUnregistered(mAgentCallbackId); } - void sendEventQosSessionAvailable(final QosSession session, + void sendEventEpsQosSessionAvailable(final QosSession session, final EpsBearerQosSessionAttributes attributes) { try { - if (DBG) log("sendEventQosSessionAvailable: sending..."); + if (DBG) log("sendEventEpsQosSessionAvailable: sending..."); mCallback.onQosEpsBearerSessionAvailable(session, attributes); } catch (final RemoteException e) { - loge("sendEventQosSessionAvailable: remote exception", e); + loge("sendEventEpsQosSessionAvailable: remote exception", e); + } + } + + void sendEventNrQosSessionAvailable(final QosSession session, + final NrQosSessionAttributes attributes) { + try { + if (DBG) log("sendEventNrQosSessionAvailable: sending..."); + mCallback.onNrQosSessionAvailable(session, attributes); + } catch (final RemoteException e) { + loge("sendEventNrQosSessionAvailable: remote exception", e); } } diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java index 8bda5323e4f8..b6ab47b276e3 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java @@ -27,6 +27,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.util.Log; import com.android.net.module.util.CollectionUtils; @@ -179,17 +180,31 @@ public class QosCallbackTracker { } /** - * Called when the NetworkAgent sends the qos session available event + * Called when the NetworkAgent sends the qos session available event for EPS * * @param qosCallbackId the callback id that the qos session is now available to * @param session the qos session that is now available * @param attributes the qos attributes that are now available on the qos session */ - public void sendEventQosSessionAvailable(final int qosCallbackId, + public void sendEventEpsQosSessionAvailable(final int qosCallbackId, final QosSession session, final EpsBearerQosSessionAttributes attributes) { - runOnAgentConnection(qosCallbackId, "sendEventQosSessionAvailable: ", - ac -> ac.sendEventQosSessionAvailable(session, attributes)); + runOnAgentConnection(qosCallbackId, "sendEventEpsQosSessionAvailable: ", + ac -> ac.sendEventEpsQosSessionAvailable(session, attributes)); + } + + /** + * Called when the NetworkAgent sends the qos session available event for NR + * + * @param qosCallbackId the callback id that the qos session is now available to + * @param session the qos session that is now available + * @param attributes the qos attributes that are now available on the qos session + */ + public void sendEventNrQosSessionAvailable(final int qosCallbackId, + final QosSession session, + final NrQosSessionAttributes attributes) { + runOnAgentConnection(qosCallbackId, "sendEventNrQosSessionAvailable: ", + ac -> ac.sendEventNrQosSessionAvailable(session, attributes)); } /** diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index 4f95d27a085e..75181307dc42 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -41,11 +41,11 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.SecureRandom; -import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Supplier; /** * Manages set of updatable font files. @@ -109,6 +109,7 @@ final class UpdatableFontDir { private final FsverityUtil mFsverityUtil; private final File mConfigFile; private final File mTmpConfigFile; + private final Supplier<Long> mCurrentTimeSupplier; private long mLastModifiedMillis; private int mConfigVersion = 1; @@ -128,18 +129,20 @@ final class UpdatableFontDir { UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser, FsverityUtil fsverityUtil) { - this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE)); + this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE), + () -> System.currentTimeMillis()); } // For unit testing UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser, - FsverityUtil fsverityUtil, File configFile) { + FsverityUtil fsverityUtil, File configFile, Supplier<Long> currentTimeSupplier) { mFilesDir = filesDir; mPreinstalledFontDirs = preinstalledFontDirs; mParser = parser; mFsverityUtil = fsverityUtil; mConfigFile = configFile; mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp"); + mCurrentTimeSupplier = currentTimeSupplier; } /* package */ void loadFontFileMap() { @@ -209,7 +212,7 @@ final class UpdatableFontDir { FileUtils.deleteContents(mFilesDir); mFontFamilyMap.clear(); - mLastModifiedMillis = System.currentTimeMillis(); + mLastModifiedMillis = mCurrentTimeSupplier.get(); try (FileOutputStream fos = new FileOutputStream(mConfigFile)) { PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig()); } catch (Exception e) { @@ -245,7 +248,7 @@ final class UpdatableFontDir { } // Write config file. - mLastModifiedMillis = Instant.now().getEpochSecond(); + mLastModifiedMillis = mCurrentTimeSupplier.get(); try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) { PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig()); } catch (Exception e) { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 4299d9e81915..385928525ada 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -422,8 +422,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int MSG_LIMIT_REACHED = 5; private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6; private static final int MSG_ADVISE_PERSIST_THRESHOLD = 7; - private static final int MSG_UPDATE_INTERFACE_QUOTA = 10; - private static final int MSG_REMOVE_INTERFACE_QUOTA = 11; + private static final int MSG_UPDATE_INTERFACE_QUOTAS = 10; + private static final int MSG_REMOVE_INTERFACE_QUOTAS = 11; private static final int MSG_POLICIES_CHANGED = 13; private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15; private static final int MSG_SUBSCRIPTION_OVERRIDE = 16; @@ -2035,33 +2035,44 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final boolean hasWarning = policy.warningBytes != LIMIT_DISABLED; final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED; long limitBytes = Long.MAX_VALUE; - if (hasLimit && policy.hasCycle()) { + long warningBytes = Long.MAX_VALUE; + if ((hasLimit || hasWarning) && policy.hasCycle()) { final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager .cycleIterator(policy).next(); final long start = cycle.first.toInstant().toEpochMilli(); final long end = cycle.second.toInstant().toEpochMilli(); final long totalBytes = getTotalBytes(policy.template, start, end); - if (policy.lastLimitSnooze < start) { + // If the limit notification is not snoozed, the limit quota needs to be calculated. + if (hasLimit && policy.lastLimitSnooze < start) { // remaining "quota" bytes are based on total usage in // current cycle. kernel doesn't like 0-byte rules, so we // set 1-byte quota and disable the radio later. limitBytes = Math.max(1, policy.limitBytes - totalBytes); } + + // If the warning notification was snoozed by user, or the service already knows + // it is over warning bytes, doesn't need to calculate warning bytes. + if (hasWarning && policy.lastWarningSnooze < start + && !policy.isOverWarning(totalBytes)) { + warningBytes = Math.max(1, policy.warningBytes - totalBytes); + } } - if (hasLimit || policy.metered) { + if (hasWarning || hasLimit || policy.metered) { if (matchingIfaces.size() > 1) { // TODO: switch to shared quota once NMS supports Slog.w(TAG, "shared quota unsupported; generating rule for each iface"); } - // Set the interface limit. For interfaces which has no cycle, or metered with - // no policy limit, or snoozed limit notification; we still need to put iptables - // rule hooks to restrict apps for data saver, so push really high quota. + // Set the interface warning and limit. For interfaces which has no cycle, + // or metered with no policy quotas, or snoozed notification; we still need to put + // iptables rule hooks to restrict apps for data saver, so push really high quota. + // TODO: Push NetworkStatsProvider.QUOTA_UNLIMITED instead of Long.MAX_VALUE to + // providers. for (int j = matchingIfaces.size() - 1; j >= 0; j--) { final String iface = matchingIfaces.valueAt(j); - setInterfaceQuotaAsync(iface, limitBytes); + setInterfaceQuotasAsync(iface, warningBytes, limitBytes); newMeteredIfaces.add(iface); } } @@ -2084,7 +2095,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int j = matchingIfaces.size() - 1; j >= 0; j--) { final String iface = matchingIfaces.valueAt(j); if (!newMeteredIfaces.contains(iface)) { - setInterfaceQuotaAsync(iface, Long.MAX_VALUE); + setInterfaceQuotasAsync(iface, Long.MAX_VALUE, Long.MAX_VALUE); newMeteredIfaces.add(iface); } } @@ -2096,7 +2107,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int i = mMeteredIfaces.size() - 1; i >= 0; i--) { final String iface = mMeteredIfaces.valueAt(i); if (!newMeteredIfaces.contains(iface)) { - removeInterfaceQuotaAsync(iface); + removeInterfaceQuotasAsync(iface); } } mMeteredIfaces = newMeteredIfaces; @@ -5036,19 +5047,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mNetworkStats.advisePersistThreshold(persistThreshold); return true; } - case MSG_UPDATE_INTERFACE_QUOTA: { - final String iface = (String) msg.obj; - // int params need to be stitched back into a long - final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL); - removeInterfaceQuota(iface); - setInterfaceQuota(iface, quota); - mNetworkStats.setStatsProviderLimitAsync(iface, quota); + case MSG_UPDATE_INTERFACE_QUOTAS: { + final IfaceQuotas val = (IfaceQuotas) msg.obj; + // TODO: Consider set a new limit before removing the original one. + removeInterfaceLimit(val.iface); + setInterfaceLimit(val.iface, val.limit); + mNetworkStats.setStatsProviderWarningAndLimitAsync(val.iface, val.warning, + val.limit); return true; } - case MSG_REMOVE_INTERFACE_QUOTA: { + case MSG_REMOVE_INTERFACE_QUOTAS: { final String iface = (String) msg.obj; - removeInterfaceQuota(iface); - mNetworkStats.setStatsProviderLimitAsync(iface, QUOTA_UNLIMITED); + removeInterfaceLimit(iface); + mNetworkStats.setStatsProviderWarningAndLimitAsync(iface, QUOTA_UNLIMITED, + QUOTA_UNLIMITED); return true; } case MSG_RESET_FIREWALL_RULES_BY_UID: { @@ -5196,15 +5208,32 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private void setInterfaceQuotaAsync(String iface, long quotaBytes) { - // long quotaBytes split up into two ints to fit in message - mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTA, (int) (quotaBytes >> 32), - (int) (quotaBytes & 0xFFFFFFFF), iface).sendToTarget(); + private static final class IfaceQuotas { + @NonNull public final String iface; + // Warning and limit bytes of interface qutoas, could be QUOTA_UNLIMITED or Long.MAX_VALUE + // if not set. 0 is not acceptable since kernel doesn't like 0-byte rules. + public final long warning; + public final long limit; + + private IfaceQuotas(@NonNull String iface, long warning, long limit) { + this.iface = iface; + this.warning = warning; + this.limit = limit; + } + } + + private void setInterfaceQuotasAsync(@NonNull String iface, + long warningBytes, long limitBytes) { + mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTAS, + new IfaceQuotas(iface, warningBytes, limitBytes)).sendToTarget(); } - private void setInterfaceQuota(String iface, long quotaBytes) { + private void setInterfaceLimit(String iface, long limitBytes) { try { - mNetworkManager.setInterfaceQuota(iface, quotaBytes); + // For legacy design the data warning is covered by global alert, where the + // kernel will notify upper layer for a small amount of change of traffic + // statistics. Thus, passing warning is not needed. + mNetworkManager.setInterfaceQuota(iface, limitBytes); } catch (IllegalStateException e) { Log.wtf(TAG, "problem setting interface quota", e); } catch (RemoteException e) { @@ -5212,11 +5241,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private void removeInterfaceQuotaAsync(String iface) { - mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTA, iface).sendToTarget(); + private void removeInterfaceQuotasAsync(String iface) { + mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTAS, iface).sendToTarget(); } - private void removeInterfaceQuota(String iface) { + private void removeInterfaceLimit(String iface) { try { mNetworkManager.removeInterfaceQuota(iface); } catch (IllegalStateException e) { diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java index 0cb0bc2c0896..0e9a9da6804b 100644 --- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java @@ -37,8 +37,9 @@ public abstract class NetworkStatsManagerInternal { public abstract void forceUpdate(); /** - * Set the quota limit to all registered custom network stats providers. + * Set the warning and limit to all registered custom network stats providers. * Note that invocation of any interface will be sent to all providers. */ - public abstract void setStatsProviderLimitAsync(@NonNull String iface, long quota); + public abstract void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning, + long limit); } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 785e487ed10d..de5aae07d6be 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -1674,11 +1674,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - public void setStatsProviderLimitAsync(@NonNull String iface, long quota) { - if (LOGV) Slog.v(TAG, "setStatsProviderLimitAsync(" + iface + "," + quota + ")"); - // TODO: Set warning accordingly. + public void setStatsProviderWarningAndLimitAsync( + @NonNull String iface, long warning, long limit) { + if (LOGV) { + Slog.v(TAG, "setStatsProviderWarningAndLimitAsync(" + + iface + "," + warning + "," + limit + ")"); + } invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface, - NetworkStatsProvider.QUOTA_UNLIMITED, quota)); + warning, limit)); } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 3bb5c1694734..3244c4400434 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -35,6 +35,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGR import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; +import static android.content.pm.PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; @@ -1657,7 +1658,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { | FLAG_PERMISSION_USER_FIXED | FLAG_PERMISSION_REVOKED_COMPAT | FLAG_PERMISSION_REVIEW_REQUIRED - | FLAG_PERMISSION_ONE_TIME; + | FLAG_PERMISSION_ONE_TIME + | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_POLICY_FIXED; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 8075bdb4f6f2..1b2ff08bb185 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -148,6 +148,8 @@ public class DomainVerificationService extends SystemService @NonNull private DomainVerificationProxy mProxy = new DomainVerificationProxyUnavailable(); + private boolean mCanSendBroadcasts; + public DomainVerificationService(@NonNull Context context, @NonNull SystemConfig systemConfig, @NonNull PlatformCompat platformCompat) { super(context); @@ -181,11 +183,18 @@ public class DomainVerificationService extends SystemService @Override public void onBootPhase(int phase) { super.onBootPhase(phase); - if (phase != SystemService.PHASE_BOOT_COMPLETED || !hasRealVerifier()) { + if (!hasRealVerifier()) { return; } - verifyPackages(null, false); + switch (phase) { + case PHASE_ACTIVITY_MANAGER_READY: + mCanSendBroadcasts = true; + break; + case PHASE_BOOT_COMPLETED: + verifyPackages(null, false); + break; + } } @Override @@ -858,7 +867,7 @@ public class DomainVerificationService extends SystemService } if (sendBroadcast) { - sendBroadcastForPackage(pkgName); + sendBroadcast(pkgName); } } @@ -937,7 +946,7 @@ public class DomainVerificationService extends SystemService } if (sendBroadcast && hasAutoVerifyDomains) { - sendBroadcastForPackage(pkgName); + sendBroadcast(pkgName); } } @@ -1098,8 +1107,19 @@ public class DomainVerificationService extends SystemService return mCollector; } - private void sendBroadcastForPackage(@NonNull String packageName) { - mProxy.sendBroadcastForPackages(Collections.singleton(packageName)); + private void sendBroadcast(@NonNull String packageName) { + sendBroadcast(Collections.singleton(packageName)); + } + + private void sendBroadcast(@NonNull Set<String> packageNames) { + if (!mCanSendBroadcasts) { + // If the system cannot send broadcasts, it's probably still in boot, so dropping this + // request should be fine. The verification agent should re-scan packages once boot + // completes. + return; + } + + mProxy.sendBroadcastForPackages(packageNames); } private boolean hasRealVerifier() { @@ -1183,7 +1203,7 @@ public class DomainVerificationService extends SystemService } if (!packagesToBroadcast.isEmpty()) { - mProxy.sendBroadcastForPackages(packagesToBroadcast); + sendBroadcast(packagesToBroadcast); } } diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 9bb2f041556a..94f8e59c6057 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -1086,17 +1086,14 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m return err; } if (params.size > 0) { - // Only v2+ incfs supports automatically trimming file over-reserved sizes - if (mIncFs->features() & incfs::Features::v2) { - if (auto err = mIncFs->reserveSpace(ifs->control, normPath, params.size)) { - if (err != -EOPNOTSUPP) { - LOG(ERROR) << "Failed to reserve space for a new file: " << err; - (void)mIncFs->unlink(ifs->control, normPath); - return err; - } else { - LOG(WARNING) << "Reserving space for backing file isn't supported, " - "may run out of disk later"; - } + if (auto err = mIncFs->reserveSpace(ifs->control, id, params.size)) { + if (err != -EOPNOTSUPP) { + LOG(ERROR) << "Failed to reserve space for a new file: " << err; + (void)mIncFs->unlink(ifs->control, normPath); + return err; + } else { + LOG(WARNING) << "Reserving space for backing file isn't supported, " + "may run out of disk later"; } } if (!data.empty()) { @@ -1680,6 +1677,15 @@ void IncrementalService::runCmdLooper() { } } +void IncrementalService::trimReservedSpaceV1(const IncFsMount& ifs) { + mIncFs->forEachFile(ifs.control, [this](auto&& control, auto&& fileId) { + if (mIncFs->isFileFullyLoaded(control, fileId) == incfs::LoadingState::Full) { + mIncFs->reserveSpace(control, fileId, -1); + } + return true; + }); +} + void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params, DataLoaderStatusListener&& statusListener, const StorageHealthCheckParams& healthCheckParams, @@ -1699,6 +1705,22 @@ void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderPara std::move(statusListener), healthCheckParams, std::move(healthListener), path::join(ifs.root, constants().mount)); + // pre-v2 IncFS doesn't do automatic reserved space trimming - need to run it manually + if (!(mIncFs->features() & incfs::Features::v2)) { + addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool { + if (!state.fullyLoaded) { + return true; + } + + const auto ifs = getIfs(storageId); + if (!ifs) { + return false; + } + trimReservedSpaceV1(*ifs); + return false; + }); + } + addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool { if (!state.fullyLoaded || state.readLogsEnabled) { return true; diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index a697305457f8..fb6f56c9166e 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -452,6 +452,8 @@ private: StorageLoadingProgressListener&& progressListener); long getMillsSinceOldestPendingRead(StorageId storage); + void trimReservedSpaceV1(const IncFsMount& ifs); + private: const std::unique_ptr<VoldServiceWrapper> mVold; const std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager; diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index 34654994c9fc..8e416f36f49e 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -212,6 +212,9 @@ public: std::string_view path) const final { return incfs::isFullyLoaded(control, path); } + incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const final { + return incfs::isFullyLoaded(control, id); + } incfs::LoadingState isEverythingFullyLoaded(const Control& control) const final { return incfs::isEverythingFullyLoaded(control); } @@ -227,9 +230,8 @@ public: ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final { return incfs::writeBlocks({blocks.data(), size_t(blocks.size())}); } - ErrorCode reserveSpace(const Control& control, std::string_view path, - IncFsSize size) const final { - return incfs::reserveSpace(control, path, size); + ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const final { + return incfs::reserveSpace(control, id, size); } WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final { @@ -238,19 +240,26 @@ public: ErrorCode setUidReadTimeouts(const Control& control, const std::vector<android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts) const final { - std::vector<incfs::UidReadTimeouts> timeouts; - timeouts.resize(perUidReadTimeouts.size()); + std::vector<incfs::UidReadTimeouts> timeouts(perUidReadTimeouts.size()); for (int i = 0, size = perUidReadTimeouts.size(); i < size; ++i) { - auto&& timeout = timeouts[i]; + auto& timeout = timeouts[i]; const auto& perUidTimeout = perUidReadTimeouts[i]; timeout.uid = perUidTimeout.uid; timeout.minTimeUs = perUidTimeout.minTimeUs; timeout.minPendingTimeUs = perUidTimeout.minPendingTimeUs; timeout.maxPendingTimeUs = perUidTimeout.maxPendingTimeUs; } - return incfs::setUidReadTimeouts(control, timeouts); } + ErrorCode forEachFile(const Control& control, FileCallback cb) const final { + return incfs::forEachFile(control, + [&](auto& control, FileId id) { return cb(control, id); }); + } + ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const final { + return incfs::forEachIncompleteFile(control, [&](auto& control, FileId id) { + return cb(control, id); + }); + } }; static JNIEnv* getOrAttachJniEnv(JavaVM* jvm); diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index a787db573dfc..d4cdcbe9cac0 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -84,6 +84,8 @@ public: void(std::string_view root, std::string_view backingDir, std::span<std::pair<std::string_view, std::string_view>> binds)>; + using FileCallback = android::base::function_ref<bool(const Control& control, FileId fileId)>; + static std::string toString(FileId fileId); virtual ~IncFsWrapper() = default; @@ -105,14 +107,14 @@ public: const Control& control, std::string_view path) const = 0; virtual incfs::LoadingState isFileFullyLoaded(const Control& control, std::string_view path) const = 0; + virtual incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const = 0; virtual incfs::LoadingState isEverythingFullyLoaded(const Control& control) const = 0; virtual ErrorCode link(const Control& control, std::string_view from, std::string_view to) const = 0; virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0; virtual UniqueFd openForSpecialOps(const Control& control, FileId id) const = 0; virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0; - virtual ErrorCode reserveSpace(const Control& control, std::string_view path, - IncFsSize size) const = 0; + virtual ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const = 0; virtual WaitResult waitForPendingReads( const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0; @@ -120,6 +122,8 @@ public: const Control& control, const std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts) const = 0; + virtual ErrorCode forEachFile(const Control& control, FileCallback cb) const = 0; + virtual ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const = 0; }; class AppOpsManagerWrapper { diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index 8ba7c8686cba..ddb778462df5 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -379,6 +379,7 @@ public: std::string_view path)); MOCK_CONST_METHOD2(isFileFullyLoaded, incfs::LoadingState(const Control& control, std::string_view path)); + MOCK_CONST_METHOD2(isFileFullyLoaded, incfs::LoadingState(const Control& control, FileId id)); MOCK_CONST_METHOD1(isEverythingFullyLoaded, incfs::LoadingState(const Control& control)); MOCK_CONST_METHOD3(link, ErrorCode(const Control& control, std::string_view from, @@ -386,14 +387,15 @@ public: MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path)); MOCK_CONST_METHOD2(openForSpecialOps, UniqueFd(const Control& control, FileId id)); MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks)); - MOCK_CONST_METHOD3(reserveSpace, - ErrorCode(const Control& control, std::string_view path, IncFsSize size)); + MOCK_CONST_METHOD3(reserveSpace, ErrorCode(const Control& control, FileId id, IncFsSize size)); 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)); + MOCK_CONST_METHOD2(forEachFile, ErrorCode(const Control& control, FileCallback cb)); + MOCK_CONST_METHOD2(forEachIncompleteFile, ErrorCode(const Control& control, FileCallback cb)); MockIncFs() { ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); @@ -1594,7 +1596,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedNoData) { int storageId = mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel, IncrementalService::CreateOptions::CreateNew); - EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _)) + EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>())) .Times(1) .WillOnce(Return(incfs::LoadingState::MissingBlocks)); ASSERT_GT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0); @@ -1605,7 +1607,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedError) { int storageId = mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel, IncrementalService::CreateOptions::CreateNew); - EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _)) + EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>())) .Times(1) .WillOnce(Return(incfs::LoadingState(-1))); ASSERT_LT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0); @@ -1616,7 +1618,7 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccess) { int storageId = mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel, IncrementalService::CreateOptions::CreateNew); - EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _)) + EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>())) .Times(1) .WillOnce(Return(incfs::LoadingState::Full)); ASSERT_EQ(0, (int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index a262939c0ef9..29aedcea0cd2 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -295,10 +295,30 @@ public final class ProfcollectForwardingService extends SystemService { return; } - try { - mIProfcollect.report(); - } catch (RemoteException e) { - Log.e(LOG_TAG, e.getMessage()); - } + final boolean uploadReport = + DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, + "upload_report", false); + + new Thread(() -> { + try { + String reportPath = mIProfcollect.report(); + if (!uploadReport) { + return; + } + Intent uploadIntent = + new Intent("com.google.android.apps.betterbug.intent.action.UPLOAD_PROFILE") + .setPackage("com.google.android.apps.internal.betterbug") + .putExtra("EXTRA_DESTINATION", "PROFCOLLECT") + .putExtra("EXTRA_PACKAGE_NAME", getContext().getPackageName()) + .putExtra("EXTRA_PROFILE_PATH", reportPath) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + Context context = getContext(); + if (context.getPackageManager().queryBroadcastReceivers(uploadIntent, 0) != null) { + context.sendBroadcast(uploadIntent); + } + } catch (RemoteException e) { + Log.e(LOG_TAG, e.getMessage()); + } + }).start(); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java index 22b2f7e04069..d220444f47de 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java @@ -1015,7 +1015,7 @@ public class ApplicationExitInfoTest { assertNotNull(info); if (timestamp != null) { - final long tolerance = 1000; // ms + final long tolerance = 10000; // ms assertTrue(timestamp - tolerance <= info.getTimestamp()); assertTrue(timestamp + tolerance >= info.getTimestamp()); } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 9ffb50176f0e..5c8a7d25a25d 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -80,6 +80,7 @@ import android.util.Log; import androidx.test.filters.SmallTest; +import com.android.internal.widget.LockPatternUtils; import com.android.server.FgThread; import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.pm.UserManagerInternal; @@ -123,6 +124,7 @@ public class UserControllerTest { private static final long HANDLER_WAIT_TIME_MS = 100; private UserController mUserController; + private LockPatternUtils mLockPatternUtils; private TestInjector mInjector; private final HashMap<Integer, UserState> mUserStates = new HashMap<>(); @@ -161,6 +163,13 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); + + // Make it appear that calling unlockUserKey() is needed. + doReturn(true).when(mInjector).isFileEncryptedNativeOnly(); + mLockPatternUtils = mock(LockPatternUtils.class); + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false); + doReturn(mLockPatternUtils).when(mInjector).getLockPatternUtils(); + // All UserController params are set to default. mUserController = new UserController(mInjector); setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS); @@ -552,6 +561,20 @@ public class UserControllerTest { /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true); } + /** + * Test that if a user has a lock screen credential set, then UserController + * doesn't bother trying to unlock their storage key without a credential + * token, as it will never work. + */ + @Test + public void testSecureUserUnlockNotAttempted() throws Exception { + when(mLockPatternUtils.isSecure(eq(TEST_USER_ID1))).thenReturn(true); + setUpUser(TEST_USER_ID1, 0); + mUserController.startUser(TEST_USER_ID1, /* foreground= */ false); + verify(mInjector.mStorageManagerMock, times(0)) + .unlockUserKey(eq(TEST_USER_ID1), anyInt(), any(), any()); + } + @Test public void testStartProfile_fullUserFails() { setUpUser(TEST_USER_ID1, 0); diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index dbb415c88a5f..e7ffea0a650d 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -54,6 +54,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; @Presubmit @@ -117,10 +118,13 @@ public final class UpdatableFontDirTest { } } + private static final long CURRENT_TIME = 1234567890L; + private File mCacheDir; private File mUpdatableFontFilesDir; private File mConfigFile; private List<File> mPreinstalledFontDirs; + private Supplier<Long> mCurrentTimeSupplier = () -> CURRENT_TIME; @SuppressWarnings("ResultOfMethodCallIgnored") @Before @@ -147,7 +151,7 @@ public final class UpdatableFontDirTest { @Test public void construct() throws Exception { - long expectedModifiedDate = 1234567890; + long expectedModifiedDate = CURRENT_TIME / 2; FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); @@ -155,7 +159,7 @@ public final class UpdatableFontDirTest { writeConfig(config, mConfigFile); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dirForPreparation.loadFontFileMap(); assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) .isEqualTo(expectedModifiedDate); @@ -168,6 +172,9 @@ public final class UpdatableFontDirTest { + " <font>foo.ttf</font>" + " <font>bar.ttf</font>" + "</family>"))); + // Verifies that getLastModifiedTimeMillis() returns the value of currentTimeMillis. + assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) + .isEqualTo(CURRENT_TIME); // Four font dirs are created. assertThat(mUpdatableFontFilesDir.list()).hasLength(4); assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) @@ -175,7 +182,7 @@ public final class UpdatableFontDirTest { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3); @@ -199,7 +206,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getFontFileMap()).isEmpty(); assertThat(dir.getFontFamilyMap()).isEmpty(); @@ -211,7 +218,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), @@ -229,7 +236,7 @@ public final class UpdatableFontDirTest { dirForPreparation.getFontFileMap().get("foo.ttf").getAbsolutePath()); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getFontFileMap()).isEmpty(); // All font dirs (including dir for "bar.ttf") should be deleted. @@ -243,7 +250,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), @@ -262,7 +269,7 @@ public final class UpdatableFontDirTest { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getFontFileMap()).isEmpty(); // All font dirs (including dir for "bar.ttf") should be deleted. @@ -276,7 +283,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), @@ -296,7 +303,7 @@ public final class UpdatableFontDirTest { FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2"); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); // For foo.ttf, preinstalled font (revision 5) should be used. assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf"); @@ -317,7 +324,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - new File("/dev/null")); + new File("/dev/null"), mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getFontFileMap()).isEmpty(); assertThat(dir.getFontFamilyMap()).isEmpty(); @@ -329,7 +336,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), @@ -351,7 +358,7 @@ public final class UpdatableFontDirTest { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); // The state should be rolled back as a whole if one of the update requests fail. assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); @@ -369,7 +376,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE))); @@ -387,7 +394,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE))); @@ -406,7 +413,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2", GOOD_SIGNATURE))); @@ -428,7 +435,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE))); @@ -445,7 +452,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Arrays.asList( @@ -463,7 +470,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -485,7 +492,7 @@ public final class UpdatableFontDirTest { FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test.ttf,1"); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -517,7 +524,7 @@ public final class UpdatableFontDirTest { try { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - readonlyFile); + readonlyFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -551,7 +558,7 @@ public final class UpdatableFontDirTest { public long getRevision(File file) throws IOException { return 0; } - }, fakeFsverityUtil, mConfigFile); + }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -580,7 +587,7 @@ public final class UpdatableFontDirTest { public long getRevision(File file) throws IOException { return 0; } - }, fakeFsverityUtil, mConfigFile); + }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -617,7 +624,7 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -637,7 +644,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE))); @@ -660,7 +667,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Arrays.asList( @@ -682,7 +689,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -703,7 +710,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -723,7 +730,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); // We assume we have monospace. assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace"); @@ -755,7 +762,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty(); FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index d405113d064c..100d3ea87a89 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -1773,57 +1773,75 @@ public class NetworkPolicyManagerServiceTest { true); } + private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) { + stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, + rxBytes, 1, txBytes, 1, 0); + when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) + .thenReturn(stats.getTotalBytes()); + when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) + .thenReturn(stats); + } + + private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException { + final NetworkPolicyManagerInternal npmi = LocalServices + .getService(NetworkPolicyManagerInternal.class); + npmi.onStatsProviderWarningOrLimitReached("TEST"); + // Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED. + postMsgAndWaitForCompletion(); + verify(mStatsService).forceUpdate(); + // Wait for processing of MSG_*_INTERFACE_QUOTAS. + postMsgAndWaitForCompletion(); + } + /** - * Test that when StatsProvider triggers limit reached, new limit will be calculated and - * re-armed. + * Test that when StatsProvider triggers warning and limit reached, new quotas will be + * calculated and re-armed. */ @Test - public void testStatsProviderLimitReached() throws Exception { + public void testStatsProviderWarningAndLimitReached() throws Exception { final int CYCLE_DAY = 15; final NetworkStats stats = new NetworkStats(0L, 1); - stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, - 2999, 1, 2000, 1, 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenReturn(stats.getTotalBytes()); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenReturn(stats); + increaseMockedTotalBytes(stats, 2999, 2000); // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); - verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, Long.MAX_VALUE); + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, + Long.MAX_VALUE); - // Set limit to 10KB. + // Set warning to 7KB and limit to 10KB. setNetworkPolicies(new NetworkPolicy( - sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L, - true)); + sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, 7000L, 10000L, true)); postMsgAndWaitForCompletion(); - // Verifies that remaining quota is set to providers. - verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L); - + // Verifies that remaining quotas are set to providers. + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L); reset(mStatsService); - // Increase the usage. - stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, - 1000, 1, 999, 1, 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenReturn(stats.getTotalBytes()); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenReturn(stats); + // Increase the usage and simulates that limit reached fires earlier by provider, + // but actually the quota is not yet reached. Verifies that the limit reached leads to + // a force update and new quotas should be set. + increaseMockedTotalBytes(stats, 1000, 999); + triggerOnStatsProviderWarningOrLimitReached(); + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L); + reset(mStatsService); - // Simulates that limit reached fires earlier by provider, but actually the quota is not - // yet reached. - final NetworkPolicyManagerInternal npmi = LocalServices - .getService(NetworkPolicyManagerInternal.class); - npmi.onStatsProviderWarningOrLimitReached("TEST"); + // Increase the usage and simulate warning reached, the new warning should be unlimited + // since service will disable warning quota to stop lower layer from keep triggering + // warning reached event. + increaseMockedTotalBytes(stats, 1000L, 1000); + triggerOnStatsProviderWarningOrLimitReached(); + verify(mStatsService).setStatsProviderWarningAndLimitAsync( + TEST_IFACE, Long.MAX_VALUE, 1002L); + reset(mStatsService); - // Verifies that the limit reached leads to a force update and new limit should be set. - postMsgAndWaitForCompletion(); - verify(mStatsService).forceUpdate(); - postMsgAndWaitForCompletion(); - verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L - 1999L); + // Increase the usage that over the warning and limit, the new limit should set to 1 to + // block the network traffic. + increaseMockedTotalBytes(stats, 1000L, 1000); + triggerOnStatsProviderWarningOrLimitReached(); + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L); + reset(mStatsService); } /** diff --git a/telephony/java/android/telephony/data/NrQos.java b/telephony/java/android/telephony/data/NrQos.java index 2011eed26977..fe124ac15393 100644 --- a/telephony/java/android/telephony/data/NrQos.java +++ b/telephony/java/android/telephony/data/NrQos.java @@ -50,6 +50,18 @@ public final class NrQos extends Qos implements Parcelable { return new NrQos(in); } + public int get5Qi() { + return fiveQi; + } + + public int getQfi() { + return qosFlowId; + } + + public int getAveragingWindow() { + return averagingWindowMs; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(Qos.QOS_TYPE_NR, dest, flags); diff --git a/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl b/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl new file mode 100644 index 000000000000..fd3bbb0865cb --- /dev/null +++ b/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.telephony.data; + + parcelable NrQosSessionAttributes; diff --git a/telephony/java/android/telephony/data/NrQosSessionAttributes.java b/telephony/java/android/telephony/data/NrQosSessionAttributes.java new file mode 100644 index 000000000000..857ccb960d52 --- /dev/null +++ b/telephony/java/android/telephony/data/NrQosSessionAttributes.java @@ -0,0 +1,256 @@ +/* + * 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 android.telephony.data; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.net.QosSessionAttributes; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Provides Qos attributes of an NR bearer. + * + * {@hide} + */ +@SystemApi +public final class NrQosSessionAttributes implements Parcelable, QosSessionAttributes { + private static final String TAG = NrQosSessionAttributes.class.getSimpleName(); + private final int m5Qi; + private final int mQfi; + private final long mMaxUplinkBitRate; + private final long mMaxDownlinkBitRate; + private final long mGuaranteedUplinkBitRate; + private final long mGuaranteedDownlinkBitRate; + private final long mAveragingWindow; + @NonNull private final List<InetSocketAddress> mRemoteAddresses; + + /** + * 5G QOS Identifier (5QI), see 3GPP TS 24.501 and 23.501. + * The allowed values are standard values(1-9, 65-68, 69-70, 75, 79-80, 82-85) + * defined in the spec and operator specific values in the range 128-254. + * + * @return the 5QI of the QOS flow + */ + public int get5Qi() { + return m5Qi; + } + + /** + * QOS flow identifier of the QOS flow description in the + * range of 1 to 63. see 3GPP TS 24.501 and 23.501. + * + * @return the QOS flow identifier of the session + */ + public int getQfi() { + return mQfi; + } + + /** + * Minimum bit rate in kbps that is guaranteed to be provided by the network on the uplink. + * + * see 3GPP TS 24.501 section 6.2.5 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the guaranteed bit rate in kbps + */ + public long getGuaranteedUplinkBitRate() { + return mGuaranteedUplinkBitRate; + } + + /** + * Minimum bit rate in kbps that is guaranteed to be provided by the network on the downlink. + * + * see 3GPP TS 24.501 section 6.2.5 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the guaranteed bit rate in kbps + */ + public long getGuaranteedDownlinkBitRate() { + return mGuaranteedDownlinkBitRate; + } + + /** + * The maximum uplink kbps that the network will accept. + * + * see 3GPP TS 24.501 section 6.2.5 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the max uplink bit rate in kbps + */ + public long getMaxUplinkBitRate() { + return mMaxUplinkBitRate; + } + + /** + * The maximum downlink kbps that the network can provide. + * + * see 3GPP TS 24.501 section 6.2.5 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the max downlink bit rate in kbps + */ + public long getMaxDownlinkBitRate() { + return mMaxDownlinkBitRate; + } + + /** + * The duration in milliseconds over which the maximum bit rates and guaranteed bit rates + * are calculated + * + * see 3GPP TS 24.501 section 6.2.5 + * + * @return the averaging window duration in milliseconds + */ + public long getAveragingWindow() { + return mAveragingWindow; + } + + /** + * List of remote addresses associated with the Qos Session. The given uplink bit rates apply + * to this given list of remote addresses. + * + * Note: In the event that the list is empty, it is assumed that the uplink bit rates apply to + * all remote addresses that are not contained in a different set of attributes. + * + * @return list of remote socket addresses that the attributes apply to + */ + @NonNull + public List<InetSocketAddress> getRemoteAddresses() { + return mRemoteAddresses; + } + + /** + * ..ctor for attributes + * + * @param fiveQi 5G quality class indicator + * @param qfi QOS flow identifier + * @param maxDownlinkBitRate the max downlink bit rate in kbps + * @param maxUplinkBitRate the max uplink bit rate in kbps + * @param guaranteedDownlinkBitRate the guaranteed downlink bit rate in kbps + * @param guaranteedUplinkBitRate the guaranteed uplink bit rate in kbps + * @param averagingWindow the averaging window duration in milliseconds + * @param remoteAddresses the remote addresses that the uplink bit rates apply to + * + * @hide + */ + public NrQosSessionAttributes(final int fiveQi, final int qfi, + final long maxDownlinkBitRate, final long maxUplinkBitRate, + final long guaranteedDownlinkBitRate, final long guaranteedUplinkBitRate, + final long averagingWindow, @NonNull final List<InetSocketAddress> remoteAddresses) { + Objects.requireNonNull(remoteAddresses, "remoteAddress must be non-null"); + m5Qi = fiveQi; + mQfi = qfi; + mMaxDownlinkBitRate = maxDownlinkBitRate; + mMaxUplinkBitRate = maxUplinkBitRate; + mGuaranteedDownlinkBitRate = guaranteedDownlinkBitRate; + mGuaranteedUplinkBitRate = guaranteedUplinkBitRate; + mAveragingWindow = averagingWindow; + + final List<InetSocketAddress> remoteAddressesTemp = copySocketAddresses(remoteAddresses); + mRemoteAddresses = Collections.unmodifiableList(remoteAddressesTemp); + } + + private static List<InetSocketAddress> copySocketAddresses( + @NonNull final List<InetSocketAddress> remoteAddresses) { + final List<InetSocketAddress> remoteAddressesTemp = new ArrayList<>(); + for (final InetSocketAddress socketAddress : remoteAddresses) { + if (socketAddress != null && socketAddress.getAddress() != null) { + remoteAddressesTemp.add(socketAddress); + } + } + return remoteAddressesTemp; + } + + private NrQosSessionAttributes(@NonNull final Parcel in) { + m5Qi = in.readInt(); + mQfi = in.readInt(); + mMaxDownlinkBitRate = in.readLong(); + mMaxUplinkBitRate = in.readLong(); + mGuaranteedDownlinkBitRate = in.readLong(); + mGuaranteedUplinkBitRate = in.readLong(); + mAveragingWindow = in.readLong(); + + final int size = in.readInt(); + final List<InetSocketAddress> remoteAddresses = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + final byte[] addressBytes = in.createByteArray(); + final int port = in.readInt(); + try { + remoteAddresses.add( + new InetSocketAddress(InetAddress.getByAddress(addressBytes), port)); + } catch (final UnknownHostException e) { + // Impossible case since its filtered out the null values in the ..ctor + Log.e(TAG, "unable to unparcel remote address at index: " + i, e); + } + } + mRemoteAddresses = Collections.unmodifiableList(remoteAddresses); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeInt(m5Qi); + dest.writeInt(mQfi); + dest.writeLong(mMaxDownlinkBitRate); + dest.writeLong(mMaxUplinkBitRate); + dest.writeLong(mGuaranteedDownlinkBitRate); + dest.writeLong(mGuaranteedUplinkBitRate); + dest.writeLong(mAveragingWindow); + + final int size = mRemoteAddresses.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { + final InetSocketAddress address = mRemoteAddresses.get(i); + dest.writeByteArray(address.getAddress().getAddress()); + dest.writeInt(address.getPort()); + } + } + + @NonNull + public static final Creator<NrQosSessionAttributes> CREATOR = + new Creator<NrQosSessionAttributes>() { + @NonNull + @Override + public NrQosSessionAttributes createFromParcel(@NonNull final Parcel in) { + return new NrQosSessionAttributes(in); + } + + @NonNull + @Override + public NrQosSessionAttributes[] newArray(final int size) { + return new NrQosSessionAttributes[size]; + } + }; +} diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java index f49d4fcab5f2..4259a8620727 100644 --- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java @@ -32,7 +32,7 @@ public class HierrarchicalDataClassBase implements Parcelable { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -98,8 +98,8 @@ public class HierrarchicalDataClassBase implements Parcelable { }; @DataClass.Generated( - time = 1604522375155L, - codegenVersion = "1.0.20", + time = 1616541542813L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java", inputSignatures = "private int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)") @Deprecated diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java index e8cce23fa324..677094b14fd6 100644 --- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java @@ -46,7 +46,7 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -120,8 +120,8 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { }; @DataClass.Generated( - time = 1604522376059L, - codegenVersion = "1.0.20", + time = 1616541543730L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java", inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)") @Deprecated diff --git a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java index 9de65522fccd..eb260ab6d35d 100644 --- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java +++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java @@ -54,7 +54,7 @@ public class ParcelAllTheThingsDataClass implements Parcelable { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -412,8 +412,8 @@ public class ParcelAllTheThingsDataClass implements Parcelable { } @DataClass.Generated( - time = 1604522374190L, - codegenVersion = "1.0.20", + time = 1616541541942L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java", inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)") @Deprecated diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java index 5a3e273275ed..158e0656b574 100644 --- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java +++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java @@ -23,8 +23,11 @@ import android.annotation.Size; import android.annotation.StringDef; import android.annotation.StringRes; import android.annotation.UserIdInt; +import android.companion.ICompanionDeviceManager; import android.content.pm.PackageManager; import android.net.LinkAddress; +import android.os.Binder; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.view.accessibility.AccessibilityNodeInfo; @@ -282,6 +285,16 @@ public final class SampleDataClass implements Parcelable { /** + * Binder types are also supported + */ + private @NonNull IBinder mToken = new Binder(); + /** + * AIDL interface types are also supported + */ + private @Nullable ICompanionDeviceManager mIPCInterface = null; + + + /** * Manually declaring any method that would otherwise be generated suppresses its generation, * allowing for fine-grained overrides of the generated behavior. */ @@ -344,7 +357,7 @@ public final class SampleDataClass implements Parcelable { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -492,6 +505,10 @@ public final class SampleDataClass implements Parcelable { * * Validation annotations following {@link Each} annotation, will be applied for each * array/collection element instead. + * @param token + * Binder types are also supported + * @param iPCInterface + * AIDL interface types are also supported */ @DataClass.Generated.Member public SampleDataClass( @@ -514,7 +531,9 @@ public final class SampleDataClass implements Parcelable { @Nullable LinkAddress[] linkAddresses5, @StringRes int stringRes, @android.annotation.IntRange(from = 0, to = 6) int dayOfWeek, - @Size(2) @NonNull @FloatRange(from = 0f) float[] coords) { + @Size(2) @NonNull @FloatRange(from = 0f) float[] coords, + @NonNull IBinder token, + @Nullable ICompanionDeviceManager iPCInterface) { this.mNum = num; this.mNum2 = num2; this.mNum4 = num4; @@ -597,6 +616,10 @@ public final class SampleDataClass implements Parcelable { "from", 0f); } + this.mToken = token; + AnnotationValidations.validate( + NonNull.class, null, mToken); + this.mIPCInterface = iPCInterface; onConstructed(); } @@ -797,6 +820,22 @@ public final class SampleDataClass implements Parcelable { } /** + * Binder types are also supported + */ + @DataClass.Generated.Member + public @NonNull IBinder getToken() { + return mToken; + } + + /** + * AIDL interface types are also supported + */ + @DataClass.Generated.Member + public @Nullable ICompanionDeviceManager getIPCInterface() { + return mIPCInterface; + } + + /** * When using transient fields for caching it's often also a good idea to initialize them * lazily. * @@ -1089,6 +1128,26 @@ public final class SampleDataClass implements Parcelable { return this; } + /** + * Binder types are also supported + */ + @DataClass.Generated.Member + public @NonNull SampleDataClass setToken(@NonNull IBinder value) { + mToken = value; + AnnotationValidations.validate( + NonNull.class, null, mToken); + return this; + } + + /** + * AIDL interface types are also supported + */ + @DataClass.Generated.Member + public @NonNull SampleDataClass setIPCInterface(@NonNull ICompanionDeviceManager value) { + mIPCInterface = value; + return this; + } + @Override @DataClass.Generated.Member public String toString() { @@ -1115,7 +1174,9 @@ public final class SampleDataClass implements Parcelable { "linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " + "stringRes = " + mStringRes + ", " + "dayOfWeek = " + mDayOfWeek + ", " + - "coords = " + java.util.Arrays.toString(mCoords) + + "coords = " + java.util.Arrays.toString(mCoords) + ", " + + "token = " + mToken + ", " + + "iPCInterface = " + mIPCInterface + " }"; } @@ -1151,7 +1212,9 @@ public final class SampleDataClass implements Parcelable { && java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5) && mStringRes == that.mStringRes && mDayOfWeek == that.mDayOfWeek - && java.util.Arrays.equals(mCoords, that.mCoords); + && java.util.Arrays.equals(mCoords, that.mCoords) + && Objects.equals(mToken, that.mToken) + && Objects.equals(mIPCInterface, that.mIPCInterface); } @Override @@ -1181,6 +1244,8 @@ public final class SampleDataClass implements Parcelable { _hash = 31 * _hash + mStringRes; _hash = 31 * _hash + mDayOfWeek; _hash = 31 * _hash + java.util.Arrays.hashCode(mCoords); + _hash = 31 * _hash + Objects.hashCode(mToken); + _hash = 31 * _hash + Objects.hashCode(mIPCInterface); return _hash; } @@ -1208,6 +1273,8 @@ public final class SampleDataClass implements Parcelable { actionInt.acceptInt(this, "stringRes", mStringRes); actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek); actionObject.acceptObject(this, "coords", mCoords); + actionObject.acceptObject(this, "token", mToken); + actionObject.acceptObject(this, "iPCInterface", mIPCInterface); } /** @deprecated May cause boxing allocations - use with caution! */ @@ -1234,6 +1301,8 @@ public final class SampleDataClass implements Parcelable { action.acceptObject(this, "stringRes", mStringRes); action.acceptObject(this, "dayOfWeek", mDayOfWeek); action.acceptObject(this, "coords", mCoords); + action.acceptObject(this, "token", mToken); + action.acceptObject(this, "iPCInterface", mIPCInterface); } @DataClass.Generated.Member @@ -1269,6 +1338,7 @@ public final class SampleDataClass implements Parcelable { if (mOtherParcelable != null) flg |= 0x40; if (mLinkAddresses4 != null) flg |= 0x800; if (mLinkAddresses5 != null) flg |= 0x10000; + if (mIPCInterface != null) flg |= 0x200000; dest.writeLong(flg); dest.writeInt(mNum); dest.writeInt(mNum2); @@ -1290,6 +1360,8 @@ public final class SampleDataClass implements Parcelable { dest.writeInt(mStringRes); dest.writeInt(mDayOfWeek); dest.writeFloatArray(mCoords); + dest.writeStrongBinder(mToken); + if (mIPCInterface != null) dest.writeStrongInterface(mIPCInterface); } @Override @@ -1326,6 +1398,8 @@ public final class SampleDataClass implements Parcelable { int stringRes = in.readInt(); int dayOfWeek = in.readInt(); float[] coords = in.createFloatArray(); + IBinder token = (IBinder) in.readStrongBinder(); + ICompanionDeviceManager iPCInterface = (flg & 0x200000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder()); this.mNum = num; this.mNum2 = num2; @@ -1409,6 +1483,10 @@ public final class SampleDataClass implements Parcelable { "from", 0f); } + this.mToken = token; + AnnotationValidations.validate( + NonNull.class, null, mToken); + this.mIPCInterface = iPCInterface; onConstructed(); } @@ -1454,6 +1532,8 @@ public final class SampleDataClass implements Parcelable { private @StringRes int mStringRes; private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek; private @Size(2) @NonNull @FloatRange(from = 0f) float[] mCoords; + private @NonNull IBinder mToken; + private @Nullable ICompanionDeviceManager mIPCInterface; private long mBuilderFieldsSet = 0L; @@ -1794,10 +1874,32 @@ public final class SampleDataClass implements Parcelable { return this; } + /** + * Binder types are also supported + */ + @DataClass.Generated.Member + public @NonNull Builder setToken(@NonNull IBinder value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x100000; + mToken = value; + return this; + } + + /** + * AIDL interface types are also supported + */ + @DataClass.Generated.Member + public @NonNull Builder setIPCInterface(@NonNull ICompanionDeviceManager value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x200000; + mIPCInterface = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull SampleDataClass build() { checkNotUsed(); - mBuilderFieldsSet |= 0x100000; // Mark builder used + mBuilderFieldsSet |= 0x400000; // Mark builder used if ((mBuilderFieldsSet & 0x10) == 0) { mName2 = "Bob"; @@ -1841,6 +1943,12 @@ public final class SampleDataClass implements Parcelable { if ((mBuilderFieldsSet & 0x80000) == 0) { mCoords = new float[] { 0f, 0f }; } + if ((mBuilderFieldsSet & 0x100000) == 0) { + mToken = new Binder(); + } + if ((mBuilderFieldsSet & 0x200000) == 0) { + mIPCInterface = null; + } SampleDataClass o = new SampleDataClass( mNum, mNum2, @@ -1861,12 +1969,14 @@ public final class SampleDataClass implements Parcelable { mLinkAddresses5, mStringRes, mDayOfWeek, - mCoords); + mCoords, + mToken, + mIPCInterface); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x100000) != 0) { + if ((mBuilderFieldsSet & 0x400000) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -1874,10 +1984,10 @@ public final class SampleDataClass implements Parcelable { } @DataClass.Generated( - time = 1604522372172L, - codegenVersion = "1.0.20", + time = 1616541539978L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java", - inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)") + inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)") @Deprecated private void __metadata() {} diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java index 3ab34452f9fc..a535e227cccf 100644 --- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java +++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java @@ -85,7 +85,7 @@ public class SampleWithCustomBuilder implements Parcelable { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -253,8 +253,8 @@ public class SampleWithCustomBuilder implements Parcelable { } @DataClass.Generated( - time = 1604522373190L, - codegenVersion = "1.0.20", + time = 1616541540898L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java", inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java index 8901cac1cb1b..d40962456741 100644 --- a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java +++ b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java @@ -36,7 +36,7 @@ public class SampleWithNestedDataClasses { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -135,8 +135,8 @@ public class SampleWithNestedDataClasses { }; @DataClass.Generated( - time = 1604522377998L, - codegenVersion = "1.0.20", + time = 1616541545539L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java", inputSignatures = " @android.annotation.NonNull java.lang.String mBar\nclass NestedDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)") @Deprecated @@ -160,7 +160,7 @@ public class SampleWithNestedDataClasses { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -259,8 +259,8 @@ public class SampleWithNestedDataClasses { }; @DataClass.Generated( - time = 1604522378007L, - codegenVersion = "1.0.20", + time = 1616541545548L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java", inputSignatures = " @android.annotation.NonNull long mBaz2\nclass NestedDataClass3 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)") @Deprecated @@ -274,7 +274,7 @@ public class SampleWithNestedDataClasses { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -373,8 +373,8 @@ public class SampleWithNestedDataClasses { }; @DataClass.Generated( - time = 1604522378015L, - codegenVersion = "1.0.20", + time = 1616541545552L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java", inputSignatures = " @android.annotation.NonNull java.lang.String mBaz\nclass NestedDataClass2 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)") @Deprecated diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java index ac776f3c2764..3583b95fb4ce 100644 --- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java +++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java @@ -64,7 +64,7 @@ public class StaleDataclassDetectorFalsePositivesTest { - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -89,8 +89,8 @@ public class StaleDataclassDetectorFalsePositivesTest { } @DataClass.Generated( - time = 1604522377011L, - codegenVersion = "1.0.20", + time = 1616541544639L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java", inputSignatures = "private @android.annotation.Nullable java.util.List<java.util.Set<?>> mUsesWildcards\npublic @android.annotation.NonNull java.lang.String someMethod(int)\nprivate @android.annotation.IntRange void annotatedWithConstArg()\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)") @Deprecated diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index fd376528aa44..44298d4b7652 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -265,6 +265,7 @@ import android.security.Credentials; import android.system.Os; import android.telephony.TelephonyManager; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.test.mock.MockContentResolver; import android.text.TextUtils; import android.util.ArraySet; @@ -9963,7 +9964,7 @@ public class ConnectivityServiceTest { && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes)); mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() - .sendQosSessionLost(qosCallbackId, sessionId); + .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER); waitForIdle(); verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session -> session.getSessionId() == sessionId @@ -9971,6 +9972,36 @@ public class ConnectivityServiceTest { } @Test + public void testNrQosCallbackAvailableAndLost() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + final int sessionId = 10; + final int qosCallbackId = 1; + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + waitForIdle(); + + final NrQosSessionAttributes attributes = new NrQosSessionAttributes( + 1, 2, 3, 4, 5, 6, 7, new ArrayList<>()); + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionAvailable(qosCallbackId, sessionId, attributes); + waitForIdle(); + + verify(mQosCallbackMockHelper.mCallback).onNrQosSessionAvailable(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_NR_BEARER), eq(attributes)); + + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_NR_BEARER); + waitForIdle(); + verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_NR_BEARER)); + } + + @Test public void testQosCallbackTooManyRequests() throws Exception { mQosCallbackMockHelper = new QosCallbackMockHelper(); diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt index 02ebaef90f0b..74392ddc30e6 100644 --- a/tools/codegen/src/com/android/codegen/FieldInfo.kt +++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt @@ -220,11 +220,12 @@ data class FieldInfo( isBinder(FieldInnerType!!) -> "BinderList" else -> "ParcelableList" } + isStrongBinder(Type) -> "StrongBinder" isIInterface(Type) -> "StrongInterface" - isBinder(Type) -> "StrongBinder" else -> "TypedObject" }.capitalize() + private fun isStrongBinder(type: String) = type == "Binder" || type == "IBinder" private fun isBinder(type: String) = type == "Binder" || type == "IBinder" || isIInterface(type) private fun isIInterface(type: String) = type.length >= 2 && type[0] == 'I' && type[1].isUpperCase() }
\ No newline at end of file diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt index d9ad649782bb..4da401951470 100644 --- a/tools/codegen/src/com/android/codegen/SharedConstants.kt +++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt @@ -1,7 +1,7 @@ package com.android.codegen const val CODEGEN_NAME = "codegen" -const val CODEGEN_VERSION = "1.0.22" +const val CODEGEN_VERSION = "1.0.23" const val CANONICAL_BUILDER_CLASS = "Builder" const val BASE_BUILDER_CLASS = "BaseBuilder" |