diff options
91 files changed, 1764 insertions, 691 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index a9c4a1501dd8..6dd7521e4d43 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -36,7 +36,6 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.UidObserver; -import android.app.compat.CompatChanges; import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; @@ -54,6 +53,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.Trace; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -74,6 +74,7 @@ import com.android.internal.util.ArrayUtils; import com.android.server.AppSchedulingModuleThread; import com.android.server.LocalServices; import com.android.server.PowerAllowlistInternal; +import com.android.server.compat.PlatformCompat; import com.android.server.job.ConstantsProto; import com.android.server.job.Flags; import com.android.server.job.JobSchedulerService; @@ -157,6 +158,15 @@ public final class QuotaController extends StateController { @Overridable // The change can be overridden in user build. static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS = 374323858L; + /** + * When enabled this change id overrides the default quota parameters adjustment. + */ + @VisibleForTesting + @ChangeId + @Disabled // Disabled by default + @Overridable // The change can be overridden in user build. + static final long OVERRIDE_QUOTA_ADJUST_DEFAULT_CONSTANTS = 378129159L; + @VisibleForTesting static class ExecutionStats { /** @@ -536,6 +546,8 @@ public final class QuotaController extends StateController { */ private final SparseSetArray<String> mSystemInstallers = new SparseSetArray<>(); + private final PlatformCompat mPlatformCompat; + /** An app has reached its quota. The message should contain a {@link UserPackage} object. */ @VisibleForTesting static final int MSG_REACHED_TIME_QUOTA = 0; @@ -587,6 +599,13 @@ public final class QuotaController extends StateController { PowerAllowlistInternal pai = LocalServices.getService(PowerAllowlistInternal.class); pai.registerTempAllowlistChangeListener(new TempAllowlistTracker()); + mPlatformCompat = (PlatformCompat) + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); + if (Flags.adjustQuotaDefaultConstants()) { + mPlatformCompat.registerListener(OVERRIDE_QUOTA_ADJUST_DEFAULT_CONSTANTS, + (packageName) -> handleQuotaDefaultConstantsCompatChange()); + } + try { ActivityManager.getService().registerUidObserver(new QcUidObserver(), ActivityManager.UID_OBSERVER_PROCSTATE, @@ -651,8 +670,9 @@ public final class QuotaController extends StateController { final int uid = jobStatus.getSourceUid(); if ((!Flags.enforceQuotaPolicyToTopStartedJobs() - || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, - uid)) && mTopAppCache.get(uid)) { + || mPlatformCompat.isChangeEnabledByUid( + OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, uid)) + && mTopAppCache.get(uid)) { if (DEBUG) { Slog.d(TAG, jobStatus.toShortString() + " is top started job"); } @@ -690,8 +710,8 @@ public final class QuotaController extends StateController { } } if (!Flags.enforceQuotaPolicyToTopStartedJobs() - || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, - jobStatus.getSourceUid())) { + || mPlatformCompat.isChangeEnabledByUid( + OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, jobStatus.getSourceUid())) { mTopStartedJobs.remove(jobStatus); } } @@ -805,8 +825,8 @@ public final class QuotaController extends StateController { /** @return true if the job was started while the app was in the TOP state. */ private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) { if (!Flags.enforceQuotaPolicyToTopStartedJobs() - || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, - jobStatus.getSourceUid())) { + || mPlatformCompat.isChangeEnabledByUid( + OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, jobStatus.getSourceUid())) { return mTopStartedJobs.contains(jobStatus); } @@ -1102,6 +1122,7 @@ public final class QuotaController extends StateController { final int standbyBucket) { final long baseLimitMs = mAllowedTimePerPeriodMs[standbyBucket]; if (Flags.adjustQuotaDefaultConstants() + && !isCompatOverridedForQuotaConstantAdjustment() && Flags.additionalQuotaForSystemInstaller() && standbyBucket == EXEMPTED_INDEX && mSystemInstallers.contains(userId, pkgName)) { @@ -1473,10 +1494,21 @@ public final class QuotaController extends StateController { } } + void handleQuotaDefaultConstantsCompatChange() { + synchronized (mLock) { + final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment(); + mQcConstants.adjustDefaultBucketWindowSizes(isCompatEnabled); + mQcConstants.adjustDefaultEjLimits(isCompatEnabled); + mQcConstants.mShouldReevaluateConstraints = true; + onConstantsUpdatedLocked(); + } + } + void processQuotaConstantsAdjustment() { - if (Flags.adjustQuotaDefaultConstants()) { - mQcConstants.adjustDefaultBucketWindowSizes(); - mQcConstants.adjustDefaultEjLimits(); + if (Flags.adjustQuotaDefaultConstants() + && !isCompatOverridedForQuotaConstantAdjustment()) { + mQcConstants.adjustDefaultBucketWindowSizes(false); + mQcConstants.adjustDefaultEjLimits(false); } } @@ -1505,6 +1537,11 @@ public final class QuotaController extends StateController { } } + private boolean isCompatOverridedForQuotaConstantAdjustment() { + return mPlatformCompat.isChangeEnabledByPackageName( + OVERRIDE_QUOTA_ADJUST_DEFAULT_CONSTANTS, "android", UserHandle.USER_SYSTEM); + } + private void incrementTimingSessionCountLocked(final int userId, @NonNull final String packageName) { final long now = sElapsedRealtimeClock.millis(); @@ -2689,7 +2726,8 @@ public final class QuotaController extends StateController { @VisibleForTesting int getProcessStateQuotaFreeThreshold(int uid) { if (Flags.enforceQuotaPolicyToFgsJobs() - && !CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) { + && !mPlatformCompat.isChangeEnabledByUid( + OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) { return ActivityManager.PROCESS_STATE_BOUND_TOP; } @@ -3596,25 +3634,40 @@ public final class QuotaController extends StateController { */ public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; - void adjustDefaultBucketWindowSizes() { - ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters() - ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS : - DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; - ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters() - ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS : - DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; - ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = Flags.tuneQuotaWindowDefaultParameters() - ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS : - DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS; - - WINDOW_SIZE_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters() - ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS : - DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS; - WINDOW_SIZE_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters() - ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS : - DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS; - WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS; - WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS; + void adjustDefaultBucketWindowSizes(boolean useLegacyQuotaConstants) { + if (useLegacyQuotaConstants) { + ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS; + + WINDOW_SIZE_EXEMPTED_MS = DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS; + WINDOW_SIZE_ACTIVE_MS = DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS; + WINDOW_SIZE_WORKING_MS = DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS; + WINDOW_SIZE_FREQUENT_MS = DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS; + } else { + ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS : + DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = + Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS : + DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS; + + WINDOW_SIZE_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS : + DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS; + WINDOW_SIZE_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters() + ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS : + DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS; + WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS; + WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS; + } mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = Math.min(MAX_PERIOD_MS, Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS)); @@ -3640,10 +3693,15 @@ public final class QuotaController extends StateController { ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS); } - void adjustDefaultEjLimits() { - EJ_LIMIT_WORKING_MS = DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS; - EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; - EJ_REWARD_INTERACTION_MS = DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS; + void adjustDefaultEjLimits(boolean useLegacyQuotaConstants) { + EJ_LIMIT_WORKING_MS = useLegacyQuotaConstants ? DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS + : DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS; + EJ_TOP_APP_TIME_CHUNK_SIZE_MS = useLegacyQuotaConstants + ? DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS : + DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; + EJ_REWARD_INTERACTION_MS = useLegacyQuotaConstants + ? DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS + : DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS; // The limit must be in the range [15 minutes, active limit]. mEJLimitsMs[WORKING_INDEX] = Math.max(15 * MINUTE_IN_MILLIS, @@ -3668,6 +3726,8 @@ public final class QuotaController extends StateController { public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @NonNull String key) { + final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment(); + switch (key) { case KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS: case KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS: @@ -3835,7 +3895,8 @@ public final class QuotaController extends StateController { case KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS: // We don't need to re-evaluate execution stats or constraint status for this. EJ_TOP_APP_TIME_CHUNK_SIZE_MS = - properties.getLong(key, Flags.adjustQuotaDefaultConstants() + properties.getLong(key, + Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS : DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS); // Limit chunking to be in the range [1 millisecond, 15 minutes] per event. @@ -3873,7 +3934,8 @@ public final class QuotaController extends StateController { case KEY_EJ_REWARD_INTERACTION_MS: // We don't need to re-evaluate execution stats or constraint status for this. EJ_REWARD_INTERACTION_MS = - properties.getLong(key, Flags.adjustQuotaDefaultConstants() + properties.getLong(key, + Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS : DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS); // Limit interaction reward to be in the range [5 seconds, 15 minutes] per @@ -3914,6 +3976,8 @@ public final class QuotaController extends StateController { } mExecutionPeriodConstantsUpdated = true; + final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment(); + // Query the values as an atomic set. final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_JOB_SCHEDULER, @@ -3958,27 +4022,27 @@ public final class QuotaController extends StateController { MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS); WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS, - (Flags.adjustQuotaDefaultConstants() + (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled && Flags.tuneQuotaWindowDefaultParameters()) ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS : - (Flags.adjustQuotaDefaultConstants() + (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS : DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS)); WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS, - (Flags.adjustQuotaDefaultConstants() + (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled && Flags.tuneQuotaWindowDefaultParameters()) ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS : - (Flags.adjustQuotaDefaultConstants() + (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS : DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS)); WINDOW_SIZE_WORKING_MS = properties.getLong(KEY_WINDOW_SIZE_WORKING_MS, - Flags.adjustQuotaDefaultConstants() + Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS : DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS); WINDOW_SIZE_FREQUENT_MS = properties.getLong(KEY_WINDOW_SIZE_FREQUENT_MS, - Flags.adjustQuotaDefaultConstants() + Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS : DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS); WINDOW_SIZE_RARE_MS = properties.getLong(KEY_WINDOW_SIZE_RARE_MS, @@ -4149,6 +4213,8 @@ public final class QuotaController extends StateController { } mEJLimitConstantsUpdated = true; + final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment(); + // Query the values as an atomic set. final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_JOB_SCHEDULER, @@ -4163,7 +4229,7 @@ public final class QuotaController extends StateController { EJ_LIMIT_ACTIVE_MS = properties.getLong( KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS); EJ_LIMIT_WORKING_MS = properties.getLong( - KEY_EJ_LIMIT_WORKING_MS, Flags.adjustQuotaDefaultConstants() + KEY_EJ_LIMIT_WORKING_MS, Flags.adjustQuotaDefaultConstants() && !isCompatEnabled ? DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS : DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS); EJ_LIMIT_FREQUENT_MS = properties.getLong( diff --git a/core/api/current.txt b/core/api/current.txt index 216bbab882a9..bba21f418e41 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -24820,7 +24820,7 @@ package android.media { method public android.view.Surface getSurface(); method public boolean isPrivacySensitive(); method public void pause() throws java.lang.IllegalStateException; - method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; + method @RequiresPermission(value=android.Manifest.permission.RECORD_AUDIO, conditional=true) public void prepare() throws java.io.IOException, java.lang.IllegalStateException; method public void registerAudioRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioRecordingCallback); method public void release(); method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener); @@ -24861,7 +24861,7 @@ package android.media { method public void setVideoProfile(@NonNull android.media.EncoderProfiles.VideoProfile); method public void setVideoSize(int, int) throws java.lang.IllegalStateException; method public void setVideoSource(int) throws java.lang.IllegalStateException; - method public void start() throws java.lang.IllegalStateException; + method @RequiresPermission(value=android.Manifest.permission.RECORD_AUDIO, conditional=true) public void start() throws java.lang.IllegalStateException; method public void stop() throws java.lang.IllegalStateException; method public void unregisterAudioRecordingCallback(@NonNull android.media.AudioManager.AudioRecordingCallback); field public static final int MEDIA_ERROR_SERVER_DIED = 100; // 0x64 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 35720fd17769..12f302a01a73 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -7354,7 +7354,15 @@ package android.media { public class AudioDeviceVolumeManager { method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public android.media.VolumeInfo getDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes); + method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE", android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes); method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes); + method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int); + field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3 + field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; // 0x5 + field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4 + field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2 + field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1 + field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0 } public final class AudioFocusInfo implements android.os.Parcelable { @@ -7399,7 +7407,7 @@ package android.media { method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups(); method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat); method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat); - method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE", android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes); + method @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE", android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public java.util.List<java.lang.Integer> getIndependentStreamTypes(); method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleStreamVolume(int); @@ -7445,7 +7453,7 @@ package android.media { method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setBluetoothVariableLatencyEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setDeviceAsNonDefaultForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes); - method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int); + method @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForCapturePreset(int, @NonNull android.media.AudioDeviceAttributes); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes); @@ -7465,12 +7473,12 @@ package android.media { field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2 field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int DEVICE_CONNECTION_STATE_CONNECTED = 1; // 0x1 field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int DEVICE_CONNECTION_STATE_DISCONNECTED = 0; // 0x0 - field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3 - field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; // 0x5 - field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4 - field public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2 - field public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1 - field public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0 + field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3 + field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; // 0x5 + field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4 + field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2 + field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1 + field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0 field public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"; field public static final String EXTRA_VOLUME_STREAM_VALUE = "android.media.EXTRA_VOLUME_STREAM_VALUE"; field public static final int FLAG_BLUETOOTH_ABS_VOLUME = 64; // 0x40 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 4c8283907712..daa1902edf02 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1955,6 +1955,10 @@ package android.media { method public static void enforceValidAudioDeviceTypeOut(int); } + public class AudioDeviceVolumeManager { + method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public boolean isFullVolumeDevice(); + } + public final class AudioFocusRequest { method @Nullable public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener(); } @@ -2010,7 +2014,6 @@ package android.media { method @NonNull public android.media.VolumePolicy getVolumePolicy(); method public boolean hasRegisteredDynamicPolicy(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean isCsdEnabled(); - method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public boolean isFullVolumeDevice(); method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean isVolumeControlUsingVolumeGroups(); method public void permissionUpdateBarrier(); diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index 6efc4ef55180..3003b79435e2 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -49,11 +49,14 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.compat.CompatChanges; import android.app.role.RoleManager; +import android.companion.virtual.VirtualDevice; +import android.companion.virtual.VirtualDeviceManager; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; import android.compat.annotation.EnabledAfter; import android.compat.annotation.Overridable; +import android.content.AttributionSource; import android.content.Context; import android.content.PermissionChecker; import android.content.pm.PackageManager; @@ -67,6 +70,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.permission.PermissionCheckerManager; +import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.text.TextUtils; import android.util.ArrayMap; @@ -1174,17 +1178,48 @@ public abstract class ForegroundServiceTypePolicy { @PackageManager.PermissionResult public int checkPermission(@NonNull Context context, int callerUid, int callerPid, String packageName, boolean allowWhileInUse) { - return checkPermission(context, mName, callerUid, callerPid, packageName, - allowWhileInUse); + int permissionResult = checkPermission(context, mName, callerUid, callerPid, + packageName, allowWhileInUse, Context.DEVICE_ID_DEFAULT); + + if (permissionResult == PERMISSION_GRANTED + || !PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(mName)) { + return permissionResult; + } + + // For device aware permissions, check if the permission is granted on any other + // active virtual device + VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class); + if (vdm == null) { + return permissionResult; + } + + final List<VirtualDevice> virtualDevices = vdm.getVirtualDevices(); + for (int i = 0, size = virtualDevices.size(); i < size; i++) { + final VirtualDevice virtualDevice = virtualDevices.get(i); + int resolvedDeviceId = PermissionManager.resolveDeviceIdForPermissionCheck( + context, virtualDevice.getDeviceId(), mName); + // we already checked on the default device context + if (resolvedDeviceId == Context.DEVICE_ID_DEFAULT) { + continue; + } + permissionResult = checkPermission(context, mName, callerUid, callerPid, + packageName, allowWhileInUse, resolvedDeviceId); + if (permissionResult == PERMISSION_GRANTED) { + break; + } + } + + return permissionResult; } @SuppressLint("AndroidFrameworkRequiresPermission") @PackageManager.PermissionResult int checkPermission(@NonNull Context context, @NonNull String name, int callerUid, - int callerPid, String packageName, boolean allowWhileInUse) { + int callerPid, String packageName, boolean allowWhileInUse, int deviceId) { + final AttributionSource attributionSource = new AttributionSource(callerUid, + packageName, null /*attributionTag*/, deviceId); @PermissionCheckerManager.PermissionResult final int result = - PermissionChecker.checkPermissionForPreflight(context, name, - callerPid, callerUid, packageName); + PermissionChecker.checkPermissionForPreflight(context, name, attributionSource); if (result == PERMISSION_HARD_DENIED) { // If the user didn't grant this permission at all. return PERMISSION_DENIED; @@ -1196,7 +1231,7 @@ public abstract class ForegroundServiceTypePolicy { ? PERMISSION_GRANTED : PERMISSION_DENIED; } final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - final int mode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid, packageName); + final int mode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, attributionSource); switch (mode) { case MODE_ALLOWED: // The appop is just allowed, plain and simple. diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 957482450893..7f1870bd2d87 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -248,7 +248,9 @@ public class WallpaperManager { /** * Command for {@link #sendWallpaperCommand}: reported by System UI when the device keyguard * starts going away. - * This command is triggered by {@link android.app.IActivityTaskManager#keyguardGoingAway(int)}. + * <p> + * This command is triggered by {@link android.app.IActivityTaskManager#keyguardGoingAway(int)} + * or by {@link android.app.IActivityTaskManager#setLockScreenShown(boolean, boolean)}. * * @hide */ @@ -256,6 +258,18 @@ public class WallpaperManager { "android.wallpaper.keyguardgoingaway"; /** + * Command for {@link #sendWallpaperCommand}: reported by System UI when the device keyguard + * starts going away. + * + * <p>This command is triggered by + * {@link android.app.IActivityTaskManager#setLockScreenShown(boolean, boolean)}. + * + * @hide + */ + public static final String COMMAND_KEYGUARD_APPEARING = + "android.wallpaper.keyguardappearing"; + + /** * Command for {@link #sendWallpaperCommand}: reported by System UI when the device is going to * sleep. The x and y arguments are a location (possibly very roughly) corresponding to the * action that caused the device to go to sleep. For example, if the power button was pressed, diff --git a/core/java/android/app/wallpaper.aconfig b/core/java/android/app/wallpaper.aconfig index 7aba172fad79..4a15a723da1a 100644 --- a/core/java/android/app/wallpaper.aconfig +++ b/core/java/android/app/wallpaper.aconfig @@ -24,6 +24,16 @@ flag { } flag { + name: "notify_keyguard_events" + namespace: "systemui" + description: "Send keyguard showing/hiding/going-away events to wallpaper as wallpaper commands (guarded by permission)" + bug: "395897130" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "accurate_wallpaper_downsampling" namespace: "systemui" description: "Accurate downsampling of wallpaper bitmap for high resolution images" diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 53203eba4020..c5412a982110 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -494,10 +494,14 @@ public class UserInfo implements Parcelable { // TODO(b/142482943): Make this logic more specific and customizable. (canHaveProfile(userType)) /* @hide */ public boolean canHaveProfile() { - if (isProfile() || isGuest() || isRestricted()) { + if (!isFull() || isProfile() || isGuest() || isRestricted() || isDemo()) { return false; } - return isMain(); + // NOTE: profiles used to be restricted just to the system user (and later to the main + // user), but from the framework point of view there is no need for such restriction, hence + // it's lifted + // TODO(b/374832167): check value of config_supportProfilesOnNonMainUser + return isMain() || android.multiuser.Flags.profilesForAll(); } // TODO(b/142482943): Get rid of this (after removing it from all tests) if feasible. diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 3411a4897e83..3dbd5b239ae5 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -646,3 +646,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "profiles_for_all" + namespace: "multiuser" + description: "Allows any regular user to have profiles" + bug: "374832167" +} diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index a7fbce51e9df..7dc6afba3f1c 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -188,24 +188,6 @@ public interface BiometricConstants { int BIOMETRIC_ERROR_CONTENT_VIEW_MORE_OPTIONS_BUTTON = 22; /** - * The error code returned after lock out error happens, the error dialog shows, and the users - * dismisses the dialog. This is a placeholder that is currently only used by the support - * library. - * - * @hide - */ - int BIOMETRIC_ERROR_LOCKOUT_ERROR_DIALOG_DISMISSED = 23; - - /** - * The error code returned after biometric hardware error happens, the error dialog shows, and - * the users dismisses the dialog.This is a placeholder that is currently only used by the - * support library. - * - * @hide - */ - int BIOMETRIC_ERROR_BIOMETRIC_HARDWARE_ERROR_DIALOG_DISMISSED = 24; - - /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. * @hide @@ -237,8 +219,6 @@ public interface BiometricConstants { BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE, BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS, BIOMETRIC_ERROR_CONTENT_VIEW_MORE_OPTIONS_BUTTON, - BIOMETRIC_ERROR_LOCKOUT_ERROR_DIALOG_DISMISSED, - BIOMETRIC_ERROR_BIOMETRIC_HARDWARE_ERROR_DIALOG_DISMISSED, BIOMETRIC_PAUSED_REJECTED}) @Retention(RetentionPolicy.SOURCE) @interface Errors {} diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index ee62dea7f9e5..6b1e918a3c47 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -149,6 +149,11 @@ public class Binder implements IBinder { private static volatile boolean sStackTrackingEnabled = false; /** + * The extension binder object + */ + private IBinder mExtension = null; + + /** * Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to * {@link TransactionTracker}. * @@ -1237,7 +1242,9 @@ public class Binder implements IBinder { /** @hide */ @Override - public final native @Nullable IBinder getExtension(); + public final @Nullable IBinder getExtension() { + return mExtension; + } /** * Set the binder extension. @@ -1245,7 +1252,12 @@ public class Binder implements IBinder { * * @hide */ - public final native void setExtension(@Nullable IBinder extension); + public final void setExtension(@Nullable IBinder extension) { + mExtension = extension; + setExtensionNative(extension); + } + + private final native void setExtensionNative(@Nullable IBinder extension); /** * Default implementation rewinds the parcels and calls onTransact. On diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 1b57b0045537..94e9aa709369 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1070,9 +1070,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } if (mSurfacePackage != null) { - mSurfaceControlViewHostParent.detach(); mEmbeddedWindowParams.clear(); if (releaseSurfacePackage) { + mSurfaceControlViewHostParent.detach(); mSurfacePackage.release(); mSurfacePackage = null; } diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 8151429f9139..f1c47a7a023b 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -760,9 +760,20 @@ public class BatteryStatsHistory { break; } - if (fragment.monotonicTimeMs >= startTimeMs && fragment != mActiveFragment) { - containers.add(new BatteryHistoryParcelContainer(fragment)); + if (fragment.monotonicTimeMs >= mHistoryBufferStartTime) { + // Do not include the backup of the current buffer, which is explicitly + // included later + continue; } + + if (i < fragments.size() - 1 + && fragments.get(i + 1).monotonicTimeMs < startTimeMs) { + // Since fragments are ordered, an early start of next fragment implies an + // early end for this one. + continue; + } + + containers.add(new BatteryHistoryParcelContainer(fragment)); } } diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index a0c8f30c9356..36bda61c94a6 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -73,6 +73,7 @@ static struct bindernative_offsets_t jmethodID mExecTransact; jmethodID mGetInterfaceDescriptor; jmethodID mTransactionCallback; + jmethodID mGetExtension; // Object state. jfieldID mObject; @@ -488,8 +489,12 @@ public: if (mVintf) { ::android::internal::Stability::markVintf(b.get()); } - if (mExtension != nullptr) { - b.get()->setExtension(mExtension); + if (mSetExtensionCalled) { + jobject javaIBinderObject = env->CallObjectMethod(obj, gBinderOffsets.mGetExtension); + sp<IBinder> extensionFromJava = ibinderForJavaObject(env, javaIBinderObject); + if (extensionFromJava != nullptr) { + b.get()->setExtension(extensionFromJava); + } } mBinder = b; ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n", @@ -515,21 +520,12 @@ public: mVintf = false; } - sp<IBinder> getExtension() { - AutoMutex _l(mLock); - sp<JavaBBinder> b = mBinder.promote(); - if (b != nullptr) { - return b.get()->getExtension(); - } - return mExtension; - } - void setExtension(const sp<IBinder>& extension) { AutoMutex _l(mLock); - mExtension = extension; + mSetExtensionCalled = true; sp<JavaBBinder> b = mBinder.promote(); if (b != nullptr) { - b.get()->setExtension(mExtension); + b.get()->setExtension(extension); } } @@ -541,8 +537,7 @@ private: // is too much binder state here, we can think about making JavaBBinder an // sp here (avoid recreating it) bool mVintf = false; - - sp<IBinder> mExtension; + bool mSetExtensionCalled = false; }; // ---------------------------------------------------------------------------- @@ -1254,10 +1249,6 @@ static void android_os_Binder_blockUntilThreadAvailable(JNIEnv* env, jobject cla return IPCThreadState::self()->blockUntilThreadAvailable(); } -static jobject android_os_Binder_getExtension(JNIEnv* env, jobject obj) { - JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject); - return javaObjectForIBinder(env, jbh->getExtension()); -} static void android_os_Binder_setExtension(JNIEnv* env, jobject obj, jobject extensionObject) { JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject); @@ -1300,8 +1291,7 @@ static const JNINativeMethod gBinderMethods[] = { { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder }, { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer }, { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }, - { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension }, - { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension }, + { "setExtensionNative", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension }, }; // clang-format on @@ -1318,6 +1308,8 @@ static int int_register_android_os_Binder(JNIEnv* env) gBinderOffsets.mTransactionCallback = GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V"); gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J"); + gBinderOffsets.mGetExtension = GetMethodIDOrDie(env, clazz, "getExtension", + "()Landroid/os/IBinder;"); return RegisterMethodsOrDie( env, kBinderPathName, diff --git a/core/tests/coretests/src/android/content/pm/UserInfoTest.java b/core/tests/coretests/src/android/content/pm/UserInfoTest.java index edeea6d85ca6..c84c21557ea4 100644 --- a/core/tests/coretests/src/android/content/pm/UserInfoTest.java +++ b/core/tests/coretests/src/android/content/pm/UserInfoTest.java @@ -16,19 +16,44 @@ package android.content.pm; +import static android.content.pm.UserInfo.FLAG_DEMO; +import static android.content.pm.UserInfo.FLAG_FULL; +import static android.content.pm.UserInfo.FLAG_GUEST; +import static android.content.pm.UserInfo.FLAG_MAIN; +import static android.content.pm.UserInfo.FLAG_PROFILE; +import static android.content.pm.UserInfo.FLAG_SYSTEM; +import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED; +import static android.os.UserManager.USER_TYPE_FULL_SYSTEM; +import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS; + import static com.google.common.truth.Truth.assertThat; +import android.content.pm.UserInfo.UserInfoFlag; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.google.common.truth.Expect; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; + @RunWith(AndroidJUnit4.class) @SmallTest -public class UserInfoTest { +public final class UserInfoTest { + + @Rule + public final SetFlagsRule flags = + new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + + @Rule public final Expect expect = Expect.create(); + @Test public void testSimple() throws Exception { final UserInfo ui = new UserInfo(10, "Test", UserInfo.FLAG_GUEST); @@ -56,9 +81,6 @@ public class UserInfoTest { assertThat(ui.isInitialized()).isEqualTo(false); assertThat(ui.isFull()).isEqualTo(false); assertThat(ui.isMain()).isEqualTo(false); - - // Derived dynamically - assertThat(ui.canHaveProfile()).isEqualTo(false); } @Test @@ -68,4 +90,64 @@ public class UserInfoTest { assertThat(ui.toString()).isNotEmpty(); assertThat(ui.toFullString()).isNotEmpty(); } + + @Test + @DisableFlags(android.multiuser.Flags.FLAG_PROFILES_FOR_ALL) + public void testCanHaveProfile_flagProfilesForAllDisabled() { + expectCannotHaveProfile("non-full user", createTestUserInfo(/* flags= */ 0)); + expectCannotHaveProfile("guest user", createTestUserInfo(FLAG_FULL | FLAG_GUEST)); + expectCanHaveProfile("main user", createTestUserInfo(FLAG_FULL | FLAG_MAIN)); + expectCannotHaveProfile("non-main user", createTestUserInfo(FLAG_FULL)); + expectCannotHaveProfile("demo user", createTestUserInfo(FLAG_FULL | FLAG_DEMO)); + expectCannotHaveProfile("restricted user", + createTestUserInfo(USER_TYPE_FULL_RESTRICTED, FLAG_FULL)); + expectCannotHaveProfile("profile user", createTestUserInfo(FLAG_PROFILE)); + expectCanHaveProfile("(full) system user that's also main user", + createTestUserInfo(USER_TYPE_FULL_SYSTEM, FLAG_FULL | FLAG_SYSTEM | FLAG_MAIN)); + expectCannotHaveProfile("headless system user that's not main user", + createTestUserInfo(USER_TYPE_SYSTEM_HEADLESS, FLAG_SYSTEM)); + } + + @Test + @EnableFlags(android.multiuser.Flags.FLAG_PROFILES_FOR_ALL) + public void testCanHaveProfile_flagProfilesForAllEnabled() { + expectCannotHaveProfile("non-full user", createTestUserInfo(/* flags= */ 0)); + expectCannotHaveProfile("guest user", createTestUserInfo(FLAG_FULL | FLAG_GUEST)); + expectCanHaveProfile("main user", createTestUserInfo(FLAG_FULL | FLAG_MAIN)); + expectCanHaveProfile("non-main user", createTestUserInfo(FLAG_FULL)); + expectCannotHaveProfile("demo user", createTestUserInfo(FLAG_FULL | FLAG_DEMO)); + expectCannotHaveProfile("restricted user", + createTestUserInfo(USER_TYPE_FULL_RESTRICTED, FLAG_FULL)); + expectCannotHaveProfile("profile user", createTestUserInfo(FLAG_PROFILE)); + expectCanHaveProfile("(full) system user that's also main user", + createTestUserInfo(USER_TYPE_FULL_SYSTEM, FLAG_FULL | FLAG_SYSTEM | FLAG_MAIN)); + expectCannotHaveProfile("headless system user that's not main user", + createTestUserInfo(USER_TYPE_SYSTEM_HEADLESS, FLAG_SYSTEM)); + } + + /** + * Creates a new {@link UserInfo} with id {@code 10}, name {@code Test}, and the given + * {@code flags}. + */ + private UserInfo createTestUserInfo(@UserInfoFlag int flags) { + return new UserInfo(10, "Test", flags); + } + + /** + * Creates a new {@link UserInfo} with id {@code 10}, name {@code Test}, and the given + * {@code userType} and {@code flags}. + */ + private UserInfo createTestUserInfo(String userType, @UserInfoFlag int flags) { + return new UserInfo(10, "Test", /* iconPath= */ null, flags, userType); + } + + private void expectCanHaveProfile(String description, UserInfo user) { + expect.withMessage("canHaveProfile() on %s (%s)", description, user) + .that(user.canHaveProfile()).isTrue(); + } + + private void expectCannotHaveProfile(String description, UserInfo user) { + expect.withMessage("canHaveProfile() on %s (%s)", description, user) + .that(user.canHaveProfile()).isFalse(); + } } diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index b6a1501831c0..19455a313a9d 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -211,3 +211,10 @@ flag { description: "Makes the split divider snap 'magnetically' to available snap points during drag" bug: "383631946" } + +flag { + name: "enable_dynamic_insets_for_app_launch" + namespace: "multitasking" + description: "Enables dynamic insets for app launch so the window is properly cropped" + bug: "336511494" +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java index 25b9f8ccc6ae..f68afea92850 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java @@ -18,6 +18,7 @@ package com.android.wm.shell.shared; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; +import static android.view.Display.INVALID_DISPLAY; import android.annotation.IntDef; import android.app.ActivityManager.RecentTaskInfo; @@ -65,6 +66,11 @@ public class GroupedTaskInfo implements Parcelable { private final int mDeskId; /** + * The ID of the display that desk with [mDeskId] is in. + */ + private final int mDeskDisplayId; + + /** * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or * TYPE_DESK. */ @@ -109,17 +115,19 @@ public class GroupedTaskInfo implements Parcelable { * Create new for a stack of fullscreen tasks */ public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) { - return new GroupedTaskInfo(/* deskId = */ -1, List.of(task), null, TYPE_FULLSCREEN, - /* minimizedFreeformTaskIds = */ null); + return new GroupedTaskInfo(/* deskId = */ -1, /* displayId = */ INVALID_DISPLAY, + List.of(task), null, + TYPE_FULLSCREEN, /* minimizedFreeformTaskIds = */ null); } /** * Create new for a pair of tasks in split screen */ public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1, - @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) { - return new GroupedTaskInfo(/* deskId = */ -1, List.of(task1, task2), splitBounds, - TYPE_SPLIT, /* minimizedFreeformTaskIds = */ null); + @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) { + return new GroupedTaskInfo(/* deskId = */ -1, /* displayId = */ INVALID_DISPLAY, + List.of(task1, task2), + splitBounds, TYPE_SPLIT, /* minimizedFreeformTaskIds = */ null); } /** @@ -127,9 +135,11 @@ public class GroupedTaskInfo implements Parcelable { */ public static GroupedTaskInfo forDeskTasks( int deskId, + int deskDisplayId, @NonNull List<TaskInfo> tasks, @NonNull Set<Integer> minimizedFreeformTaskIds) { - return new GroupedTaskInfo(deskId, tasks, /* splitBounds = */ null, TYPE_DESK, + return new GroupedTaskInfo(deskId, deskDisplayId, tasks, /* splitBounds = */ null, + TYPE_DESK, minimizedFreeformTaskIds.stream().mapToInt(i -> i).toArray()); } @@ -149,11 +159,13 @@ public class GroupedTaskInfo implements Parcelable { private GroupedTaskInfo( int deskId, + int deskDisplayId, @NonNull List<TaskInfo> tasks, @Nullable SplitBounds splitBounds, @GroupType int type, @Nullable int[] minimizedFreeformTaskIds) { mDeskId = deskId; + mDeskDisplayId = deskDisplayId; mTasks = tasks; mGroupedTasks = null; mSplitBounds = splitBounds; @@ -164,6 +176,7 @@ public class GroupedTaskInfo implements Parcelable { private GroupedTaskInfo(@NonNull List<GroupedTaskInfo> groupedTasks) { mDeskId = -1; + mDeskDisplayId = INVALID_DISPLAY; mTasks = null; mGroupedTasks = groupedTasks; mSplitBounds = null; @@ -185,6 +198,7 @@ public class GroupedTaskInfo implements Parcelable { protected GroupedTaskInfo(@NonNull Parcel parcel) { mDeskId = parcel.readInt(); + mDeskDisplayId = parcel.readInt(); mTasks = new ArrayList(); final int numTasks = parcel.readInt(); for (int i = 0; i < numTasks; i++) { @@ -295,6 +309,16 @@ public class GroupedTaskInfo implements Parcelable { } /** + * Returns the ID of the display that hosts the desk represented by [mDeskId]. + */ + public int getDeskDisplayId() { + if (mType != TYPE_DESK) { + throw new IllegalStateException("No display ID for non desktop task"); + } + return mDeskDisplayId; + } + + /** * Get type of this recents entry. One of {@link GroupType}. * Note: This is deprecated, callers should use `isBaseType()` and not make assumptions about * specific group types @@ -323,6 +347,7 @@ public class GroupedTaskInfo implements Parcelable { } GroupedTaskInfo other = (GroupedTaskInfo) obj; return mDeskId == other.mDeskId + && mDeskDisplayId == other.mDeskDisplayId && mType == other.mType && Objects.equals(mTasks, other.mTasks) && Objects.equals(mGroupedTasks, other.mGroupedTasks) @@ -332,7 +357,7 @@ public class GroupedTaskInfo implements Parcelable { @Override public int hashCode() { - return Objects.hash(mDeskId, mType, mTasks, mGroupedTasks, mSplitBounds, + return Objects.hash(mDeskId, mDeskDisplayId, mType, mTasks, mGroupedTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds)); } @@ -345,6 +370,7 @@ public class GroupedTaskInfo implements Parcelable { .collect(Collectors.joining(",\n\t", "[\n\t", "\n]"))); } else { taskString.append("Desk ID= ").append(mDeskId).append(", "); + taskString.append("Desk Display ID=").append(mDeskDisplayId).append(", "); taskString.append("Tasks=" + mTasks.stream() .map(taskInfo -> getTaskInfoDumpString(taskInfo)) .collect(Collectors.joining(", ", "[", "]"))); @@ -377,6 +403,7 @@ public class GroupedTaskInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mDeskId); + parcel.writeInt(mDeskDisplayId); // We don't use the parcel list methods because we want to only write the TaskInfo state // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated final int tasksSize = mTasks != null ? mTasks.size() : 0; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index bb5b5cec1b4a..382fa9640ff9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -536,17 +536,20 @@ public class RecentTasksController implements TaskStackListenerCallback, } /** - * Represents a desk whose ID is `mDeskId` and contains the tasks in `mDeskTasks`. Some of these - * tasks are minimized and their IDs are contained in the `mMinimizedDeskTasks` set. + * Represents a desk whose ID is `mDeskId` inside the display with `mDisplayId` and contains + * the tasks in `mDeskTasks`. Some of these tasks are minimized and their IDs are contained + * in the `mMinimizedDeskTasks` set. */ private static class Desk { final int mDeskId; + final int mDisplayId; boolean mHasVisibleTasks = false; final ArrayList<TaskInfo> mDeskTasks = new ArrayList<>(); final Set<Integer> mMinimizedDeskTasks = new HashSet<>(); - Desk(int deskId) { + Desk(int deskId, int displayId) { mDeskId = deskId; + mDisplayId = displayId; } void addTask(TaskInfo taskInfo, boolean isMinimized, boolean isVisible) { @@ -562,7 +565,8 @@ public class RecentTasksController implements TaskStackListenerCallback, } GroupedTaskInfo createDeskTaskInfo() { - return GroupedTaskInfo.forDeskTasks(mDeskId, mDeskTasks, mMinimizedDeskTasks); + return GroupedTaskInfo.forDeskTasks(mDeskId, mDisplayId, mDeskTasks, + mMinimizedDeskTasks); } } @@ -601,7 +605,8 @@ public class RecentTasksController implements TaskStackListenerCallback, private Desk getOrCreateDesk(int deskId) { var desk = mTmpDesks.get(deskId); if (desk == null) { - desk = new Desk(deskId); + desk = new Desk(deskId, + mDesktopUserRepositories.get().getCurrent().getDisplayForDesk(deskId)); mTmpDesks.put(deskId, desk); } return desk; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 5e8c1fe2aa8d..e08ef5883390 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -49,6 +49,7 @@ import android.view.ViewConfiguration; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.window.DesktopExperienceFlags; import android.window.DesktopModeFlags; import android.window.WindowContainerTransaction; @@ -218,11 +219,17 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL relayoutParams.mRunningTaskInfo = taskInfo; relayoutParams.mLayoutResId = R.layout.caption_window_decor; relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); - relayoutParams.mShadowRadius = hasGlobalFocus - ? context.getResources().getDimensionPixelSize( - R.dimen.freeform_decor_shadow_focused_thickness) - : context.getResources().getDimensionPixelSize( - R.dimen.freeform_decor_shadow_unfocused_thickness); + if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) { + relayoutParams.mShadowRadiusId = hasGlobalFocus + ? R.dimen.freeform_decor_shadow_focused_thickness + : R.dimen.freeform_decor_shadow_unfocused_thickness; + } else { + relayoutParams.mShadowRadius = hasGlobalFocus + ? context.getResources().getDimensionPixelSize( + R.dimen.freeform_decor_shadow_focused_thickness) + : context.getResources().getDimensionPixelSize( + R.dimen.freeform_decor_shadow_unfocused_thickness); + } relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop; relayoutParams.mIsCaptionVisible = taskInfo.isFreeform() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 989550c60c0d..e8019e47e374 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -72,6 +72,7 @@ import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.widget.ImageButton; +import android.window.DesktopExperienceFlags; import android.window.DesktopModeFlags; import android.window.TaskSnapshot; import android.window.WindowContainerTransaction; @@ -719,7 +720,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .getScaledTouchSlop(); final Resources res = mResult.mRootView.getResources(); final DragResizeWindowGeometry newGeometry = new DragResizeWindowGeometry( - mRelayoutParams.mCornerRadius, + DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue() + ? mResult.mCornerRadius : mRelayoutParams.mCornerRadius, new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res), getFineResizeCornerSize(res), getLargeResizeCornerSize(res), @@ -1072,13 +1074,23 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } if (isAppHeader && DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) { - relayoutParams.mShadowRadius = hasGlobalFocus - ? context.getResources().getDimensionPixelSize( - R.dimen.freeform_decor_shadow_focused_thickness) - : context.getResources().getDimensionPixelSize( - R.dimen.freeform_decor_shadow_unfocused_thickness); + if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) { + relayoutParams.mShadowRadiusId = hasGlobalFocus + ? R.dimen.freeform_decor_shadow_focused_thickness + : R.dimen.freeform_decor_shadow_unfocused_thickness; + } else { + relayoutParams.mShadowRadius = hasGlobalFocus + ? context.getResources().getDimensionPixelSize( + R.dimen.freeform_decor_shadow_focused_thickness) + : context.getResources().getDimensionPixelSize( + R.dimen.freeform_decor_shadow_unfocused_thickness); + } } else { - relayoutParams.mShadowRadius = INVALID_SHADOW_RADIUS; + if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) { + relayoutParams.mShadowRadiusId = Resources.ID_NULL; + } else { + relayoutParams.mShadowRadius = INVALID_SHADOW_RADIUS; + } } relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop; @@ -1104,8 +1116,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mWindowDecorConfig = windowDecorConfig; if (DesktopModeStatus.useRoundedCorners()) { - relayoutParams.mCornerRadius = shouldIgnoreCornerRadius ? INVALID_CORNER_RADIUS : - getCornerRadius(context, relayoutParams.mLayoutResId); + if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) { + relayoutParams.mCornerRadiusId = shouldIgnoreCornerRadius ? Resources.ID_NULL : + getCornerRadiusId(relayoutParams.mLayoutResId); + } else { + relayoutParams.mCornerRadius = shouldIgnoreCornerRadius ? INVALID_CORNER_RADIUS : + getCornerRadius(context, relayoutParams.mLayoutResId); + } } // Set opaque background for all freeform tasks to prevent freeform tasks below // from being visible if freeform task window above is translucent. @@ -1113,6 +1130,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mShouldSetBackground = DesktopModeStatus.shouldSetBackground(taskInfo); } + @Deprecated private static int getCornerRadius(@NonNull Context context, int layoutResId) { if (layoutResId == R.layout.desktop_mode_app_header) { return loadDimensionPixelSize(context.getResources(), @@ -1122,6 +1140,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return INVALID_CORNER_RADIUS; } + private static int getCornerRadiusId(int layoutResId) { + if (layoutResId == R.layout.desktop_mode_app_header) { + return com.android.wm.shell.shared.R.dimen + .desktop_windowing_freeform_rounded_corner_radius; + } + return Resources.ID_NULL; + } + /** * If task has focused window decor, return the caption id of the fullscreen caption size * resource. Otherwise, return ID_NULL and caption width be set to task width. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt index eb324f74ca82..238242792782 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt @@ -278,13 +278,16 @@ class MultiDisplayVeiledResizeTaskPositioner( currentDisplayLayout, ) ) - - multiDisplayDragMoveIndicatorController.onDragEnd( - desktopWindowDecoration.mTaskInfo.taskId, - transactionSupplier, - ) } + // Call the MultiDisplayDragMoveIndicatorController to clear any active indicator + // surfaces. This is necessary even if the drag ended on the same display, as surfaces + // may have been created for other displays during the drag. + multiDisplayDragMoveIndicatorController.onDragEnd( + desktopWindowDecoration.mTaskInfo.taskId, + transactionSupplier, + ) + interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 91a899c09407..6fd963f4203d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -47,6 +47,7 @@ import android.view.SurfaceControlViewHost; import android.view.View; import android.view.WindowManager; import android.view.WindowlessWindowManager; +import android.window.DesktopExperienceFlags; import android.window.SurfaceSyncGroup; import android.window.TaskConstants; import android.window.WindowContainerToken; @@ -286,6 +287,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2; outResult.mCaptionY = 0; outResult.mCaptionTopPadding = params.mCaptionTopPadding; + if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) { + outResult.mCornerRadius = params.mCornerRadiusId == Resources.ID_NULL + ? INVALID_CORNER_RADIUS : loadDimensionPixelSize(resources, + params.mCornerRadiusId); + outResult.mShadowRadius = params.mShadowRadiusId == Resources.ID_NULL + ? INVALID_SHADOW_RADIUS : loadDimensionPixelSize(resources, + params.mShadowRadiusId); + } Trace.beginSection("relayout-createViewHostIfNeeded"); createViewHostIfNeeded(mDecorWindowContext, mDisplay); @@ -497,9 +506,16 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setPosition(mTaskSurface, taskPosition.x, taskPosition.y); } - if (params.mShadowRadius != INVALID_SHADOW_RADIUS) { - startT.setShadowRadius(mTaskSurface, params.mShadowRadius); - finishT.setShadowRadius(mTaskSurface, params.mShadowRadius); + if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) { + if (outResult.mShadowRadius != INVALID_SHADOW_RADIUS) { + startT.setShadowRadius(mTaskSurface, outResult.mShadowRadius); + finishT.setShadowRadius(mTaskSurface, outResult.mShadowRadius); + } + } else { + if (params.mShadowRadius != INVALID_SHADOW_RADIUS) { + startT.setShadowRadius(mTaskSurface, params.mShadowRadius); + finishT.setShadowRadius(mTaskSurface, params.mShadowRadius); + } } if (params.mSetTaskVisibilityPositionAndCrop) { @@ -517,9 +533,16 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> startT.unsetColor(mTaskSurface); } - if (params.mCornerRadius != INVALID_CORNER_RADIUS) { - startT.setCornerRadius(mTaskSurface, params.mCornerRadius); - finishT.setCornerRadius(mTaskSurface, params.mCornerRadius); + if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) { + if (outResult.mCornerRadius != INVALID_CORNER_RADIUS) { + startT.setCornerRadius(mTaskSurface, outResult.mCornerRadius); + finishT.setCornerRadius(mTaskSurface, outResult.mCornerRadius); + } + } else { + if (params.mCornerRadius != INVALID_CORNER_RADIUS) { + startT.setCornerRadius(mTaskSurface, params.mCornerRadius); + finishT.setCornerRadius(mTaskSurface, params.mCornerRadius); + } } } @@ -824,9 +847,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> @InsetsSource.Flags int mInsetSourceFlags; final Region mDisplayExclusionRegion = Region.obtain(); + @Deprecated int mShadowRadius = INVALID_SHADOW_RADIUS; + @Deprecated int mCornerRadius = INVALID_CORNER_RADIUS; + int mShadowRadiusId = Resources.ID_NULL; + int mCornerRadiusId = Resources.ID_NULL; + int mCaptionTopPadding; boolean mIsCaptionVisible; @@ -849,9 +877,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mIsInsetSource = true; mInsetSourceFlags = 0; mDisplayExclusionRegion.setEmpty(); - - mShadowRadius = INVALID_SHADOW_RADIUS; - mCornerRadius = INVALID_SHADOW_RADIUS; + if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) { + mShadowRadiusId = Resources.ID_NULL; + mCornerRadiusId = Resources.ID_NULL; + } else { + mShadowRadius = INVALID_SHADOW_RADIUS; + mCornerRadius = INVALID_SHADOW_RADIUS; + } mCaptionTopPadding = 0; mIsCaptionVisible = false; @@ -893,6 +925,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mWidth; int mHeight; T mRootView; + int mCornerRadius; + int mShadowRadius; void reset() { mWidth = 0; @@ -904,6 +938,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mCaptionTopPadding = 0; mCustomizableCaptionRegion.setEmpty(); mRootView = null; + if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) { + mCornerRadius = INVALID_CORNER_RADIUS; + mShadowRadius = INVALID_SHADOW_RADIUS; + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml index aa1b24189274..33ea0baa4f6d 100644 --- a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml +++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml @@ -18,6 +18,8 @@ <!-- Resources used in WindowDecorationTests --> <dimen name="test_freeform_decor_caption_height">32dp</dimen> <dimen name="test_freeform_decor_caption_menu_width">216dp</dimen> + <dimen name="test_freeform_shadow_radius">20dp</dimen> + <dimen name="test_freeform_corner_radius">16dp</dimen> <dimen name="test_window_decor_left_outset">10dp</dimen> <dimen name="test_window_decor_top_outset">20dp</dimen> <dimen name="test_window_decor_right_outset">30dp</dimen> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt index 75f6bda4d750..4e8812d34ef4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt @@ -21,6 +21,7 @@ import android.app.TaskInfo import android.graphics.Rect import android.os.Parcel import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY import android.window.IWindowContainerToken import android.window.WindowContainerToken import androidx.test.filters.SmallTest @@ -281,7 +282,8 @@ class GroupedTaskInfoTest : ShellTestCase() { val task2 = createTaskInfo(id = 2) val taskInfo = GroupedTaskInfo.forDeskTasks( - /* deskId = */ 500, listOf(task1, task2), setOf()) + /* deskId = */ 500, DEFAULT_DISPLAY, listOf(task1, task2), setOf() + ) assertThat(taskInfo.deskId).isEqualTo(500) assertThat(taskInfo.getTaskById(1)).isEqualTo(task1) @@ -335,6 +337,7 @@ class GroupedTaskInfoTest : ShellTestCase() { ): GroupedTaskInfo { return GroupedTaskInfo.forDeskTasks( deskId, + DEFAULT_DISPLAY, freeformTaskIds.map { createTaskInfo(it) }.toList(), minimizedTaskIds.toSet()) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index f37f2fb14bea..f7b9c3352dea 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -59,6 +59,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Point; import android.graphics.Rect; @@ -341,7 +342,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreSetForFreeform() { + @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreSetForFreeform_dynamicDisabled() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); RelayoutParams relayoutParams = new RelayoutParams(); @@ -353,7 +355,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForFullscreen() { + @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForFullscreen_dynamicDisabled() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); RelayoutParams relayoutParams = new RelayoutParams(); @@ -364,7 +367,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForSplit() { + @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForSplit_dynamicDisabled() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); RelayoutParams relayoutParams = new RelayoutParams(); @@ -375,7 +379,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersSetForFreeform() { + @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersSetForFreeform_dynamicDisabled() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); fillRoundedCornersResources(/* fillValue= */ 30); @@ -387,7 +392,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForFullscreen() { + @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForFullscreen_dynamicDisabled() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); fillRoundedCornersResources(/* fillValue= */ 30); @@ -399,7 +405,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForSplit() { + @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForSplit_dynamicDisabled() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); fillRoundedCornersResources(/* fillValue= */ 30); @@ -411,7 +418,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet() { + @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet_dynamicDisabled() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); fillRoundedCornersResources(/* fillValue= */ 30); @@ -440,6 +448,107 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreSetForFreeform() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + RelayoutParams relayoutParams = new RelayoutParams(); + + updateRelayoutParams(relayoutParams, taskInfo); + + assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForFullscreen() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + RelayoutParams relayoutParams = new RelayoutParams(); + + updateRelayoutParams(relayoutParams, taskInfo); + + assertThat(relayoutParams.mShadowRadiusId).isEqualTo(Resources.ID_NULL); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForSplit() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + RelayoutParams relayoutParams = new RelayoutParams(); + + updateRelayoutParams(relayoutParams, taskInfo); + + assertThat(relayoutParams.mShadowRadiusId).isEqualTo(Resources.ID_NULL); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersSetForFreeform() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + RelayoutParams relayoutParams = new RelayoutParams(); + + updateRelayoutParams(relayoutParams, taskInfo); + + assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForFullscreen() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + RelayoutParams relayoutParams = new RelayoutParams(); + + updateRelayoutParams(relayoutParams, taskInfo); + + assertThat(relayoutParams.mCornerRadiusId).isEqualTo(Resources.ID_NULL); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForSplit() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + RelayoutParams relayoutParams = new RelayoutParams(); + + updateRelayoutParams(relayoutParams, taskInfo); + + assertThat(relayoutParams.mCornerRadiusId).isEqualTo(Resources.ID_NULL); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX) + public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + mMockSplitScreenController, + DEFAULT_APPLY_START_TRANSACTION_ON_DRAW, + DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP, + DEFAULT_IS_STATUSBAR_VISIBLE, + DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, + DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, + new InsetsState(), + DEFAULT_HAS_GLOBAL_FOCUS, + mExclusionRegion, + /* shouldIgnoreCornerRadius= */ true, + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS, + DEFAULT_IS_RECENTS_TRANSITION_RUNNING, + DEFAULT_IS_MOVING_TO_BACK); + + assertThat(relayoutParams.mCornerRadiusId).isEqualTo(Resources.ID_NULL); + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY) public void updateRelayoutParams_appHeader_usesTaskDensity() { final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt index 0798613ed632..24a46aacde15 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt @@ -63,6 +63,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoInteractions import org.mockito.Mockito.`when` import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -210,6 +211,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { eq(taskPositioner), ) verify(mockDesktopWindowDecoration, never()).hideResizeVeil() + verifyNoInteractions(mockMultiDisplayDragMoveIndicatorController) } @Test @@ -248,6 +250,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { verify(mockDesktopWindowDecoration, never()).showResizeVeil(any()) verify(mockDesktopWindowDecoration, never()).hideResizeVeil() + verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any()) Assert.assertEquals(rectAfterEnd, endBounds) } @@ -268,6 +271,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { verify(spyDisplayLayout0, never()).localPxToGlobalDp(any(), any()) verify(spyDisplayLayout0, never()).globalDpToLocalPx(any(), any()) + verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any()) } @Test @@ -290,6 +294,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { verify(mockDesktopWindowDecoration, never()).showResizeVeil(any()) verify(mockDesktopWindowDecoration, never()).hideResizeVeil() + verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any()) Assert.assertEquals(rectAfterEnd, endBounds) } @@ -346,6 +351,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { }, eq(taskPositioner), ) + verifyNoInteractions(mockMultiDisplayDragMoveIndicatorController) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 2e95a979220c..c691dc72b1ea 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -61,7 +61,8 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.Handler; import android.os.LocaleList; -import android.testing.AndroidTestingRunner; +import android.platform.test.annotations.UsesFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.util.DisplayMetrics; import android.view.AttachedSurfaceControl; import android.view.Display; @@ -78,6 +79,7 @@ import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; @@ -96,6 +98,9 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -108,7 +113,8 @@ import java.util.function.Supplier; * atest WMShellUnitTests:WindowDecorationTests */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(ParameterizedAndroidJunit4.class) +@UsesFlags(com.android.window.flags.Flags.class) public class WindowDecorationTests extends ShellTestCase { private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400); private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60); @@ -116,6 +122,12 @@ public class WindowDecorationTests extends ShellTestCase { private static final int SHADOW_RADIUS = 10; private static final int STATUS_BAR_INSET_SOURCE_ID = 0; + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf( + Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX); + } + private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult = new WindowDecoration.RelayoutResult<>(); @@ -156,6 +168,10 @@ public class WindowDecorationTests extends ShellTestCase { private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams(); private int mCaptionMenuWidthId; + public WindowDecorationTests(FlagsParameterization flags) { + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() { mMockSurfaceControlStartT = createMockSurfaceControlTransaction(); @@ -165,8 +181,13 @@ public class WindowDecorationTests extends ShellTestCase { mRelayoutParams.mLayoutResId = 0; mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height; mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width; - mRelayoutParams.mShadowRadius = SHADOW_RADIUS; - mRelayoutParams.mCornerRadius = CORNER_RADIUS; + if (Flags.enableDynamicRadiusComputationBugfix()) { + mRelayoutParams.mShadowRadiusId = R.dimen.test_freeform_shadow_radius; + mRelayoutParams.mCornerRadiusId = R.dimen.test_freeform_corner_radius; + } else { + mRelayoutParams.mShadowRadius = SHADOW_RADIUS; + mRelayoutParams.mCornerRadius = CORNER_RADIUS; + } when(mMockDisplayController.getDisplay(Display.DEFAULT_DISPLAY)) .thenReturn(mock(Display.class)); @@ -282,9 +303,21 @@ public class WindowDecorationTests extends ShellTestCase { any(), anyInt()); - verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS); - verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS); - verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, SHADOW_RADIUS); + if (Flags.enableDynamicRadiusComputationBugfix()) { + final int cornerRadius = WindowDecoration.loadDimensionPixelSize( + windowDecor.mDecorWindowContext.getResources(), + mRelayoutParams.mCornerRadiusId); + verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, cornerRadius); + verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, cornerRadius); + final int shadowRadius = WindowDecoration.loadDimensionPixelSize( + windowDecor.mDecorWindowContext.getResources(), + mRelayoutParams.mShadowRadiusId); + verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, shadowRadius); + } else { + verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS); + verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS); + verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, SHADOW_RADIUS); + } assertEquals(300, mRelayoutResult.mWidth); assertEquals(100, mRelayoutResult.mHeight); @@ -1198,7 +1231,8 @@ public class WindowDecorationTests extends ShellTestCase { } @Override - public void setTaskFocusState(boolean focused) {} + public void setTaskFocusState(boolean focused) { + } } private class TestWindowDecoration extends WindowDecoration<TestView> { diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index a892e887bd43..ab1be7e6128d 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -139,6 +139,7 @@ cc_defaults { "libandroidfw", "libcrypto", "libsync", + "libgui", "libui", "aconfig_text_flags_c_lib", "aconfig_view_accessibility_flags_c_lib", diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp index 9d16ee86739e..7e1eb70ca02b 100644 --- a/libs/hwui/WebViewFunctorManager.cpp +++ b/libs/hwui/WebViewFunctorManager.cpp @@ -16,6 +16,7 @@ #include "WebViewFunctorManager.h" +#include <gui/SurfaceComposerClient.h> #include <log/log.h> #include <private/hwui/WebViewFunctor.h> #include <utils/Trace.h> @@ -43,7 +44,7 @@ public: static ASurfaceControl* getSurfaceControl() { ALOG_ASSERT(sCurrentFunctor); - return sCurrentFunctor->getSurfaceControl(); + return reinterpret_cast<ASurfaceControl*>(sCurrentFunctor->getSurfaceControl()); } static void mergeTransaction(ASurfaceTransaction* transaction) { ALOG_ASSERT(sCurrentFunctor); @@ -129,12 +130,12 @@ bool WebViewFunctor::prepareRootSurfaceControl() { renderthread::CanvasContext* activeContext = renderthread::CanvasContext::getActiveContext(); if (!activeContext) return false; - ASurfaceControl* rootSurfaceControl = activeContext->getSurfaceControl(); + sp<SurfaceControl> rootSurfaceControl = activeContext->getSurfaceControl(); if (!rootSurfaceControl) return false; int32_t rgid = activeContext->getSurfaceControlGenerationId(); if (mParentSurfaceControlGenerationId != rgid) { - reparentSurfaceControl(rootSurfaceControl); + reparentSurfaceControl(reinterpret_cast<ASurfaceControl*>(rootSurfaceControl.get())); mParentSurfaceControlGenerationId = rgid; } @@ -210,33 +211,35 @@ void WebViewFunctor::removeOverlays() { mCallbacks.removeOverlays(mFunctor, mData, currentFunctor.mergeTransaction); if (mSurfaceControl) { reparentSurfaceControl(nullptr); - auto funcs = renderthread::RenderThread::getInstance().getASurfaceControlFunctions(); - funcs.releaseFunc(mSurfaceControl); mSurfaceControl = nullptr; } } ASurfaceControl* WebViewFunctor::getSurfaceControl() { ATRACE_NAME("WebViewFunctor::getSurfaceControl"); - if (mSurfaceControl != nullptr) return mSurfaceControl; + if (mSurfaceControl != nullptr) { + return reinterpret_cast<ASurfaceControl*>(mSurfaceControl.get()); + } renderthread::CanvasContext* activeContext = renderthread::CanvasContext::getActiveContext(); LOG_ALWAYS_FATAL_IF(activeContext == nullptr, "Null active canvas context!"); - ASurfaceControl* rootSurfaceControl = activeContext->getSurfaceControl(); + sp<SurfaceControl> rootSurfaceControl = activeContext->getSurfaceControl(); LOG_ALWAYS_FATAL_IF(rootSurfaceControl == nullptr, "Null root surface control!"); - auto funcs = renderthread::RenderThread::getInstance().getASurfaceControlFunctions(); mParentSurfaceControlGenerationId = activeContext->getSurfaceControlGenerationId(); - mSurfaceControl = funcs.createFunc(rootSurfaceControl, "Webview Overlay SurfaceControl"); - ASurfaceTransaction* transaction = funcs.transactionCreateFunc(); + + SurfaceComposerClient* client = rootSurfaceControl->getClient().get(); + mSurfaceControl = client->createSurface( + String8("Webview Overlay SurfaceControl"), 0 /* width */, 0 /* height */, + // Format is only relevant for buffer queue layers. + PIXEL_FORMAT_UNKNOWN /* format */, ISurfaceComposerClient::eFXSurfaceBufferState, + rootSurfaceControl->getHandle()); + activeContext->prepareSurfaceControlForWebview(); - funcs.transactionSetZOrderFunc(transaction, mSurfaceControl, -1); - funcs.transactionSetVisibilityFunc(transaction, mSurfaceControl, - ASURFACE_TRANSACTION_VISIBILITY_SHOW); - funcs.transactionApplyFunc(transaction); - funcs.transactionDeleteFunc(transaction); - return mSurfaceControl; + SurfaceComposerClient::Transaction transaction; + transaction.setLayer(mSurfaceControl, -1).show(mSurfaceControl).apply(); + return reinterpret_cast<ASurfaceControl*>(mSurfaceControl.get()); } void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) { @@ -249,8 +252,7 @@ void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) { done = activeContext->mergeTransaction(transaction, mSurfaceControl); } if (!done) { - auto funcs = renderthread::RenderThread::getInstance().getASurfaceControlFunctions(); - funcs.transactionApplyFunc(transaction); + reinterpret_cast<SurfaceComposerClient::Transaction*>(transaction)->apply(); } } @@ -258,11 +260,10 @@ void WebViewFunctor::reparentSurfaceControl(ASurfaceControl* parent) { ATRACE_NAME("WebViewFunctor::reparentSurfaceControl"); if (mSurfaceControl == nullptr) return; - auto funcs = renderthread::RenderThread::getInstance().getASurfaceControlFunctions(); - ASurfaceTransaction* transaction = funcs.transactionCreateFunc(); - funcs.transactionReparentFunc(transaction, mSurfaceControl, parent); - mergeTransaction(transaction); - funcs.transactionDeleteFunc(transaction); + SurfaceComposerClient::Transaction transaction; + transaction.reparent(mSurfaceControl, sp<SurfaceControl>::fromExisting( + reinterpret_cast<SurfaceControl*>(parent))); + mergeTransaction(reinterpret_cast<ASurfaceTransaction*>(&transaction)); } void WebViewFunctor::reportRenderingThreads(const pid_t* thread_ids, size_t size) { diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h index ec17640f9b5e..ac16f9138384 100644 --- a/libs/hwui/WebViewFunctorManager.h +++ b/libs/hwui/WebViewFunctorManager.h @@ -25,7 +25,11 @@ #include <mutex> #include <vector> -namespace android::uirenderer { +namespace android { + +class SurfaceControl; + +namespace uirenderer { class WebViewFunctorManager; @@ -100,7 +104,9 @@ private: bool mHasContext = false; bool mCreatedHandle = false; int32_t mParentSurfaceControlGenerationId = 0; - ASurfaceControl* mSurfaceControl = nullptr; +#ifdef __ANDROID__ + sp<SurfaceControl> mSurfaceControl = nullptr; +#endif std::vector<pid_t> mRenderingThreads; }; @@ -126,4 +132,5 @@ private: std::vector<sp<WebViewFunctor::Handle>> mActiveFunctors; }; -} // namespace android::uirenderer +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index cfec24b17cd4..009974b3c8de 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -53,6 +53,7 @@ #include <src/image/SkImage_Base.h> #include <thread/CommonPool.h> #ifdef __ANDROID__ +#include <gui/SurfaceControl.h> #include <ui/GraphicBufferAllocator.h> #endif #include <utils/Color.h> @@ -217,9 +218,11 @@ static void android_view_ThreadedRenderer_setSurface(JNIEnv* env, jobject clazz, static void android_view_ThreadedRenderer_setSurfaceControl(JNIEnv* env, jobject clazz, jlong proxyPtr, jlong surfaceControlPtr) { +#ifdef __ANDROID__ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); - ASurfaceControl* surfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControlPtr); - proxy->setSurfaceControl(surfaceControl); + SurfaceControl* surfaceControl = reinterpret_cast<SurfaceControl*>(surfaceControlPtr); + proxy->setSurfaceControl(sp<SurfaceControl>::fromExisting(surfaceControl)); +#endif } static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz, @@ -684,7 +687,7 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env, class CopyRequestAdapter : public CopyRequest { public: - CopyRequestAdapter(JavaVM* vm, jobject jCopyRequest, Rect srcRect) + CopyRequestAdapter(JavaVM* vm, jobject jCopyRequest, ::android::uirenderer::Rect srcRect) : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {} virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override { @@ -710,8 +713,9 @@ static void android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, jobject c jobject jCopyRequest) { JavaVM* vm = nullptr; LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); - auto copyRequest = std::make_shared<CopyRequestAdapter>(vm, env->NewGlobalRef(jCopyRequest), - Rect(left, top, right, bottom)); + auto copyRequest = std::make_shared<CopyRequestAdapter>( + vm, env->NewGlobalRef(jCopyRequest), + ::android::uirenderer::Rect(left, top, right, bottom)); ANativeWindow* window = fromSurface(env, jsurface); RenderProxy::copySurfaceInto(window, std::move(copyRequest)); ANativeWindow_release(window); diff --git a/libs/hwui/platform/host/WebViewFunctorManager.cpp b/libs/hwui/platform/host/WebViewFunctorManager.cpp index 4ba206b41b39..66646b2da2ef 100644 --- a/libs/hwui/platform/host/WebViewFunctorManager.cpp +++ b/libs/hwui/platform/host/WebViewFunctorManager.cpp @@ -45,7 +45,7 @@ void WebViewFunctor::destroyContext() {} void WebViewFunctor::removeOverlays() {} ASurfaceControl* WebViewFunctor::getSurfaceControl() { - return mSurfaceControl; + return nullptr; } void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) {} diff --git a/libs/hwui/platform/host/renderthread/RenderThread.cpp b/libs/hwui/platform/host/renderthread/RenderThread.cpp index f9d0f4704e08..ece45304e6d5 100644 --- a/libs/hwui/platform/host/renderthread/RenderThread.cpp +++ b/libs/hwui/platform/host/renderthread/RenderThread.cpp @@ -27,8 +27,6 @@ namespace renderthread { static bool gHasRenderThreadInstance = false; static JVMAttachHook gOnStartHook = nullptr; -ASurfaceControlFunctions::ASurfaceControlFunctions() {} - bool RenderThread::hasInstance() { return gHasRenderThreadInstance; } diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index b248c4bc9ade..d5ac99389d87 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -18,6 +18,12 @@ #include <apex/window.h> #include <fcntl.h> + +#ifdef __ANDROID__ +#include <gui/ITransactionCompletedListener.h> +#include <gui/SurfaceComposerClient.h> +#endif + #include <gui/TraceUtils.h> #include <strings.h> #include <sys/stat.h> @@ -165,7 +171,9 @@ void CanvasContext::destroy() { stopDrawing(); setHardwareBuffer(nullptr); setSurface(nullptr); +#ifdef __ANDROID__ setSurfaceControl(nullptr); +#endif freePrefetchedLayers(); destroyHardwareResources(); mAnimationContext->destroy(); @@ -220,10 +228,15 @@ void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) { setupPipelineSurface(); } -void CanvasContext::setSurfaceControl(ASurfaceControl* surfaceControl) { - if (surfaceControl == mSurfaceControl) return; +#ifdef __ANDROID__ +sp<SurfaceControl> CanvasContext::getSurfaceControl() const { + return mSurfaceControl; +} +#endif - auto funcs = mRenderThread.getASurfaceControlFunctions(); +void CanvasContext::setSurfaceControl(sp<SurfaceControl> surfaceControl) { +#ifdef __ANDROID__ + if (surfaceControl == mSurfaceControl) return; if (surfaceControl == nullptr) { setASurfaceTransactionCallback(nullptr); @@ -231,17 +244,23 @@ void CanvasContext::setSurfaceControl(ASurfaceControl* surfaceControl) { } if (mSurfaceControl != nullptr) { - funcs.unregisterListenerFunc(this, &onSurfaceStatsAvailable); - funcs.releaseFunc(mSurfaceControl); + TransactionCompletedListener::getInstance()->removeSurfaceStatsListener( + this, reinterpret_cast<void*>(onSurfaceStatsAvailable)); } - mSurfaceControl = surfaceControl; + + mSurfaceControl = std::move(surfaceControl); mSurfaceControlGenerationId++; - mExpectSurfaceStats = surfaceControl != nullptr; + mExpectSurfaceStats = mSurfaceControl != nullptr; if (mExpectSurfaceStats) { - funcs.acquireFunc(mSurfaceControl); - funcs.registerListenerFunc(surfaceControl, mSurfaceControlGenerationId, this, - &onSurfaceStatsAvailable); + SurfaceStatsCallback callback = [generationId = mSurfaceControlGenerationId]( + void* callback_context, nsecs_t, const sp<Fence>&, + const SurfaceStats& surfaceStats) { + onSurfaceStatsAvailable(callback_context, generationId, surfaceStats); + }; + TransactionCompletedListener::getInstance()->addSurfaceStatsListener( + this, reinterpret_cast<void*>(onSurfaceStatsAvailable), mSurfaceControl, callback); } +#endif } void CanvasContext::setupPipelineSurface() { @@ -896,17 +915,26 @@ FrameInfo* CanvasContext::getFrameInfoFromLastFew(uint64_t frameNumber, uint32_t } void CanvasContext::onSurfaceStatsAvailable(void* context, int32_t surfaceControlId, - ASurfaceControlStats* stats) { + const SurfaceStats& stats) { +#ifdef __ANDROID__ auto* instance = static_cast<CanvasContext*>(context); - const ASurfaceControlFunctions& functions = - instance->mRenderThread.getASurfaceControlFunctions(); + nsecs_t gpuCompleteTime = -1L; + if (const auto* fence = std::get_if<sp<Fence>>(&stats.acquireTimeOrFence)) { + // We got a fence instead of the acquire time due to latching unsignaled. + // Ideally the client could just get the acquire time directly from + // the fence instead of calling this function which needs to block. + (*fence)->waitForever("acquireFence"); + gpuCompleteTime = (*fence)->getSignalTime(); + } else { + gpuCompleteTime = std::get<int64_t>(stats.acquireTimeOrFence); + } - nsecs_t gpuCompleteTime = functions.getAcquireTimeFunc(stats); if (gpuCompleteTime == Fence::SIGNAL_TIME_PENDING) { gpuCompleteTime = -1; } - uint64_t frameNumber = functions.getFrameNumberFunc(stats); + + uint64_t frameNumber = stats.eventStats.frameNumber; FrameInfo* frameInfo = instance->getFrameInfoFromLastFew(frameNumber, surfaceControlId); @@ -919,6 +947,7 @@ void CanvasContext::onSurfaceStatsAvailable(void* context, int32_t surfaceContro instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter, frameNumber, surfaceControlId); } +#endif } // Called by choreographer to do an RT-driven animation @@ -1140,10 +1169,11 @@ CanvasContext* CanvasContext::getActiveContext() { return ScopedActiveContext::getActiveContext(); } -bool CanvasContext::mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control) { +bool CanvasContext::mergeTransaction(ASurfaceTransaction* transaction, + const sp<SurfaceControl>& control) { if (!mASurfaceTransactionCallback) return false; return std::invoke(mASurfaceTransactionCallback, reinterpret_cast<int64_t>(transaction), - reinterpret_cast<int64_t>(control), getFrameNumber()); + reinterpret_cast<int64_t>(control.get()), getFrameNumber()); } void CanvasContext::prepareSurfaceControlForWebview() { diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 3de8e0516070..655aebada954 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -50,6 +50,9 @@ #include "utils/RingBuffer.h" namespace android { + +class SurfaceStats; + namespace uirenderer { class AnimationContext; @@ -121,7 +124,9 @@ public: */ GrDirectContext* getGrContext() const { return mRenderThread.getGrContext(); } - ASurfaceControl* getSurfaceControl() const { return mSurfaceControl; } +#ifdef __ANDROID__ + sp<SurfaceControl> getSurfaceControl() const; +#endif int32_t getSurfaceControlGenerationId() const { return mSurfaceControlGenerationId; } // Won't take effect until next EGLSurface creation @@ -129,7 +134,7 @@ public: void setHardwareBuffer(AHardwareBuffer* buffer); void setSurface(ANativeWindow* window, bool enableTimeout = true); - void setSurfaceControl(ASurfaceControl* surfaceControl); + void setSurfaceControl(sp<SurfaceControl> surfaceControl); bool pauseSurface(); void setStopped(bool stopped); bool isStopped() { return mStopped || !hasOutputTarget(); } @@ -207,7 +212,7 @@ public: // Called when SurfaceStats are available. static void onSurfaceStatsAvailable(void* context, int32_t surfaceControlId, - ASurfaceControlStats* stats); + const SurfaceStats& stats); void setASurfaceTransactionCallback( const std::function<bool(int64_t, int64_t, int64_t)>& callback) { @@ -218,7 +223,7 @@ public: mBufferParams = params; } - bool mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control); + bool mergeTransaction(ASurfaceTransaction* transaction, const sp<SurfaceControl>& control); void setPrepareSurfaceControlForWebviewCallback(const std::function<void()>& callback) { mPrepareSurfaceControlForWebviewCallback = callback; @@ -286,7 +291,9 @@ private: std::unique_ptr<ReliableSurface> mNativeSurface; // The SurfaceControl reference is passed from ViewRootImpl, can be set to // NULL to remove the reference - ASurfaceControl* mSurfaceControl = nullptr; +#ifdef __ANDROID__ + sp<SurfaceControl> mSurfaceControl = nullptr; +#endif // id to track surface control changes and WebViewFunctor uses it to determine // whether reparenting is needed also used by FrameMetricsReporter to determine // if a frame is from an "old" surface (i.e. one that existed before the diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index ebfd8fde91f6..e4be5fa8d39e 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -21,6 +21,11 @@ #include <SkPicture.h> #include <gui/TraceUtils.h> #include <pthread.h> + +#ifdef __ANDROID__ +#include <gui/SurfaceControl.h> +#endif + #include <ui/GraphicBufferAllocator.h> #include "DeferredLayerUpdater.h" @@ -115,17 +120,12 @@ void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) { }); } -void RenderProxy::setSurfaceControl(ASurfaceControl* surfaceControl) { - auto funcs = mRenderThread.getASurfaceControlFunctions(); - if (surfaceControl) { - funcs.acquireFunc(surfaceControl); - } - mRenderThread.queue().post([this, control = surfaceControl, funcs]() mutable { - mContext->setSurfaceControl(control); - if (control) { - funcs.releaseFunc(control); - } +void RenderProxy::setSurfaceControl(sp<SurfaceControl> surfaceControl) { +#ifdef __ANDROID__ + mRenderThread.queue().post([this, control = std::move(surfaceControl)]() mutable { + mContext->setSurfaceControl(std::move(control)); }); +#endif } void RenderProxy::allocateBuffers() { diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index ad6d54bfcf91..23b3ebd4b360 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -20,7 +20,6 @@ #include <SkRefCnt.h> #include <android/hardware_buffer.h> #include <android/native_window.h> -#include <android/surface_control.h> #include <cutils/compiler.h> #include <utils/Functor.h> @@ -39,6 +38,7 @@ class SkImage; namespace android { class GraphicBuffer; +class SurfaceControl; class Surface; namespace uirenderer { @@ -80,7 +80,7 @@ public: void setName(const char* name); void setHardwareBuffer(AHardwareBuffer* buffer); void setSurface(ANativeWindow* window, bool enableTimeout = true); - void setSurfaceControl(ASurfaceControl* surfaceControl); + void setSurfaceControl(sp<SurfaceControl> surfaceControl); void allocateBuffers(); bool pause(); void setStopped(bool stopped); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 6ab8e4e0e2ab..5e404247376f 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -55,66 +55,6 @@ static bool gHasRenderThreadInstance = false; static JVMAttachHook gOnStartHook = nullptr; -ASurfaceControlFunctions::ASurfaceControlFunctions() { - void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); - createFunc = (ASC_create)dlsym(handle_, "ASurfaceControl_create"); - LOG_ALWAYS_FATAL_IF(createFunc == nullptr, - "Failed to find required symbol ASurfaceControl_create!"); - - acquireFunc = (ASC_acquire) dlsym(handle_, "ASurfaceControl_acquire"); - LOG_ALWAYS_FATAL_IF(acquireFunc == nullptr, - "Failed to find required symbol ASurfaceControl_acquire!"); - - releaseFunc = (ASC_release) dlsym(handle_, "ASurfaceControl_release"); - LOG_ALWAYS_FATAL_IF(releaseFunc == nullptr, - "Failed to find required symbol ASurfaceControl_release!"); - - registerListenerFunc = (ASC_registerSurfaceStatsListener) dlsym(handle_, - "ASurfaceControl_registerSurfaceStatsListener"); - LOG_ALWAYS_FATAL_IF(registerListenerFunc == nullptr, - "Failed to find required symbol ASurfaceControl_registerSurfaceStatsListener!"); - - unregisterListenerFunc = (ASC_unregisterSurfaceStatsListener) dlsym(handle_, - "ASurfaceControl_unregisterSurfaceStatsListener"); - LOG_ALWAYS_FATAL_IF(unregisterListenerFunc == nullptr, - "Failed to find required symbol ASurfaceControl_unregisterSurfaceStatsListener!"); - - getAcquireTimeFunc = (ASCStats_getAcquireTime) dlsym(handle_, - "ASurfaceControlStats_getAcquireTime"); - LOG_ALWAYS_FATAL_IF(getAcquireTimeFunc == nullptr, - "Failed to find required symbol ASurfaceControlStats_getAcquireTime!"); - - getFrameNumberFunc = (ASCStats_getFrameNumber) dlsym(handle_, - "ASurfaceControlStats_getFrameNumber"); - LOG_ALWAYS_FATAL_IF(getFrameNumberFunc == nullptr, - "Failed to find required symbol ASurfaceControlStats_getFrameNumber!"); - - transactionCreateFunc = (AST_create)dlsym(handle_, "ASurfaceTransaction_create"); - LOG_ALWAYS_FATAL_IF(transactionCreateFunc == nullptr, - "Failed to find required symbol ASurfaceTransaction_create!"); - - transactionDeleteFunc = (AST_delete)dlsym(handle_, "ASurfaceTransaction_delete"); - LOG_ALWAYS_FATAL_IF(transactionDeleteFunc == nullptr, - "Failed to find required symbol ASurfaceTransaction_delete!"); - - transactionApplyFunc = (AST_apply)dlsym(handle_, "ASurfaceTransaction_apply"); - LOG_ALWAYS_FATAL_IF(transactionApplyFunc == nullptr, - "Failed to find required symbol ASurfaceTransaction_apply!"); - - transactionReparentFunc = (AST_reparent)dlsym(handle_, "ASurfaceTransaction_reparent"); - LOG_ALWAYS_FATAL_IF(transactionReparentFunc == nullptr, - "Failed to find required symbol transactionReparentFunc!"); - - transactionSetVisibilityFunc = - (AST_setVisibility)dlsym(handle_, "ASurfaceTransaction_setVisibility"); - LOG_ALWAYS_FATAL_IF(transactionSetVisibilityFunc == nullptr, - "Failed to find required symbol ASurfaceTransaction_setVisibility!"); - - transactionSetZOrderFunc = (AST_setZOrder)dlsym(handle_, "ASurfaceTransaction_setZOrder"); - LOG_ALWAYS_FATAL_IF(transactionSetZOrderFunc == nullptr, - "Failed to find required symbol ASurfaceTransaction_setZOrder!"); -} - void RenderThread::extendedFrameCallback(const AChoreographerFrameCallbackData* cbData, void* data) { RenderThread* rt = reinterpret_cast<RenderThread*>(data); diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index 86fddbae0831..f733c7c58ef3 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -77,49 +77,6 @@ struct VsyncSource { virtual ~VsyncSource() {} }; -typedef ASurfaceControl* (*ASC_create)(ASurfaceControl* parent, const char* debug_name); -typedef void (*ASC_acquire)(ASurfaceControl* control); -typedef void (*ASC_release)(ASurfaceControl* control); - -typedef void (*ASC_registerSurfaceStatsListener)(ASurfaceControl* control, int32_t id, - void* context, - ASurfaceControl_SurfaceStatsListener func); -typedef void (*ASC_unregisterSurfaceStatsListener)(void* context, - ASurfaceControl_SurfaceStatsListener func); - -typedef int64_t (*ASCStats_getAcquireTime)(ASurfaceControlStats* stats); -typedef uint64_t (*ASCStats_getFrameNumber)(ASurfaceControlStats* stats); - -typedef ASurfaceTransaction* (*AST_create)(); -typedef void (*AST_delete)(ASurfaceTransaction* transaction); -typedef void (*AST_apply)(ASurfaceTransaction* transaction); -typedef void (*AST_reparent)(ASurfaceTransaction* aSurfaceTransaction, - ASurfaceControl* aSurfaceControl, - ASurfaceControl* newParentASurfaceControl); -typedef void (*AST_setVisibility)(ASurfaceTransaction* transaction, - ASurfaceControl* surface_control, int8_t visibility); -typedef void (*AST_setZOrder)(ASurfaceTransaction* transaction, ASurfaceControl* surface_control, - int32_t z_order); - -struct ASurfaceControlFunctions { - ASurfaceControlFunctions(); - - ASC_create createFunc; - ASC_acquire acquireFunc; - ASC_release releaseFunc; - ASC_registerSurfaceStatsListener registerListenerFunc; - ASC_unregisterSurfaceStatsListener unregisterListenerFunc; - ASCStats_getAcquireTime getAcquireTimeFunc; - ASCStats_getFrameNumber getFrameNumberFunc; - - AST_create transactionCreateFunc; - AST_delete transactionDeleteFunc; - AST_apply transactionApplyFunc; - AST_reparent transactionReparentFunc; - AST_setVisibility transactionSetVisibilityFunc; - AST_setZOrder transactionSetZOrderFunc; -}; - class ChoreographerSource; class DummyVsyncSource; @@ -166,10 +123,6 @@ public: void preload(); - const ASurfaceControlFunctions& getASurfaceControlFunctions() { - return mASurfaceControlFunctions; - } - void trimMemory(TrimLevel level); void trimCaches(CacheTrimLevel level); @@ -244,7 +197,6 @@ private: CacheManager* mCacheManager; sp<VulkanManager> mVkManager; - ASurfaceControlFunctions mASurfaceControlFunctions; std::mutex mJankDataMutex; }; diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java index 56d3df3b2555..311d64f6c7e8 100644 --- a/media/java/android/media/AudioDeviceVolumeManager.java +++ b/media/java/android/media/AudioDeviceVolumeManager.java @@ -29,6 +29,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.Context; import android.os.IBinder; import android.os.RemoteException; @@ -52,6 +53,127 @@ public class AudioDeviceVolumeManager { private static final String TAG = "AudioDeviceVolumeManager"; + /** + * @hide + * Volume behavior for an audio device that has no particular volume behavior set. Invalid as + * an argument to {@link #setDeviceVolumeBehavior(AudioDeviceAttributes, int)} and should not + * be returned by {@link #getDeviceVolumeBehavior(AudioDeviceAttributes)}. + */ + public static final int DEVICE_VOLUME_BEHAVIOR_UNSET = -1; + /** + * @hide + * Volume behavior for an audio device where a software attenuation is applied + * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) + */ + @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) + public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; + /** + * @hide + * Volume behavior for an audio device where the volume is always set to provide no attenuation + * nor gain (e.g. unit gain). + * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) + */ + @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) + public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; + /** + * @hide + * Volume behavior for an audio device where the volume is either set to muted, or to provide + * no attenuation nor gain (e.g. unit gain). + * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) + */ + @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) + public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; + /** + * @hide + * Volume behavior for an audio device where no software attenuation is applied, and + * the volume is kept synchronized between the host and the device itself through a + * device-specific protocol such as BT AVRCP. + * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) + */ + @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) + public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; + /** + * @hide + * Volume behavior for an audio device where no software attenuation is applied, and + * the volume is kept synchronized between the host and the device itself through a + * device-specific protocol (such as for hearing aids), based on the audio mode (e.g. + * normal vs in phone call). + * @see AudioManager#setMode(int) + * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) + */ + @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) + public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; + + /** + * @hide + * A variant of {@link #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} where the host cannot reliably set + * the volume percentage of the audio device. Specifically, {@link AudioManager#setStreamVolume} + * will have no effect, or an unreliable effect. + */ + @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) + public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; + + /** @hide */ + @IntDef({ + DEVICE_VOLUME_BEHAVIOR_VARIABLE, + DEVICE_VOLUME_BEHAVIOR_FULL, + DEVICE_VOLUME_BEHAVIOR_FIXED, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceVolumeBehavior {} + + /** @hide */ + @IntDef({ + DEVICE_VOLUME_BEHAVIOR_UNSET, + DEVICE_VOLUME_BEHAVIOR_VARIABLE, + DEVICE_VOLUME_BEHAVIOR_FULL, + DEVICE_VOLUME_BEHAVIOR_FIXED, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceVolumeBehaviorState {} + + /** + * Variants of absolute volume behavior that are set in for absolute volume management. + * @hide + */ + @IntDef({ + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AbsoluteDeviceVolumeBehavior {} + + /** + * @hide + * Throws IAE on an invalid volume behavior value + * @param volumeBehavior behavior value to check + */ + public static void enforceValidVolumeBehavior(int volumeBehavior) { + switch (volumeBehavior) { + case DEVICE_VOLUME_BEHAVIOR_VARIABLE: + case DEVICE_VOLUME_BEHAVIOR_FULL: + case DEVICE_VOLUME_BEHAVIOR_FIXED: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY: + return; + default: + throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior); + } + } + /** @hide * Indicates no special treatment in the handling of the volume adjustment */ public static final int ADJUST_MODE_NORMAL = 0; @@ -158,7 +280,7 @@ public class AudioDeviceVolumeManager { android.Manifest.permission.BLUETOOTH_PRIVILEGED }) public void register(boolean register, @NonNull AudioDeviceAttributes device, @NonNull List<VolumeInfo> volumes, boolean handlesVolumeAdjustment, - @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) { + @AbsoluteDeviceVolumeBehavior int behavior) { try { getService().registerDeviceVolumeDispatcherForAbsoluteVolume(register, this, mPackageName, @@ -204,6 +326,94 @@ public class AudioDeviceVolumeManager { /** * @hide + * Sets the volume behavior for an audio output device. + * @see #DEVICE_VOLUME_BEHAVIOR_VARIABLE + * @see #DEVICE_VOLUME_BEHAVIOR_FULL + * @see #DEVICE_VOLUME_BEHAVIOR_FIXED + * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE + * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE + * @param device the device to be affected + * @param deviceVolumeBehavior one of the device behaviors + */ + @SystemApi + @RequiresPermission(anyOf = { + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED + }) + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) + public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, + @DeviceVolumeBehavior int deviceVolumeBehavior) { + // verify arguments (validity of device type is enforced in server) + Objects.requireNonNull(device); + enforceValidVolumeBehavior(deviceVolumeBehavior); + // communicate with service + final IAudioService service = getService(); + try { + service.setDeviceVolumeBehavior(device, deviceVolumeBehavior, mPackageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Returns the volume device behavior for the given audio device + * @param device the audio device + * @return the volume behavior for the device + */ + @SystemApi + @RequiresPermission(anyOf = { + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.QUERY_AUDIO_STATE, + Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED + }) + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) + public @DeviceVolumeBehavior int getDeviceVolumeBehavior( + @NonNull AudioDeviceAttributes device) { + // verify arguments (validity of device type is enforced in server) + Objects.requireNonNull(device); + // communicate with service + final IAudioService service = getService(); + try { + return service.getDeviceVolumeBehavior(device); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Returns {@code true} if the volume device behavior is {@link #DEVICE_VOLUME_BEHAVIOR_FULL}. + */ + @TestApi + @RequiresPermission(anyOf = { + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.QUERY_AUDIO_STATE, + Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED + }) + @SuppressWarnings("UnflaggedApi") // @TestApi without associated feature. + public boolean isFullVolumeDevice() { + final AudioAttributes attributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + List<AudioDeviceAttributes> devices; + final IAudioService service = getService(); + try { + devices = service.getDevicesForAttributes(attributes); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + for (AudioDeviceAttributes device : devices) { + if (getDeviceVolumeBehavior(device) == DEVICE_VOLUME_BEHAVIOR_FULL) { + return true; + } + } + return false; + } + + /** + * @hide * Configures a device to use absolute volume model, and registers a listener for receiving * volume updates to apply on that device * @param device the audio device set to absolute volume mode @@ -297,7 +507,7 @@ public class AudioDeviceVolumeManager { @NonNull @CallbackExecutor Executor executor, @NonNull OnAudioDeviceVolumeChangedListener vclistener) { baseSetDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener, - handlesVolumeAdjustment, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); + handlesVolumeAdjustment, DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); } /** @@ -355,12 +565,12 @@ public class AudioDeviceVolumeManager { @NonNull @CallbackExecutor Executor executor, @NonNull OnAudioDeviceVolumeChangedListener vclistener) { baseSetDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener, - handlesVolumeAdjustment, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + handlesVolumeAdjustment, DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); } /** * Base method for configuring a device to use absolute volume behavior, or one of its variants. - * See {@link AudioManager.AbsoluteDeviceVolumeBehavior} for a list of allowed behaviors. + * See {@link AbsoluteDeviceVolumeBehavior} for a list of allowed behaviors. * * @param behavior the variant of absolute device volume behavior to adopt */ @@ -372,7 +582,7 @@ public class AudioDeviceVolumeManager { @NonNull @CallbackExecutor Executor executor, @NonNull OnAudioDeviceVolumeChangedListener vclistener, boolean handlesVolumeAdjustment, - @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) { + @AbsoluteDeviceVolumeBehavior int behavior) { Objects.requireNonNull(device); Objects.requireNonNull(volumes); Objects.requireNonNull(executor); @@ -417,7 +627,7 @@ public class AudioDeviceVolumeManager { */ void onDeviceVolumeBehaviorChanged( @NonNull AudioDeviceAttributes device, - @AudioManager.DeviceVolumeBehavior int volumeBehavior); + @DeviceVolumeBehavior int volumeBehavior); } /** @@ -580,19 +790,19 @@ public class AudioDeviceVolumeManager { * @param behavior one of the volume behaviors defined in AudioManager * @return a string for the given behavior */ - public static String volumeBehaviorName(@AudioManager.DeviceVolumeBehavior int behavior) { + public static String volumeBehaviorName(@DeviceVolumeBehavior int behavior) { switch (behavior) { - case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE: + case DEVICE_VOLUME_BEHAVIOR_VARIABLE: return "DEVICE_VOLUME_BEHAVIOR_VARIABLE"; - case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL: + case DEVICE_VOLUME_BEHAVIOR_FULL: return "DEVICE_VOLUME_BEHAVIOR_FULL"; - case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED: + case DEVICE_VOLUME_BEHAVIOR_FIXED: return "DEVICE_VOLUME_BEHAVIOR_FIXED"; - case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE"; - case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE"; - case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY: return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY"; default: return "invalid volume behavior " + behavior; @@ -611,7 +821,7 @@ public class AudioDeviceVolumeManager { @Override public void dispatchDeviceVolumeBehaviorChanged(@NonNull AudioDeviceAttributes device, - @AudioManager.DeviceVolumeBehavior int volumeBehavior) { + @DeviceVolumeBehavior int volumeBehavior) { mDeviceVolumeBehaviorChangedListenerMgr.callListeners((listener) -> listener.onDeviceVolumeBehaviorChanged(device, volumeBehavior)); } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 32af7c6fca68..4aba491c291e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -19,6 +19,7 @@ package android.media; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; import static android.content.Context.DEVICE_ID_DEFAULT; +import static android.media.audio.Flags.FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.cacheGetStreamMinMaxVolume; import static android.media.audio.Flags.cacheGetStreamVolume; @@ -6659,24 +6660,30 @@ public class AudioManager { * @hide * Volume behavior for an audio device where a software attenuation is applied * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) + * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_VARIABLE} instead */ @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; /** * @hide * Volume behavior for an audio device where the volume is always set to provide no attenuation * nor gain (e.g. unit gain). * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) + * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_FULL} instead */ @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; /** * @hide * Volume behavior for an audio device where the volume is either set to muted, or to provide * no attenuation nor gain (e.g. unit gain). * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) + * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_FIXED} instead */ @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; /** * @hide @@ -6684,8 +6691,10 @@ public class AudioManager { * the volume is kept synchronized between the host and the device itself through a * device-specific protocol such as BT AVRCP. * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) + * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} instead */ @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; /** * @hide @@ -6695,8 +6704,11 @@ public class AudioManager { * normal vs in phone call). * @see #setMode(int) * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) + * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE} + * instead */ @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; /** @@ -6704,8 +6716,11 @@ public class AudioManager { * A variant of {@link #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} where the host cannot reliably set * the volume percentage of the audio device. Specifically, {@link #setStreamVolume} will have * no effect, or an unreliable effect. + * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY} + * instead */ @SystemApi + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; /** @hide */ @@ -6720,49 +6735,6 @@ public class AudioManager { @Retention(RetentionPolicy.SOURCE) public @interface DeviceVolumeBehavior {} - /** @hide */ - @IntDef({ - DEVICE_VOLUME_BEHAVIOR_UNSET, - DEVICE_VOLUME_BEHAVIOR_VARIABLE, - DEVICE_VOLUME_BEHAVIOR_FULL, - DEVICE_VOLUME_BEHAVIOR_FIXED, - DEVICE_VOLUME_BEHAVIOR_ABSOLUTE, - DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE, - DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface DeviceVolumeBehaviorState {} - - /** - * Variants of absolute volume behavior that are set in {@link AudioDeviceVolumeManager}. - * @hide - */ - @IntDef({ - DEVICE_VOLUME_BEHAVIOR_ABSOLUTE, - DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface AbsoluteDeviceVolumeBehavior {} - - /** - * @hide - * Throws IAE on an invalid volume behavior value - * @param volumeBehavior behavior value to check - */ - public static void enforceValidVolumeBehavior(int volumeBehavior) { - switch (volumeBehavior) { - case DEVICE_VOLUME_BEHAVIOR_VARIABLE: - case DEVICE_VOLUME_BEHAVIOR_FULL: - case DEVICE_VOLUME_BEHAVIOR_FIXED: - case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: - case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: - case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY: - return; - default: - throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior); - } - } - /** * @hide * Sets the volume behavior for an audio output device. @@ -6773,17 +6745,21 @@ public class AudioManager { * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE * @param device the device to be affected * @param deviceVolumeBehavior one of the device behaviors + * + * @deprecated use + * {@link AudioDeviceVolumeManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int)} instead */ @SystemApi @RequiresPermission(anyOf = { Manifest.permission.MODIFY_AUDIO_ROUTING, Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED }) + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, @DeviceVolumeBehavior int deviceVolumeBehavior) { // verify arguments (validity of device type is enforced in server) Objects.requireNonNull(device); - enforceValidVolumeBehavior(deviceVolumeBehavior); + AudioDeviceVolumeManager.enforceValidVolumeBehavior(deviceVolumeBehavior); // communicate with service final IAudioService service = getService(); try { @@ -6810,6 +6786,8 @@ public class AudioManager { * Returns the volume device behavior for the given audio device * @param device the audio device * @return the volume behavior for the device + * @deprecated use + * {@link AudioDeviceVolumeManager#getDeviceVolumeBehavior(AudioDeviceAttributes)} instead */ @SystemApi @RequiresPermission(anyOf = { @@ -6817,6 +6795,7 @@ public class AudioManager { Manifest.permission.QUERY_AUDIO_STATE, Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED }) + @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT) public @DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) { // verify arguments (validity of device type is enforced in server) @@ -6836,29 +6815,6 @@ public class AudioManager { } /** - * @hide - * Returns {@code true} if the volume device behavior is {@link #DEVICE_VOLUME_BEHAVIOR_FULL}. - */ - @TestApi - @RequiresPermission(anyOf = { - Manifest.permission.MODIFY_AUDIO_ROUTING, - Manifest.permission.QUERY_AUDIO_STATE, - Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED - }) - public boolean isFullVolumeDevice() { - final AudioAttributes attributes = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .build(); - final List<AudioDeviceAttributes> devices = getDevicesForAttributes(attributes); - for (AudioDeviceAttributes device : devices) { - if (getDeviceVolumeBehavior(device) == DEVICE_VOLUME_BEHAVIOR_FULL) { - return true; - } - } - return false; - } - - /** * Indicate wired accessory connection state change. * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx) * @param state new connection state: 1 connected, 0 disconnected diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 7af78b81cda5..03bcc6afc1b7 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -1299,6 +1299,7 @@ public class MediaRecorder implements AudioRouting, * start() or before setOutputFormat(). * @throws IOException if prepare fails otherwise. */ + @RequiresPermission(value = android.Manifest.permission.RECORD_AUDIO, conditional = true) public void prepare() throws IllegalStateException, IOException { if (mPath != null) { @@ -1337,6 +1338,7 @@ public class MediaRecorder implements AudioRouting, * @throws IllegalStateException if it is called before * prepare() or when the camera is already in use by another app. */ + @RequiresPermission(value = android.Manifest.permission.RECORD_AUDIO, conditional = true) public native void start() throws IllegalStateException; /** diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java index be711accd542..89e5372b530e 100644 --- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java +++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java @@ -27,6 +27,7 @@ import android.widget.Button; import android.widget.LinearLayout; import androidx.annotation.GravityInt; +import androidx.annotation.IntDef; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; @@ -34,21 +35,46 @@ import com.android.settingslib.widget.preference.button.R; import com.google.android.material.button.MaterialButton; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A preference handled a button */ public class ButtonPreference extends Preference implements GroupSectionDividerMixin { + public static final int TYPE_FILLED = 0; + public static final int TYPE_TONAL = 1; + public static final int TYPE_OUTLINE = 2; + + @IntDef({TYPE_FILLED, TYPE_TONAL, TYPE_OUTLINE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Type { + } + + public static final int SIZE_NORMAL = 0; + public static final int SIZE_LARGE = 1; + public static final int SIZE_EXTRA_LARGE = 2; + + @IntDef({SIZE_NORMAL, SIZE_LARGE, SIZE_EXTRA_LARGE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Size { + } + enum ButtonStyle { - FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled), - FILLED_LARGE(0, 1, R.layout.settingslib_expressive_button_filled_large), - FILLED_EXTRA(0, 2, R.layout.settingslib_expressive_button_filled_extra), - TONAL_NORMAL(1, 0, R.layout.settingslib_expressive_button_tonal), - TONAL_LARGE(1, 1, R.layout.settingslib_expressive_button_tonal_large), - TONAL_EXTRA(1, 2, R.layout.settingslib_expressive_button_tonal_extra), - OUTLINE_NORMAL(2, 0, R.layout.settingslib_expressive_button_outline), - OUTLINE_LARGE(2, 1, R.layout.settingslib_expressive_button_outline_large), - OUTLINE_EXTRA(2, 2, R.layout.settingslib_expressive_button_outline_extra); + FILLED_NORMAL(TYPE_FILLED, SIZE_NORMAL, R.layout.settingslib_expressive_button_filled), + FILLED_LARGE(TYPE_FILLED, SIZE_LARGE, R.layout.settingslib_expressive_button_filled_large), + FILLED_EXTRA(TYPE_FILLED, SIZE_EXTRA_LARGE, + R.layout.settingslib_expressive_button_filled_extra), + TONAL_NORMAL(TYPE_TONAL, SIZE_NORMAL, R.layout.settingslib_expressive_button_tonal), + TONAL_LARGE(TYPE_TONAL, SIZE_LARGE, R.layout.settingslib_expressive_button_tonal_large), + TONAL_EXTRA(TYPE_TONAL, SIZE_EXTRA_LARGE, + R.layout.settingslib_expressive_button_tonal_extra), + OUTLINE_NORMAL(TYPE_OUTLINE, SIZE_NORMAL, R.layout.settingslib_expressive_button_outline), + OUTLINE_LARGE(TYPE_OUTLINE, SIZE_LARGE, + R.layout.settingslib_expressive_button_outline_large), + OUTLINE_EXTRA(TYPE_OUTLINE, SIZE_EXTRA_LARGE, + R.layout.settingslib_expressive_button_outline_extra); private final int mType; private final int mSize; @@ -60,7 +86,7 @@ public class ButtonPreference extends Preference implements GroupSectionDividerM this.mLayoutId = layoutId; } - static int getLayoutId(int type, int size) { + static int getLayoutId(@Type int type, @Size int size) { for (ButtonStyle style : values()) { if (style.mType == type && style.mSize == size) { return style.mLayoutId; @@ -266,7 +292,7 @@ public class ButtonPreference extends Preference implements GroupSectionDividerM * <li>2: extra large</li> * </ul> */ - public void setButtonStyle(int type, int size) { + public void setButtonStyle(@Type int type, @Size int size) { int layoutId = ButtonStyle.getLayoutId(type, size); setLayoutResource(layoutId); notifyChanged(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt index 1d42424bc6ed..8d50cdc001ca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt @@ -19,13 +19,17 @@ package com.android.systemui.qs.panels.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.qs.FakeQSTile import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.shade.domain.interactor.disableDualShade +import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -34,6 +38,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @SmallTest +@EnableSceneContainer class DetailsViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private lateinit var underTest: DetailsViewModel @@ -45,10 +50,12 @@ class DetailsViewModelTest : SysuiTestCase() { underTest = kosmos.detailsViewModel } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun changeTileDetailsViewModel() = + fun changeTileDetailsViewModelWithDualShadeEnabled() = with(kosmos) { testScope.runTest { + kosmos.enableDualShade() val specs = listOf(spec, specNoDetails) tileSpecRepository.setTiles(0, specs) runCurrent() @@ -85,4 +92,36 @@ class DetailsViewModelTest : SysuiTestCase() { assertThat(underTest.onTileClicked(null)).isFalse() } } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun ignoreChangingTileDetailsViewModelWithDualShadeDisabled() = + with(kosmos) { + testScope.runTest { + kosmos.disableDualShade() + val specs = listOf(spec, specNoDetails) + tileSpecRepository.setTiles(0, specs) + runCurrent() + + val tiles = currentTilesInteractor.currentTiles.value + + assertThat(currentTilesInteractor.currentTilesSpecs.size).isEqualTo(2) + assertThat(tiles[1].spec).isEqualTo(specNoDetails) + (tiles[1].tile as FakeQSTile).hasDetailsViewModel = false + + assertThat(underTest.activeTileDetails).isNull() + + // Click on the tile who has the `spec`. + assertThat(underTest.onTileClicked(spec)).isFalse() + assertThat(underTest.activeTileDetails).isNull() + + // Click on a tile who dose not have a valid spec. + assertThat(underTest.onTileClicked(null)).isFalse() + assertThat(underTest.activeTileDetails).isNull() + + // Click on a tile who dose not have a detailed view. + assertThat(underTest.onTileClicked(specNoDetails)).isFalse() + assertThat(underTest.activeTileDetails).isNull() + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index ce120c51db6a..95366568a37a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.platform.test.annotations.DisableFlags; import android.provider.Settings; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -38,6 +39,7 @@ import android.view.ViewGroup; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.notification.collection.EntryAdapter; @@ -418,6 +420,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { assertTrue("when alpha is .5, menu is visible", row.isMenuVisible()); } + @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES) @Test public void testOnTouchMove() { NotificationMenuRow row = Mockito.spy( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt index ccc8be7de038..6c6ba933c03a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt @@ -130,7 +130,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled setTargets() @@ -150,7 +152,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled canRowBeDismissed = false @@ -172,7 +176,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled setTargets() @@ -192,7 +198,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled canRowBeDismissed = false @@ -294,6 +302,29 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { assertThat(underTest.isSwipedViewRoundableSet).isFalse() } + @Test + fun isMagneticRowDismissible_isDismissibleWhenDetached() = + kosmos.testScope.runTest { + setDetachedState() + + val isDismissible = underTest.isMagneticRowSwipeDetached(swipedRow) + assertThat(isDismissible).isTrue() + } + + @Test + fun setMagneticRowTranslation_whenDetached_belowAttachThreshold_reattaches() = + kosmos.testScope.runTest { + // GIVEN that the swiped view has been detached + setDetachedState() + + // WHEN setting a new translation above the attach threshold + val translation = 50f + underTest.setMagneticRowTranslation(swipedRow, translation) + + // THEN the swiped view reattaches magnetically and the state becomes PULLING + assertThat(underTest.currentState).isEqualTo(State.PULLING) + } + @After fun tearDown() { // We reset the manager so that all MagneticRowListener can cancel all animations @@ -302,7 +333,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { private fun setDetachedState() { val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // Set the pulling state setTargets() @@ -327,8 +360,8 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener { val delegate = this return object : MagneticRowListener { - override fun setMagneticTranslation(translation: Float) { - delegate.setMagneticTranslation(translation) + override fun setMagneticTranslation(translation: Float, trackEagerly: Boolean) { + delegate.setMagneticTranslation(translation, trackEagerly) } override fun triggerMagneticForce( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 789701f5e4b0..de48f4018989 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -49,6 +49,7 @@ import android.view.ViewConfiguration; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.flags.FakeFeatureFlags; @@ -362,6 +363,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { verify(mSwipeHelper, times(1)).isFalseGesture(); } + @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES) @Test public void testIsDismissGesture_farEnough() { doReturn(false).when(mSwipeHelper).isFalseGesture(); @@ -374,6 +376,20 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { verify(mSwipeHelper, times(1)).isFalseGesture(); } + @EnableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES) + @Test + public void testIsDismissGesture_magneticSwipeIsDismissible() { + doReturn(false).when(mSwipeHelper).isFalseGesture(); + doReturn(false).when(mSwipeHelper).swipedFarEnough(); + doReturn(false).when(mSwipeHelper).swipedFastEnough(); + doReturn(true).when(mCallback).isMagneticViewDetached(any()); + when(mCallback.canChildBeDismissedInDirection(any(), anyBoolean())).thenReturn(true); + when(mEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_UP); + + assertTrue("Should be a dismissal", mSwipeHelper.isDismissGesture(mEvent)); + verify(mSwipeHelper, times(1)).isFalseGesture(); + } + @Test public void testIsDismissGesture_notFarOrFastEnough() { doReturn(false).when(mSwipeHelper).isFalseGesture(); diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 089466707298..d017754ae653 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -778,18 +778,26 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { protected boolean swipedFarEnough() { float translation = getTranslation(mTouchedView); - return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize( - mTouchedView); + return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mTouchedView); } public boolean isDismissGesture(MotionEvent ev) { float translation = getTranslation(mTouchedView); return ev.getActionMasked() == MotionEvent.ACTION_UP && !mFalsingManager.isUnlockingDisabled() - && !isFalseGesture() && (swipedFastEnough() || swipedFarEnough()) + && !isFalseGesture() && isSwipeDismissible() && mCallback.canChildBeDismissedInDirection(mTouchedView, translation > 0); } + /** Can the swipe gesture on the touched view be considered as a dismiss intention */ + public boolean isSwipeDismissible() { + if (magneticNotificationSwipes()) { + return mCallback.isMagneticViewDetached(mTouchedView) || swipedFastEnough(); + } else { + return swipedFastEnough() || swipedFarEnough(); + } + } + /** Returns true if the gesture should be rejected. */ public boolean isFalseGesture() { boolean falsingDetected = mCallback.isAntiFalsingNeeded(); @@ -970,6 +978,13 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { void onMagneticInteractionEnd(View view, float velocity); /** + * Determine if a view managed by magnetic interactions is magnetically detached + * @param view The magnetic view + * @return if the view is detached according to its magnetic state. + */ + boolean isMagneticViewDetached(View view); + + /** * Called when the child is long pressed and available to start drag and drop. * * @param v the view that was long pressed. diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 14b13d105482..24b955152093 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -286,7 +286,9 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mLaunchSourceId); final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS) .putExtra(Intent.EXTRA_COMPONENT_NAME, - ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); + ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()) + .setPackage(mQSSettingsPackageRepository.getSettingsPackageName()); + mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0, mDialogTransitionAnimator.createActivityTransitionController( dialog)); @@ -588,9 +590,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, com.android.internal.R.color.materialColorOnPrimaryContainer)); } text.setText(item.getToolName()); - Intent intent = item.getToolIntent() - .setPackage(mQSSettingsPackageRepository.getSettingsPackageName()); - + Intent intent = item.getToolIntent(); view.setOnClickListener(v -> { final String name = intent.getComponent() != null ? intent.getComponent().flattenToString() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt index 3287443f0405..60ca6e6e67a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt @@ -23,11 +23,15 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.shade.domain.interactor.ShadeModeInteractor import javax.inject.Inject @SysUISingleton @Stable -class DetailsViewModel @Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) { +class DetailsViewModel @Inject constructor( + val currentTilesInteractor: CurrentTilesInteractor, + val shadeModeInteractor: ShadeModeInteractor +) { /** * The current active [TileDetailsViewModel]. If it's `null`, it means the qs overlay is not @@ -52,6 +56,10 @@ class DetailsViewModel @Inject constructor(val currentTilesInteractor: CurrentTi * @see activeTileDetails */ fun onTileClicked(spec: TileSpec?): Boolean { + if (!shadeModeInteractor.isDualShade){ + return false + } + if (spec == null) { _activeTileDetails.value = null return false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS index d06f24fdb81b..ba4001014681 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS @@ -17,3 +17,4 @@ valiiftime@google.com yurilin@google.com per-file MediaNotificationProcessor.java = ethibodeau@google.com +per-file MagicActionBackgroundDrawable.kt = dupin@google.com diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java index f6535730cf77..8fc6cbe7c9e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java @@ -69,10 +69,13 @@ public class BundleEntry extends PipelineEntry { return mUnmodifiableChildren; } + void clearChildren() { + mChildren.clear(); + } + /** * @return Null because bundles do not have an associated NotificationEntry. */ - @Nullable @Override public NotificationEntry getRepresentativeEntry() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 5cea82140692..fe2bd345d559 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -567,7 +567,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { for (BundleEntry be : mIdToBundleEntry.values()) { be.beginNewAttachState(); - // TODO(b/399736937) Clear bundle children + be.clearChildren(); // BundleEntry has not representative summary so we do not need to clear it here. } mNotifList.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt index 2f0701f96f28..3747aba3a109 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.util.ArrayMap +import com.android.systemui.statusbar.notification.collection.BundleEntry import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -37,9 +38,17 @@ class GroupCountCoordinator @Inject constructor() : Coordinator { private fun onBeforeFinalizeFilter(entries: List<PipelineEntry>) { // save untruncated child counts to our internal map untruncatedChildCounts.clear() - entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry -> - untruncatedChildCounts[groupEntry] = groupEntry.children.size - } + entries.asSequence() + .flatMap { entry -> + when (entry) { + is GroupEntry -> listOf(entry) + is BundleEntry -> entry.children.filterIsInstance<GroupEntry>() + else -> emptyList() + } + } + .forEach { groupEntry -> + untruncatedChildCounts[groupEntry] = groupEntry.children.size + } } private fun onAfterRenderGroup(group: GroupEntry, controller: NotifGroupController) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 1be415d7bf47..20169ef481bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -36,6 +36,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.statusbar.notification.collection.BundleEntry; import com.android.systemui.statusbar.notification.collection.GroupEntry; +import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -307,8 +308,15 @@ public class PreparationCoordinator implements Coordinator { private void inflateAllRequiredViews(List<PipelineEntry> entries) { for (int i = 0, size = entries.size(); i < size; i++) { PipelineEntry entry = entries.get(i); - if (NotificationBundleUi.isEnabled() && entry instanceof BundleEntry) { - // TODO(b/399738511) Inflate bundle views. + if (NotificationBundleUi.isEnabled() && entry instanceof BundleEntry bundleEntry) { + for (ListEntry listEntry : bundleEntry.getChildren()) { + if (listEntry instanceof GroupEntry groupEntry) { + inflateRequiredGroupViews(groupEntry); + } else { + NotificationEntry notifEntry = (NotificationEntry) listEntry; + inflateRequiredNotifViews(notifEntry); + } + } } else if (entry instanceof GroupEntry) { GroupEntry groupEntry = (GroupEntry) entry; inflateRequiredGroupViews(groupEntry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 292f74a65554..f36a0cf51b97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.Flags.notificationColorUpdateLogger; import static com.android.systemui.Flags.physicalNotificationMovement; +import static java.lang.Math.abs; + import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Configuration; @@ -29,6 +31,7 @@ import android.util.FloatProperty; import android.util.IndentingPrintWriter; import android.util.Log; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.FrameLayout; @@ -110,14 +113,27 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro protected SpringAnimation mMagneticAnimator = new SpringAnimation( this /* object */, DynamicAnimation.TRANSLATION_X); + private int mTouchSlop; + protected MagneticRowListener mMagneticRowListener = new MagneticRowListener() { @Override - public void setMagneticTranslation(float translation) { - if (mMagneticAnimator.isRunning()) { - mMagneticAnimator.animateToFinalPosition(translation); - } else { + public void setMagneticTranslation(float translation, boolean trackEagerly) { + if (!mMagneticAnimator.isRunning()) { setTranslation(translation); + return; + } + + if (trackEagerly) { + float delta = abs(getTranslation() - translation); + if (delta > mTouchSlop) { + mMagneticAnimator.animateToFinalPosition(translation); + } else { + mMagneticAnimator.cancel(); + setTranslation(translation); + } + } else { + mMagneticAnimator.animateToFinalPosition(translation); } } @@ -183,6 +199,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro private void initDimens() { mContentShift = getResources().getDimensionPixelSize( R.dimen.shelf_transform_content_shift); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index 977936fa34fc..c03dc279888f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -46,7 +46,6 @@ import com.android.systemui.Flags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.res.R; import com.android.systemui.statusbar.AlphaOptimizedImageView; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; @@ -363,7 +362,9 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl final float dismissThreshold = getDismissThreshold(); final boolean snappingToDismiss = delta < -dismissThreshold || delta > dismissThreshold; if (mSnappingToDismiss != snappingToDismiss) { - getMenuView().performHapticFeedback(CLOCK_TICK); + if (!Flags.magneticNotificationSwipes()) { + getMenuView().performHapticFeedback(CLOCK_TICK); + } } mSnappingToDismiss = snappingToDismiss; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt index aa6951715755..48cff7497e3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt @@ -33,12 +33,12 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow interface MagneticNotificationRowManager { /** - * Set the swipe threshold in pixels. After crossing the threshold, the magnetic target detaches - * and the magnetic neighbors snap back. + * Notifies a change in the device density. The density can be used to compute the values of + * thresholds in pixels. * - * @param[threshold] Swipe threshold in pixels. + * @param[density] The device density. */ - fun setSwipeThresholdPx(thresholdPx: Float) + fun onDensityChange(density: Float) /** * Set the magnetic and roundable targets of a magnetic swipe interaction. @@ -87,6 +87,9 @@ interface MagneticNotificationRowManager { */ fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float? = null) + /** Determine if the given [ExpandableNotificationRow] has been magnetically detached. */ + fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean + /* Reset any roundness that magnetic targets may have */ fun resetRoundness() @@ -104,12 +107,15 @@ interface MagneticNotificationRowManager { /** Detaching threshold in dp */ const val MAGNETIC_DETACH_THRESHOLD_DP = 56 + /** Re-attaching threshold in dp */ + const val MAGNETIC_ATTACH_THRESHOLD_DP = 40 + /* An empty implementation of a manager */ @JvmStatic val Empty: MagneticNotificationRowManager get() = object : MagneticNotificationRowManager { - override fun setSwipeThresholdPx(thresholdPx: Float) {} + override fun onDensityChange(density: Float) {} override fun setMagneticAndRoundableTargets( swipingRow: ExpandableNotificationRow, @@ -127,6 +133,10 @@ interface MagneticNotificationRowManager { velocity: Float?, ) {} + override fun isMagneticRowSwipeDetached( + row: ExpandableNotificationRow + ): Boolean = false + override fun resetRoundness() {} override fun reset() {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt index 5a23f7cc2861..6e8b2226b4f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt @@ -47,6 +47,7 @@ constructor( private set private var magneticDetachThreshold = Float.POSITIVE_INFINITY + private var magneticAttachThreshold = 0f // Has the roundable target been set for the magnetic view that is being swiped. val isSwipedViewRoundableSet: Boolean @@ -57,13 +58,25 @@ constructor( SpringForce().setStiffness(DETACH_STIFFNESS).setDampingRatio(DETACH_DAMPING_RATIO) private val snapForce = SpringForce().setStiffness(SNAP_BACK_STIFFNESS).setDampingRatio(SNAP_BACK_DAMPING_RATIO) + private val attachForce = + SpringForce().setStiffness(ATTACH_STIFFNESS).setDampingRatio(ATTACH_DAMPING_RATIO) // Multiplier applied to the translation of a row while swiped val swipedRowMultiplier = MAGNETIC_TRANSLATION_MULTIPLIERS[MAGNETIC_TRANSLATION_MULTIPLIERS.size / 2] - override fun setSwipeThresholdPx(thresholdPx: Float) { - magneticDetachThreshold = thresholdPx + /** + * An offset applied to input translation that increases on subsequent re-attachments of a + * detached magnetic view. This helps keep computations consistent when the drag gesture input + * and the swiped notification don't share the same origin point after a re-attaching animation. + */ + private var translationOffset = 0f + + override fun onDensityChange(density: Float) { + magneticDetachThreshold = + density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + magneticAttachThreshold = + density * MagneticNotificationRowManager.MAGNETIC_ATTACH_THRESHOLD_DP } override fun setMagneticAndRoundableTargets( @@ -72,6 +85,7 @@ constructor( sectionsManager: NotificationSectionsManager, ) { if (currentState == State.IDLE) { + translationOffset = 0f updateMagneticAndRoundableTargets(swipingRow, stackScrollLayout, sectionsManager) currentState = State.TARGETS_SET } else { @@ -121,36 +135,36 @@ constructor( val canTargetBeDismissed = currentMagneticListeners.swipedListener()?.canRowBeDismissed() ?: false + val correctedTranslation = translation - translationOffset when (currentState) { State.IDLE -> { logger.logMagneticRowTranslationNotSet(currentState, row.getLoggingKey()) return false } State.TARGETS_SET -> { - pullTargets(translation, canTargetBeDismissed) + pullTargets(correctedTranslation, canTargetBeDismissed) currentState = State.PULLING } State.PULLING -> { - updateRoundness(translation) + updateRoundness(correctedTranslation) if (canTargetBeDismissed) { - pullDismissibleRow(translation) + pullDismissibleRow(correctedTranslation) } else { - pullTargets(translation, canSwipedBeDismissed = false) + pullTargets(correctedTranslation, canSwipedBeDismissed = false) } } State.DETACHED -> { - val swiped = currentMagneticListeners.swipedListener() - swiped?.setMagneticTranslation(translation) + translateDetachedRow(correctedTranslation) } } return true } - private fun updateRoundness(translation: Float) { + private fun updateRoundness(translation: Float, animate: Boolean = false) { val normalizedTranslation = abs(swipedRowMultiplier * translation) / magneticDetachThreshold notificationRoundnessManager.setRoundnessForAffectedViews( /* roundness */ normalizedTranslation.coerceIn(0f, MAX_PRE_DETACH_ROUNDNESS), - /* animate */ false, + animate, ) } @@ -232,7 +246,28 @@ constructor( ) } + private fun translateDetachedRow(translation: Float) { + val targetTranslation = swipedRowMultiplier * translation + val crossedThreshold = abs(targetTranslation) <= magneticAttachThreshold + if (crossedThreshold) { + translationOffset += translation + updateRoundness(translation = 0f, animate = true) + currentMagneticListeners.swipedListener()?.let { attach(it) } + currentState = State.PULLING + } else { + val swiped = currentMagneticListeners.swipedListener() + swiped?.setMagneticTranslation(translation, trackEagerly = false) + } + } + + private fun attach(listener: MagneticRowListener) { + listener.cancelMagneticAnimations() + listener.triggerMagneticForce(endTranslation = 0f, attachForce) + msdlPlayer.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR) + } + override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) { + translationOffset = 0f if (row.isSwipedTarget()) { when (currentState) { State.PULLING -> { @@ -254,9 +289,13 @@ constructor( } } + override fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean = + row.isSwipedTarget() && currentState == State.DETACHED + override fun resetRoundness() = notificationRoundnessManager.clear() override fun reset() { + translationOffset = 0f currentMagneticListeners.forEach { it?.cancelMagneticAnimations() it?.cancelTranslationAnimations() @@ -300,6 +339,8 @@ constructor( private const val DETACH_DAMPING_RATIO = 0.95f private const val SNAP_BACK_STIFFNESS = 550f private const val SNAP_BACK_DAMPING_RATIO = 0.6f + private const val ATTACH_STIFFNESS = 800f + private const val ATTACH_DAMPING_RATIO = 0.95f // Maximum value of corner roundness that gets applied during the pre-detach dragging private const val MAX_PRE_DETACH_ROUNDNESS = 0.8f diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt index 5959ef1e093b..344dab4369f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt @@ -21,8 +21,17 @@ import androidx.dynamicanimation.animation.SpringForce /** A listener that responds to magnetic forces applied to an [ExpandableNotificationRow] */ interface MagneticRowListener { - /** Set a translation due to a magnetic attachment. */ - fun setMagneticTranslation(translation: Float) + /** + * Set a translation due to a magnetic attachment. + * + * If a magnetic animation is running, [trackEagerly] decides if the new translation is applied + * immediately or if the animation finishes first. When applying the translation immediately, + * the change in translation must be greater than a touch slop threshold. + * + * @param[translation] Incoming gesture translation. + * @param[trackEagerly] Whether we eagerly track the incoming translation or not. + */ + fun setMagneticTranslation(translation: Float, trackEagerly: Boolean = true) /** * Trigger the magnetic behavior when the row detaches or snaps back from its magnetic diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index f3d8ee245540..612c19fc6696 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -486,15 +486,22 @@ public class NotificationStackScrollLayoutController implements Dumpable { } @Override + public boolean isMagneticViewDetached(View view) { + if (view instanceof ExpandableNotificationRow row) { + return mMagneticNotificationRowManager.isMagneticRowSwipeDetached(row); + } else { + return false; + } + } + + @Override public float getTotalTranslationLength(View animView) { return mView.getTotalTranslationLength(animView); } @Override public void onDensityScaleChange(float density) { - mMagneticNotificationRowManager.setSwipeThresholdPx( - density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP - ); + mMagneticNotificationRowManager.onDensityChange(density); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index c5a846e1da05..5105e55b0a5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -255,12 +255,13 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc int menuSnapTarget = menuRow.getMenuSnapTarget(); boolean isNonFalseMenuRevealingGesture = isMenuRevealingGestureAwayFromMenu && !isFalseGesture(); + boolean isMagneticViewDetached = mCallback.isMagneticViewDetached(animView); if ((isNonDismissGestureTowardsMenu || isNonFalseMenuRevealingGesture) && menuSnapTarget != 0) { // Menu has not been snapped to previously and this is menu revealing gesture snapOpen(animView, menuSnapTarget, velocity); menuRow.onSnapOpen(); - } else if (isDismissGesture && !gestureTowardsMenu) { + } else if (isDismissGesture && (!gestureTowardsMenu || isMagneticViewDetached)) { dismiss(animView, velocity); menuRow.onDismiss(); } else { @@ -272,6 +273,7 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc private void handleSwipeFromOpenState(MotionEvent ev, View animView, float velocity, NotificationMenuRowPlugin menuRow) { boolean isDismissGesture = isDismissGesture(ev); + boolean isMagneticViewDetached = mCallback.isMagneticViewDetached(animView); final boolean withinSnapMenuThreshold = menuRow.isWithinSnapMenuThreshold(); @@ -280,7 +282,7 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc // Haven't moved enough to unsnap from the menu menuRow.onSnapOpen(); snapOpen(animView, menuRow.getMenuSnapTarget(), velocity); - } else if (isDismissGesture && !menuRow.shouldSnapBack()) { + } else if (isDismissGesture && (!menuRow.shouldSnapBack() || isMagneticViewDetached)) { // Only dismiss if we're not moving towards the menu dismiss(animView, velocity); menuRow.onDismiss(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt index dc22905ba320..56fd270dffb7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt @@ -18,5 +18,11 @@ package com.android.systemui.qs.panels.ui.viewmodel import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor +import com.android.systemui.shade.domain.interactor.shadeModeInteractor -val Kosmos.detailsViewModel by Kosmos.Fixture { DetailsViewModel(currentTilesInteractor) } +val Kosmos.detailsViewModel by Kosmos.Fixture { + DetailsViewModel( + currentTilesInteractor, + shadeModeInteractor + ) +} diff --git a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java index 94cef418b6c8..edcf5748a8fc 100644 --- a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java @@ -17,6 +17,7 @@ package com.android.server.accessibility; import android.Manifest; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.SuppressLint; import android.app.Notification; @@ -149,11 +150,7 @@ public class HearingDevicePhoneCallNotificationController { } if (state == TelephonyManager.CALL_STATE_IDLE) { - if (mIsCommDeviceChangedRegistered) { - mIsCommDeviceChangedRegistered = false; - mAudioManager.removeOnCommunicationDeviceChangedListener( - mCommDeviceChangedListener); - } + removeOnCommunicationDeviceChangedListenerIfNeeded(mCommDeviceChangedListener); dismissNotificationIfNeeded(); if (mHearingDevice != null) { @@ -172,10 +169,8 @@ public class HearingDevicePhoneCallNotificationController { if (mHearingDevice != null) { showNotificationIfNeeded(); } else { - mAudioManager.addOnCommunicationDeviceChangedListener( - mCommDeviceChangedExecutor, + addOnCommunicationDeviceChangedListenerIfNeeded(mCommDeviceChangedExecutor, mCommDeviceChangedListener); - mIsCommDeviceChangedRegistered = true; } } else { mHearingDevice = getSupportedInputHearingDeviceInfo( @@ -187,6 +182,27 @@ public class HearingDevicePhoneCallNotificationController { } } + private void addOnCommunicationDeviceChangedListenerIfNeeded( + @NonNull @CallbackExecutor Executor executor, + @NonNull AudioManager.OnCommunicationDeviceChangedListener listener) { + if (mIsCommDeviceChangedRegistered) { + return; + } + + mIsCommDeviceChangedRegistered = true; + mAudioManager.addOnCommunicationDeviceChangedListener(executor, listener); + } + + private void removeOnCommunicationDeviceChangedListenerIfNeeded( + @NonNull AudioManager.OnCommunicationDeviceChangedListener listener) { + if (!mIsCommDeviceChangedRegistered) { + return; + } + + mAudioManager.removeOnCommunicationDeviceChangedListener(listener); + mIsCommDeviceChangedRegistered = false; + } + private void showNotificationIfNeeded() { if (mIsNotificationShown) { return; diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index a4cbf420b93b..193d82743a34 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -788,7 +788,6 @@ public class AdbDebuggingManager { // === Messages we can send to adbd =========== static final String MSG_DISCONNECT_DEVICE = "DD"; - static final String MSG_DISABLE_ADBDWIFI = "DA"; @Nullable @VisibleForTesting AdbKeyStore mAdbKeyStore; @@ -1093,9 +1092,6 @@ public class AdbDebuggingManager { setAdbConnectionInfo(null); mContext.unregisterReceiver(mBroadcastReceiver); - if (mThread != null) { - mThread.sendResponse(MSG_DISABLE_ADBDWIFI); - } onAdbdWifiServerDisconnected(-1); stopAdbDebuggingThread(); break; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index bf7f1946531c..6b3661a2a004 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -929,7 +929,8 @@ public class AudioService extends IAudioService.Stub private final Object mAbsoluteVolumeDeviceInfoMapLock = new Object(); // Devices where the framework sends a full scale audio signal, and controls the volume of // the external audio system separately. - // For possible volume behaviors, see {@link AudioManager.AbsoluteDeviceVolumeBehavior}. + // For possible volume behaviors, see + // {@link AudioDeviceVolumeManager.AbsoluteDeviceVolumeBehavior}. @GuardedBy("mAbsoluteVolumeDeviceInfoMapLock") Map<Integer, AbsoluteVolumeDeviceInfo> mAbsoluteVolumeDeviceInfoMap = new ArrayMap<>(); @@ -942,7 +943,7 @@ public class AudioService extends IAudioService.Stub private final List<VolumeInfo> mVolumeInfos; private final IAudioDeviceVolumeDispatcher mCallback; private final boolean mHandlesVolumeAdjustment; - private @AudioManager.AbsoluteDeviceVolumeBehavior int mDeviceVolumeBehavior; + private @AudioDeviceVolumeManager.AbsoluteDeviceVolumeBehavior int mDeviceVolumeBehavior; private AbsoluteVolumeDeviceInfo( AudioService parent, @@ -950,7 +951,7 @@ public class AudioService extends IAudioService.Stub List<VolumeInfo> volumeInfos, IAudioDeviceVolumeDispatcher callback, boolean handlesVolumeAdjustment, - @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) { + @AudioDeviceVolumeManager.AbsoluteDeviceVolumeBehavior int behavior) { this.mParent = parent; this.mDevice = device; this.mVolumeInfos = volumeInfos; @@ -8173,7 +8174,7 @@ public class AudioService extends IAudioService.Stub IAudioDeviceVolumeDispatcher cb, String packageName, AudioDeviceAttributes device, List<VolumeInfo> volumes, boolean handlesVolumeAdjustment, - @AudioManager.AbsoluteDeviceVolumeBehavior int deviceVolumeBehavior) { + @AudioDeviceVolumeManager.AbsoluteDeviceVolumeBehavior int deviceVolumeBehavior) { // verify permissions if (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED @@ -8240,12 +8241,13 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(anyOf = { MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED }) public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, - @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) { + @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior, + @Nullable String pkgName) { // verify permissions super.setDeviceVolumeBehavior_enforcePermission(); // verify arguments Objects.requireNonNull(device); - AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); + AudioDeviceVolumeManager.enforceValidVolumeBehavior(deviceVolumeBehavior); device = retrieveBluetoothAddress(device); @@ -8268,7 +8270,8 @@ public class AudioService extends IAudioService.Stub } private void setDeviceVolumeBehaviorInternal(@NonNull AudioDeviceAttributes device, - @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @NonNull String caller) { + @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior, + @NonNull String caller) { int audioSystemDeviceOut = device.getInternalType(); boolean volumeBehaviorChanged = false; // update device masks based on volume behavior @@ -8323,7 +8326,7 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(anyOf = { MODIFY_AUDIO_ROUTING, QUERY_AUDIO_STATE, MODIFY_AUDIO_SETTINGS_PRIVILEGED }) - public @AudioManager.DeviceVolumeBehavior + public @AudioDeviceVolumeManager.DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) { // verify permissions super.getDeviceVolumeBehavior_enforcePermission(); @@ -8335,7 +8338,7 @@ public class AudioService extends IAudioService.Stub return getDeviceVolumeBehaviorInt(device); } - private @AudioManager.DeviceVolumeBehavior + private @AudioDeviceVolumeManager.DeviceVolumeBehavior int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) { // Get the internal type set by the AudioDeviceAttributes constructor which is always more // exact (avoids double conversions) than a conversion from SDK type via @@ -15354,7 +15357,8 @@ public class AudioService extends IAudioService.Stub /** * Returns whether the input device uses absolute volume behavior, including its variants. - * For included volume behaviors, see {@link AudioManager.AbsoluteDeviceVolumeBehavior}. + * For included volume behaviors, see + * {@link AudioDeviceVolumeManager.AbsoluteDeviceVolumeBehavior}. * <p>This is distinct from Bluetooth A2DP absolute volume behavior * ({@link #isA2dpAbsoluteVolumeDevice}). */ @@ -15381,7 +15385,7 @@ public class AudioService extends IAudioService.Stub } private void persistDeviceVolumeBehavior(int deviceType, - @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior) { + @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior) { if (DEBUG_VOL) { Log.d(TAG, "Persisting Volume Behavior for DeviceType: " + deviceType); } @@ -15396,7 +15400,7 @@ public class AudioService extends IAudioService.Stub } } - @AudioManager.DeviceVolumeBehaviorState + @AudioDeviceVolumeManager.DeviceVolumeBehaviorState private int retrieveStoredDeviceVolumeBehavior(int deviceType) { return mSettings.getSystemIntForUser(mContentResolver, getSettingsNameForDeviceVolumeBehavior(deviceType), diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java index ab86433ca50d..62c3dbd3df8c 100644 --- a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java +++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java @@ -23,6 +23,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceVolumeManager; +import android.media.AudioManager; import android.media.VolumeInfo; import java.util.concurrent.Executor; @@ -53,7 +54,7 @@ public interface AudioDeviceVolumeManagerWrapper { /** * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior( - * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)} + * AudioDeviceAttributes, VolumeInfo, boolean, Executor, OnAudioDeviceVolumeChangedListener)} */ void setDeviceAbsoluteVolumeBehavior( @NonNull AudioDeviceAttributes device, @@ -64,7 +65,7 @@ public interface AudioDeviceVolumeManagerWrapper { /** * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeAdjustOnlyBehavior( - * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)} + * AudioDeviceAttributes, VolumeInfo, boolean, Executor, OnAudioDeviceVolumeChangedListener)} */ void setDeviceAbsoluteVolumeAdjustOnlyBehavior( @NonNull AudioDeviceAttributes device, @@ -72,4 +73,16 @@ public interface AudioDeviceVolumeManagerWrapper { boolean handlesVolumeAdjustment, @NonNull @CallbackExecutor Executor executor, @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener); + + /** + * Wraps {@link AudioDeviceVolumeManager#getDeviceVolumeBehavior(AudioDeviceAttributes)} + */ + @AudioManager.DeviceVolumeBehavior + int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device); + + /** + * Wraps {@link AudioDeviceVolumeManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int)} + */ + void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, + @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior); } diff --git a/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java index fd4dd516fd51..6d01e2da3e9d 100644 --- a/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java +++ b/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java @@ -85,18 +85,6 @@ public interface AudioManagerWrapper { void setWiredDeviceConnectionState(int device, int state, String address, String name); /** - * Wraps {@link AudioManager#getDeviceVolumeBehavior(AudioDeviceAttributes)} - */ - @AudioManager.DeviceVolumeBehavior - int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device); - - /** - * Wraps {@link AudioManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int)} - */ - void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, - @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior); - - /** * Wraps {@link AudioManager#getDevicesForAttributes(AudioAttributes)} */ @NonNull diff --git a/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java index 10cbb00d2398..02d8579f738f 100644 --- a/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java +++ b/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java @@ -16,11 +16,14 @@ package com.android.server.hdmi; +import static android.media.audio.Flags.unifyAbsoluteVolumeManagement; + import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.content.Context; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceVolumeManager; +import android.media.AudioManager; import android.media.VolumeInfo; import java.util.concurrent.Executor; @@ -38,9 +41,11 @@ public class DefaultAudioDeviceVolumeManagerWrapper private static final String TAG = "AudioDeviceVolumeManagerWrapper"; private final AudioDeviceVolumeManager mAudioDeviceVolumeManager; + private final AudioManager mAudioManager; public DefaultAudioDeviceVolumeManagerWrapper(Context context) { mAudioDeviceVolumeManager = new AudioDeviceVolumeManager(context); + mAudioManager = context.getSystemService(AudioManager.class); } @Override @@ -78,4 +83,24 @@ public class DefaultAudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeAdjustOnlyBehavior(device, volume, handlesVolumeAdjustment, executor, vclistener); } + + @Override + @AudioDeviceVolumeManager.DeviceVolumeBehavior + public int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) { + if (!unifyAbsoluteVolumeManagement()) { + int deviceBehavior = mAudioManager.getDeviceVolumeBehavior(device); + return deviceBehavior; + } + return mAudioDeviceVolumeManager.getDeviceVolumeBehavior(device); + } + + @Override + public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, + @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior) { + if (!unifyAbsoluteVolumeManagement()) { + int deviceBehavior = deviceVolumeBehavior; + mAudioManager.setDeviceVolumeBehavior(device, deviceBehavior); + } + mAudioDeviceVolumeManager.setDeviceVolumeBehavior(device, deviceVolumeBehavior); + } } diff --git a/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java b/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java index 061e145c27f3..662715420ec6 100644 --- a/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java +++ b/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java @@ -94,18 +94,6 @@ public class DefaultAudioManagerWrapper implements AudioManagerWrapper { } @Override - @AudioManager.DeviceVolumeBehavior - public int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) { - return mAudioManager.getDeviceVolumeBehavior(device); - } - - @Override - public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, - @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior) { - mAudioManager.setDeviceVolumeBehavior(device, deviceVolumeBehavior); - } - - @Override @NonNull public List<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes) { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index fdd0ef2f90e1..41b0b4dc716a 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -4595,7 +4595,7 @@ public class HdmiControlService extends SystemService { * Wrapper for {@link AudioManager#getDeviceVolumeBehavior} that takes advantage of cached * results for the volume behaviors of HDMI audio devices. */ - @AudioManager.DeviceVolumeBehavior + @AudioDeviceVolumeManager.DeviceVolumeBehavior private int getDeviceVolumeBehavior(AudioDeviceAttributes device) { if (AVB_AUDIO_OUTPUT_DEVICES.contains(device)) { synchronized (mLock) { @@ -4604,7 +4604,7 @@ public class HdmiControlService extends SystemService { } } } - return getAudioManager().getDeviceVolumeBehavior(device); + return getAudioDeviceVolumeManager().getDeviceVolumeBehavior(device); } /** @@ -4695,7 +4695,7 @@ public class HdmiControlService extends SystemService { // Condition 3: All AVB-capable audio outputs already use full/absolute volume behavior // We only need to check the first AVB-capable audio output because only TV panels // have more than one of them, and they always have the same volume behavior. - @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior = + @AudioDeviceVolumeManager.DeviceVolumeBehavior int currentVolumeBehavior = getDeviceVolumeBehavior(getAvbCapableAudioOutputDevices().get(0)); boolean alreadyUsingFullOrAbsoluteVolume = FULL_AND_ABSOLUTE_VOLUME_BEHAVIORS.contains(currentVolumeBehavior); @@ -4719,7 +4719,8 @@ public class HdmiControlService extends SystemService { // Condition 5: The System Audio device supports <Set Audio Volume Level> switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) { case DeviceFeatures.FEATURE_SUPPORTED: - if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { + if (currentVolumeBehavior + != AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { // Start an action that will call enableAbsoluteVolumeBehavior // once the System Audio device sends <Report Audio Status> localCecDevice.startNewAvbAudioStatusAction( @@ -4731,13 +4732,15 @@ public class HdmiControlService extends SystemService { // This allows the device to display numeric volume UI for the System Audio device. if (tv() != null && mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled) { if (currentVolumeBehavior - != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) { + != AudioDeviceVolumeManager + .DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) { // If we're currently using absolute volume behavior, switch to full volume // behavior until we successfully adopt adjust-only absolute volume behavior - if (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { + if (currentVolumeBehavior + == AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) { - getAudioManager().setDeviceVolumeBehavior(device, - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + getAudioDeviceVolumeManager().setDeviceVolumeBehavior(device, + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); } } // Start an action that will call enableAbsoluteVolumeBehavior @@ -4750,7 +4753,8 @@ public class HdmiControlService extends SystemService { } return; case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN: - if (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { + if (currentVolumeBehavior + == AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { switchToFullVolumeBehavior(); } localCecDevice.querySetAudioVolumeLevelSupport( @@ -4773,8 +4777,8 @@ public class HdmiControlService extends SystemService { for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) { if (ABSOLUTE_VOLUME_BEHAVIORS.contains(getDeviceVolumeBehavior(device))) { - getAudioManager().setDeviceVolumeBehavior(device, - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + getAudioDeviceVolumeManager().setDeviceVolumeBehavior(device, + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); } } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 233b577e1c61..33a7e7476cf6 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -7724,6 +7724,7 @@ public class UserManagerService extends IUserManager.Stub { pw.print(" Has profile owner: "); pw.println(mIsUserManaged.get(userId)); + pw.println(" Restrictions:"); synchronized (mRestrictionsLock) { UserRestrictionsUtils.dumpRestrictions( @@ -7756,6 +7757,9 @@ public class UserManagerService extends IUserManager.Stub { } } + pw.print(" Can have profile: "); + pw.println(userInfo.canHaveProfile()); + if (userData.userProperties != null) { userData.userProperties.println(pw, " "); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java index f413fe33c3f2..58f34d0404d0 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java @@ -36,7 +36,4 @@ public abstract class WallpaperManagerInternal { /** Notifies when the screen starts turning on and is not yet visible to the user. */ public abstract void onScreenTurningOn(int displayId); - - /** Notifies when the keyguard is going away. Sent right after the bouncer is gone. */ - public abstract void onKeyguardGoingAway(); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index e7da33d50b27..274175aa71ba 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -22,6 +22,7 @@ import static android.Manifest.permission.READ_WALLPAPER_INTERNAL; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.Flags.fixWallpaperChanged; import static android.app.Flags.liveWallpaperContentHandling; +import static android.app.Flags.notifyKeyguardEvents; import static android.app.Flags.removeNextWallpaperComponent; import static android.app.WallpaperManager.COMMAND_REAPPLY; import static android.app.WallpaperManager.FLAG_LOCK; @@ -1709,8 +1710,32 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper, mWallpaperCropper); LocalServices.addService(WallpaperManagerInternal.class, new LocalService()); + + LocalServices.getService(ActivityTaskManagerInternal.class) + .registerScreenObserver(mKeyguardObserver); + } + private final ActivityTaskManagerInternal.ScreenObserver mKeyguardObserver = + new ActivityTaskManagerInternal.ScreenObserver() { + @Override + public void onKeyguardStateChanged(boolean isShowing) { + if (!notifyKeyguardEvents()) { + return; + } + if (isShowing) { + notifyKeyguardAppearing(); + } else { + notifyKeyguardGoingAway(); + } + } + + @Override + public void onKeyguardGoingAway() { + notifyKeyguardGoingAway(); + } + }; + private final class LocalService extends WallpaperManagerInternal { @Override public void onDisplayAddSystemDecorations(int displayId) { @@ -1733,11 +1758,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onScreenTurningOn(int displayId) { notifyScreenTurningOn(displayId); } - - @Override - public void onKeyguardGoingAway() { - notifyKeyguardGoingAway(); - } } void initialize() { @@ -2571,6 +2591,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED; } + private boolean hasPermission(WallpaperData data, String permission) { + try { + return PackageManager.PERMISSION_GRANTED == mIPackageManager.checkPermission( + permission, + data.getComponent().getPackageName(), + data.userId); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to check wallpaper service permission", e); + return false; + } + } + private boolean hasAppOpPermission(String permission, int callingUid, String callingPackage, String attributionTag, String message) { final String op = AppOpsManager.permissionToOp(permission); @@ -2873,16 +2905,37 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * Propagate a keyguard going away event to the wallpaper engine. */ private void notifyKeyguardGoingAway() { + dispatchKeyguardCommand(WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY); + } + + /** + * Propagate a keyguard appearing event to the wallpaper engine. + */ + private void notifyKeyguardAppearing() { + dispatchKeyguardCommand(WallpaperManager.COMMAND_KEYGUARD_APPEARING); + } + + /** + * Propagate a keyguard-related event to the wallpaper engine. + * + * When the flag below is enabled, the event will only be dispatched in case the recipient + * has {@link android.Manifest.pertmission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE} permission. + */ + private void dispatchKeyguardCommand(String command) { synchronized (mLock) { for (WallpaperData data : getActiveWallpapers()) { + if (notifyKeyguardEvents() && !hasPermission( + data, android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)) { + continue; + } + data.connection.forEachDisplayConnector(displayConnector -> { if (displayConnector.mEngine != null) { try { displayConnector.mEngine.dispatchWallpaperCommand( - WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY, - -1, -1, -1, new Bundle()); + command, -1, -1, -1, new Bundle()); } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify that the keyguard is going away", e); + Slog.w(TAG, "Failed to dispatch wallpaper command: " + command, e); } } }); diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java index a731bf7c64b3..70fc6bace868 100644 --- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java +++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java @@ -82,6 +82,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, */ @VisibleForTesting static final int SNAPSHOT_MODE_NONE = 2; + static final float THEME_SNAPSHOT_MIN_Length = 128.0f; protected final WindowManagerService mService; protected final float mHighResSnapshotScale; @@ -436,14 +437,21 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, final Rect taskBounds = source.getBounds(); final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride(); final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState); + final int taskWidth = taskBounds.width(); + final int taskHeight = taskBounds.height(); + float scale = mHighResSnapshotScale; + if (Flags.reduceTaskSnapshotMemoryUsage()) { + final int minLength = Math.min(taskWidth, taskHeight); + if (THEME_SNAPSHOT_MIN_Length < minLength) { + scale = Math.min(THEME_SNAPSHOT_MIN_Length / minLength, scale); + } + } final SnapshotDrawerUtils.SystemBarBackgroundPainter decorPainter = new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags, attrs.privateFlags, attrs.insetsFlags.appearance, taskDescription, - mHighResSnapshotScale, mainWindow.getRequestedVisibleTypes()); - final int taskWidth = taskBounds.width(); - final int taskHeight = taskBounds.height(); - final int width = (int) (taskWidth * mHighResSnapshotScale); - final int height = (int) (taskHeight * mHighResSnapshotScale); + scale, mainWindow.getRequestedVisibleTypes()); + final int width = (int) (taskWidth * scale); + final int height = (int) (taskHeight * scale); final RenderNode node = RenderNode.create("SnapshotController", null); node.setLeftTopRightBottom(0, 0, width, height); node.setClipToBounds(false); diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index 5cc186c40b6c..a19f4388a7c8 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -272,6 +272,12 @@ class ActivityStartInterceptor { mActivityOptions = interceptResult.getActivityOptions(); mCallingPid = mRealCallingPid; mCallingUid = mRealCallingUid; + // When an activity launch is intercepted, Intent#prepareToLeaveProcess is not called + // since the interception happens in the system_server. So if any activity is calling + // a trampoline activity, the keys do not get collected. Since all the interceptors + // are present in the system_server, add the creator token before launching the + // intercepted intent. + mService.mAmInternal.addCreatorToken(mIntent, mCallingPackage); if (interceptResult.isActivityResolved()) { return true; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index c243cdc23137..21b730e13585 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -124,8 +124,9 @@ public abstract class ActivityTaskManagerInternal { public static final String ASSIST_KEY_RECEIVER_EXTRAS = "receiverExtras"; public interface ScreenObserver { - void onAwakeStateChanged(boolean isAwake); - void onKeyguardStateChanged(boolean isShowing); + default void onAwakeStateChanged(boolean isAwake) {} + default void onKeyguardStateChanged(boolean isShowing) {} + default void onKeyguardGoingAway() {} } /** diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 9c9656671519..46d24b0d3201 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -281,7 +281,6 @@ import com.android.server.sdksandbox.SdkSandboxManagerLocal; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.uri.UriGrantsManagerInternal; -import com.android.server.wallpaper.WallpaperManagerInternal; import com.android.server.wm.utils.WindowStyleCache; import com.android.wm.shell.Flags; @@ -373,7 +372,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private ComponentName mSysUiServiceComponent; private PermissionPolicyInternal mPermissionPolicyInternal; private StatusBarManagerInternal mStatusBarManagerInternal; - private WallpaperManagerInternal mWallpaperManagerInternal; private UserManagerInternal mUserManagerInternal; @VisibleForTesting final ActivityTaskManagerInternal mInternal; @@ -3719,10 +3717,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (isPowerModePreApplied && !foundResumed) { endPowerMode(POWER_MODE_REASON_START_ACTIVITY); } - } - WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal(); - if (wallpaperManagerInternal != null) { - wallpaperManagerInternal.onKeyguardGoingAway(); + + mH.post(() -> { + for (int i = mScreenObservers.size() - 1; i >= 0; i--) { + mScreenObservers.get(i).onKeyguardGoingAway(); + } + }); } } finally { Binder.restoreCallingIdentity(token); @@ -5573,13 +5573,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mStatusBarManagerInternal; } - WallpaperManagerInternal getWallpaperManagerInternal() { - if (mWallpaperManagerInternal == null) { - mWallpaperManagerInternal = LocalServices.getService(WallpaperManagerInternal.class); - } - return mWallpaperManagerInternal; - } - UserManagerInternal getUserManagerInternal() { if (mUserManagerInternal == null) { mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java index eafc8be7bf77..016cebac2b86 100644 --- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java +++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java @@ -24,6 +24,10 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.graphics.Bitmap; +import android.graphics.PixelFormat; +import android.hardware.HardwareBuffer; +import android.media.Image; +import android.media.ImageReader; import android.os.Process; import android.os.SystemClock; import android.os.Trace; @@ -33,10 +37,12 @@ import android.window.TaskSnapshot; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.TransitionAnimation; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider; import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto; +import com.android.window.flags.Flags; import java.io.File; import java.io.FileOutputStream; @@ -400,23 +406,20 @@ class SnapshotPersistQueue { Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mId); return false; } - final Bitmap bitmap = Bitmap.wrapHardwareBuffer( - mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace()); - if (bitmap == null) { - Slog.e(TAG, "Invalid task snapshot hw bitmap"); - return false; - } - final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */); + final HardwareBuffer hwBuffer = mSnapshot.getHardwareBuffer(); + final int width = hwBuffer.getWidth(); + final int height = hwBuffer.getHeight(); + final int pixelFormat = hwBuffer.getFormat(); + final Bitmap swBitmap = !Flags.reduceTaskSnapshotMemoryUsage() + || (pixelFormat != PixelFormat.RGB_565 && pixelFormat != PixelFormat.RGBA_8888) + || !mSnapshot.isRealSnapshot() + || TransitionAnimation.hasProtectedContent(hwBuffer) + ? copyToSwBitmapReadBack() + : copyToSwBitmapDirect(width, height, pixelFormat); if (swBitmap == null) { - Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() + ", isMutable=" - + bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed."); return false; } - final int width = bitmap.getWidth(); - final int height = bitmap.getHeight(); - bitmap.recycle(); - final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId); try (FileOutputStream fos = new FileOutputStream(file)) { swBitmap.compress(JPEG, COMPRESS_QUALITY, fos); @@ -448,6 +451,58 @@ class SnapshotPersistQueue { return true; } + private Bitmap copyToSwBitmapReadBack() { + final Bitmap bitmap = Bitmap.wrapHardwareBuffer( + mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace()); + if (bitmap == null) { + Slog.e(TAG, "Invalid task snapshot hw bitmap"); + return null; + } + + final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */); + if (swBitmap == null) { + Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() + + ", isMutable=" + bitmap.isMutable() + + ") to (config=ARGB_8888, isMutable=false) failed."); + return null; + } + bitmap.recycle(); + return swBitmap; + } + + /** + * Use ImageReader to create the software bitmap, so SkImage won't create an extra texture. + */ + private Bitmap copyToSwBitmapDirect(int width, int height, int pixelFormat) { + try (ImageReader ir = ImageReader.newInstance(width, height, + pixelFormat, 1 /* maxImages */)) { + ir.getSurface().attachAndQueueBufferWithColorSpace(mSnapshot.getHardwareBuffer(), + mSnapshot.getColorSpace()); + try (Image image = ir.acquireLatestImage()) { + if (image == null || image.getPlaneCount() < 1) { + Slog.e(TAG, "Image reader cannot acquire image"); + return null; + } + + final Image.Plane[] planes = image.getPlanes(); + if (planes.length != 1) { + Slog.e(TAG, "Image reader cannot get plane"); + return null; + } + final Image.Plane plane = planes[0]; + final int rowPadding = plane.getRowStride() - plane.getPixelStride() + * image.getWidth(); + final Bitmap swBitmap = Bitmap.createBitmap( + image.getWidth() + rowPadding / plane.getPixelStride() /* width */, + image.getHeight() /* height */, + pixelFormat == PixelFormat.RGB_565 + ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888); + swBitmap.copyPixelsFromBuffer(plane.getBuffer()); + return swBitmap; + } + } + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 2c10af4c5851..65001f436d3f 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -3401,7 +3401,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { * Applies the new configuration for the changed displays. Returns the activities that should * check whether to deliver the new configuration to clients. */ - @Nullable void applyDisplayChangeIfNeeded(@NonNull ArraySet<WindowContainer<?>> activitiesMayChange) { for (int i = mParticipants.size() - 1; i >= 0; --i) { final WindowContainer<?> wc = mParticipants.valueAt(i); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java index 8c09f26bb7fa..fdf78ad88311 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -56,12 +56,12 @@ import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.IActivityManager; import android.app.UiModeManager; +import android.app.compat.CompatChanges; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobWorkItem; import android.app.usage.UsageStatsManagerInternal; -import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -81,6 +81,7 @@ import android.os.BatteryManagerInternal.ChargingPolicyChangeListener; import android.os.Looper; import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.WorkSource; import android.os.WorkSource.WorkChain; @@ -98,6 +99,7 @@ import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.PowerAllowlistInternal; import com.android.server.SystemServiceManager; +import com.android.server.compat.PlatformCompat; import com.android.server.job.controllers.ConnectivityController; import com.android.server.job.controllers.JobStatus; import com.android.server.job.controllers.QuotaController; @@ -106,14 +108,10 @@ import com.android.server.job.restrictions.ThermalStatusRestriction; import com.android.server.pm.UserManagerInternal; import com.android.server.usage.AppStandbyInternal; -import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; -import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; - import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestRule; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; @@ -147,9 +145,6 @@ public class JobSchedulerServiceTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - @Rule - public TestRule compatChangeRule = new PlatformCompatChangeRule(); - private ChargingPolicyChangeListener mChargingPolicyChangeListener; private int mSourceUid; @@ -166,8 +161,10 @@ public class JobSchedulerServiceTest { mMockingSession = mockitoSession() .initMocks(this) .strictness(Strictness.LENIENT) + .mockStatic(CompatChanges.class) .mockStatic(LocalServices.class) .mockStatic(PermissionChecker.class) + .mockStatic(ServiceManager.class) .startMocking(); // Called in JobSchedulerService constructor. @@ -230,6 +227,9 @@ public class JobSchedulerServiceTest { ArgumentCaptor<ChargingPolicyChangeListener> chargingPolicyChangeListenerCaptor = ArgumentCaptor.forClass(ChargingPolicyChangeListener.class); + doReturn(mock(PlatformCompat.class)) + .when(() -> ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + mService = new TestJobSchedulerService(mContext); mService.waitOnAsyncLoadingForTesting(); @@ -1074,12 +1074,15 @@ public class JobSchedulerServiceTest { */ @Test @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) - @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) public void testGetRescheduleJobForFailure_abandonedJob() { final long nowElapsed = sElapsedRealtimeClock.millis(); final long initialBackoffMs = MINUTE_IN_MILLIS; mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3; + // Mock the OVERRIDE_HANDLE_ABANDONED_JOBS compat change overrides. + when(CompatChanges.isChangeEnabled( + eq(JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS), anyInt())).thenReturn(false); + JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo() .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR)); @@ -1148,8 +1151,10 @@ public class JobSchedulerServiceTest { */ @Test @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) - @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) public void testGetRescheduleJobForFailure_EnableFlagDisableCompatCheckAggressiveBackoff() { + // Mock the OVERRIDE_HANDLE_ABANDONED_JOBS compat change overrides. + when(CompatChanges.isChangeEnabled( + eq(JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS), anyInt())).thenReturn(false); assertFalse(mService.shouldUseAggressiveBackoff( mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF - 1, mSourceUid)); @@ -1167,8 +1172,10 @@ public class JobSchedulerServiceTest { */ @Test @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) - @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) public void testGetRescheduleJobForFailure_EnableFlagEnableCompatCheckAggressiveBackoff() { + // Mock the OVERRIDE_HANDLE_ABANDONED_JOBS compat change overrides. + when(CompatChanges.isChangeEnabled( + eq(JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS), anyInt())).thenReturn(true); assertFalse(mService.shouldUseAggressiveBackoff( mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF - 1, mSourceUid)); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 2d84887afb41..924fe9586af9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -76,6 +76,7 @@ import android.os.BatteryManagerInternal; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; @@ -93,6 +94,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.PowerAllowlistInternal; +import com.android.server.compat.PlatformCompat; import com.android.server.job.Flags; import com.android.server.job.JobSchedulerInternal; import com.android.server.job.JobSchedulerService; @@ -104,8 +106,6 @@ import com.android.server.job.controllers.QuotaController.TimedEvent; import com.android.server.job.controllers.QuotaController.TimingSession; import com.android.server.usage.AppStandbyInternal; -import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -168,6 +168,8 @@ public class QuotaControllerTest { private PowerAllowlistInternal mPowerAllowlistInternal; @Mock private UsageStatsManagerInternal mUsageStatsManager; + @Mock + private PlatformCompat mPlatformCompat; @Rule public final CheckFlagsRule mCheckFlagsRule = @@ -182,6 +184,7 @@ public class QuotaControllerTest { .strictness(Strictness.LENIENT) .spyStatic(DeviceConfig.class) .mockStatic(LocalServices.class) + .mockStatic(ServiceManager.class) .startMocking(); // Called in StateController constructor. @@ -198,6 +201,7 @@ public class QuotaControllerTest { } when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager); + doReturn(mActivityMangerInternal) .when(() -> LocalServices.getService(ActivityManagerInternal.class)); doReturn(mock(AppStandbyInternal.class)) @@ -253,6 +257,8 @@ public class QuotaControllerTest { ArgumentCaptor.forClass(PowerAllowlistInternal.TempAllowlistChangeListener.class); ArgumentCaptor<UsageStatsManagerInternal.UsageEventListener> ueListenerCaptor = ArgumentCaptor.forClass(UsageStatsManagerInternal.UsageEventListener.class); + doReturn(mPlatformCompat) + .when(() -> ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); mQuotaController = new QuotaController(mJobSchedulerService, mock(BackgroundJobsController.class), mock(ConnectivityController.class)); @@ -5591,13 +5597,17 @@ public class QuotaControllerTest { } @Test - @EnableCompatChanges({QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, - QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS}) @RequiresFlagsEnabled({Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS, Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS}) public void testTracking_OutOfQuota_ForegroundAndBackground_CompactChangeOverrides() { setDischarging(); + // Mock the OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS compat change overrides. + doReturn(true).when(mPlatformCompat).isChangeEnabledByUid( + eq(QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS), anyInt()); + doReturn(true).when(mPlatformCompat).isChangeEnabledByUid( + eq(QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS), anyInt()); + JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1); JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2); trackJobs(jobBg, jobTop); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index fc864dd230d9..3ed4a52bed23 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -343,12 +343,13 @@ public class BatteryStatsHistoryTest { assertThat(mReadFiles).containsExactly("123.bh", "1000.bh"); } else if (item.eventCode == HistoryItem.EVENT_ALARM) { eventsRead++; - assertThat(mReadFiles).containsExactly("123.bh", "1000.bh", "2000.bh"); + // This event is in the current buffer, so 2000.bh shouldn't be read from disk + assertThat(mReadFiles).containsExactly("123.bh", "1000.bh"); } } assertThat(eventsRead).isEqualTo(3); - assertThat(mReadFiles).containsExactly("123.bh", "1000.bh", "2000.bh", "3000.bh"); + assertThat(mReadFiles).containsExactly("123.bh", "1000.bh"); } @Test @@ -366,34 +367,41 @@ public class BatteryStatsHistoryTest { return invocation.callRealMethod(); }).when(mHistory).readFragmentToParcel(any(), any()); - BatteryStatsHistoryIterator iterator = mHistory.iterate(1000, 3000); + int eventsRead = 0; + BatteryStatsHistoryIterator iterator = mHistory.iterate(1001, 3000); while (iterator.hasNext()) { HistoryItem item = iterator.next(); if (item.eventCode == HistoryItem.EVENT_JOB_START) { fail("Event outside the range"); } else if (item.eventCode == HistoryItem.EVENT_JOB_FINISH) { + eventsRead++; assertThat(mReadFiles).containsExactly("1000.bh"); } else if (item.eventCode == HistoryItem.EVENT_ALARM) { fail("Event outside the range"); } } - assertThat(mReadFiles).containsExactly("1000.bh", "2000.bh"); + assertThat(eventsRead).isEqualTo(1); + assertThat(mReadFiles).containsExactly("1000.bh"); } private void prepareMultiFileHistory() { - mClock.realtime = 1000; - mClock.uptime = 1000; + mClock.realtime = 500; + mClock.uptime = 500; mHistory.recordEvent(mClock.realtime, mClock.uptime, BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42); + mClock.realtime = 1000; + mClock.uptime = 1000; mHistory.startNextFragment(mClock.realtime); // 1000.bh - mClock.realtime = 2000; - mClock.uptime = 2000; + mClock.realtime = 1500; + mClock.uptime = 1500; mHistory.recordEvent(mClock.realtime, mClock.uptime, BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42); + mClock.realtime = 2000; + mClock.uptime = 2000; mHistory.startNextFragment(mClock.realtime); // 2000.bh mClock.realtime = 3000; @@ -401,8 +409,8 @@ public class BatteryStatsHistoryTest { mHistory.recordEvent(mClock.realtime, mClock.uptime, HistoryItem.EVENT_ALARM, "alarm", 42); - // Flush accumulated history to disk - mHistory.startNextFragment(mClock.realtime); + // Back up accumulated history to disk + mHistory.writeHistory(); } private void verifyActiveFile(BatteryStatsHistory history, String file) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java index 3565244d90b3..33529c3f4375 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java @@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -207,6 +208,23 @@ public class HearingDevicePhoneCallNotificationControllerTest { eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH), any()); } + @Test + @EnableFlags(Flags.FLAG_HEARING_INPUT_CHANGE_WHEN_COMM_DEVICE) + public void onCallStateChanged_offHookMultiple_addListenerOnlyOneTime() { + AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS, + AudioManager.DEVICE_OUT_BLUETOOTH_A2DP); + when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn( + new AudioDeviceInfo[]{a2dpDeviceInfo}); + when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo); + + mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK); + mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK); + + verify(mAudioManager, times(1)).addOnCommunicationDeviceChangedListener( + any(Executor.class), + any(AudioManager.OnCommunicationDeviceChangedListener.class)); + } + private AudioDeviceInfo createAudioDeviceInfo(String address, int type) { AudioDevicePort audioDevicePort = mock(AudioDevicePort.class); doReturn(type).when(audioDevicePort).type(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java index b2d48a77386f..2349120f8e76 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java @@ -226,13 +226,16 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { */ protected void adoptFullVolumeBehaviorOnAvbCapableAudioOutputDevices() { if (getDeviceType() == HdmiDeviceInfo.DEVICE_PLAYBACK) { - mAudioManager.setDeviceVolumeBehavior(HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI, - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + mAudioDeviceVolumeManager.setDeviceVolumeBehavior( + HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI, + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); } else if (getDeviceType() == HdmiDeviceInfo.DEVICE_TV) { - mAudioManager.setDeviceVolumeBehavior(HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC, - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); - mAudioManager.setDeviceVolumeBehavior(HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_EARC, - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + mAudioDeviceVolumeManager.setDeviceVolumeBehavior( + HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC, + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); + mAudioDeviceVolumeManager.setDeviceVolumeBehavior( + HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_EARC, + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); } } @@ -307,8 +310,9 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(), INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute()); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); } protected void enableAdjustOnlyAbsoluteVolumeBehavior() { @@ -320,8 +324,9 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(), INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute()); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); } protected void verifyGiveAudioStatusNeverSent() { @@ -419,14 +424,16 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED); // AVB should not be enabled before receiving <Report Audio Status> - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); receiveReportAudioStatus(60, false); // Check that absolute volume behavior was the last one adopted - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); // Check that the volume and mute status received were included when setting AVB verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeBehavior( @@ -447,19 +454,22 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { enableSystemAudioModeIfNeeded(); receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); receiveReportAudioStatus(127, false); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); } @Test public void avbEnabled_standby_avbDisabled() { enableAbsoluteVolumeBehavior(); mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); } @Test @@ -468,8 +478,9 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_DISABLED); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); } @Test @@ -477,8 +488,9 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { enableAbsoluteVolumeBehavior(); receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); } @Test @@ -489,8 +501,9 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { getSystemAudioDeviceLogicalAddress(), getLogicalAddress(), Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE)); mTestLooper.dispatchAll(); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); } @Test @@ -501,8 +514,9 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { enableAbsoluteVolumeBehavior(); receiveSetSystemAudioMode(false); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); } @Test diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java index 4c12e436542b..7c7e2207c59c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java @@ -20,7 +20,7 @@ import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.media.AudioDeviceAttributes; -import android.media.AudioManager; +import android.media.AudioDeviceVolumeManager; import org.junit.Test; @@ -60,8 +60,8 @@ public abstract class BasePlaybackDeviceAvbTest extends BaseAbsoluteVolumeBehavi */ @Test public void savlNotSupported_allOtherConditionsMet_giveAudioStatusNotSent() { - mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(), - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + mAudioDeviceVolumeManager.setDeviceVolumeBehavior(getAudioOutputDevice(), + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED); enableSystemAudioModeIfNeeded(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java index f44517a47f55..7a4359889994 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java @@ -96,13 +96,15 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED); // Adjust-only AVB should not be enabled before receiving <Report Audio Status> - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); receiveReportAudioStatus(20, false); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior( eq(getAudioOutputDevice()), @@ -124,8 +126,9 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav receiveReportAudioStatus(40, true); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior( eq(getAudioOutputDevice()), @@ -149,8 +152,9 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav receiveReportAudioStatus(40, true); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior( eq(getAudioOutputDevice()), @@ -283,13 +287,15 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav verifyGiveAudioStatusSent(); // The device should use adjust-only AVB while waiting for <Report Audio Status> - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); // The device should switch to AVB upon receiving <Report Audio Status> receiveReportAudioStatus(60, false); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); } @@ -321,8 +327,9 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav mTestLooper.dispatchAll(); // The device should not switch away from adjust-only AVB - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); // The device should query support for <Set Audio Volume Level> again assertThat(mNativeWrapper.getResultMessages()).contains( diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java index 90f94cb4b596..e07f4f92084e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java @@ -23,6 +23,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceVolumeManager; import android.media.AudioManager; import android.media.AudioSystem; import android.media.VolumeInfo; @@ -137,18 +138,6 @@ public class FakeAudioFramework { // Do nothing } - - @Override - @AudioManager.DeviceVolumeBehavior - public int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) { - return mDeviceVolumeBehaviors.getOrDefault(device, DEFAULT_DEVICE_VOLUME_BEHAVIOR); - } - - public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, - @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior) { - setVolumeBehaviorHelper(device, deviceVolumeBehavior); - } - @Override @NonNull public List<AudioDeviceAttributes> getDevicesForAttributes( @@ -186,7 +175,8 @@ public class FakeAudioFramework { boolean handlesVolumeAdjustment, @NonNull @CallbackExecutor Executor executor, @NonNull OnAudioDeviceVolumeChangedListener vclistener) { - setVolumeBehaviorHelper(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); + setVolumeBehaviorHelper(device, + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); } @Override @@ -197,7 +187,19 @@ public class FakeAudioFramework { @NonNull @CallbackExecutor Executor executor, @NonNull OnAudioDeviceVolumeChangedListener vclistener) { setVolumeBehaviorHelper(device, - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + } + + @Override + @AudioDeviceVolumeManager.DeviceVolumeBehavior + public int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) { + return mDeviceVolumeBehaviors.getOrDefault(device, DEFAULT_DEVICE_VOLUME_BEHAVIOR); + } + + @Override + public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, + @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior) { + setVolumeBehaviorHelper(device, deviceVolumeBehavior); } } @@ -222,7 +224,7 @@ public class FakeAudioFramework { * Helper method for changing an audio device's volume behavior. Notifies listeners. */ private void setVolumeBehaviorHelper(AudioDeviceAttributes device, - @AudioManager.DeviceVolumeBehavior int newVolumeBehavior) { + @AudioDeviceVolumeManager.DeviceVolumeBehavior int newVolumeBehavior) { int currentVolumeBehavior = mDeviceVolumeBehaviors.getOrDefault( device, DEFAULT_DEVICE_VOLUME_BEHAVIOR); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java index 43ab804e04be..ffc1c62f79b3 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java @@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; -import android.media.AudioManager; +import android.media.AudioDeviceVolumeManager; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -64,8 +64,9 @@ public class PlaybackDeviceToAudioSystemAvbTest extends BasePlaybackDeviceAvbTes // Audio System disables System Audio Mode. AVB should be disabled. receiveSetSystemAudioMode(false); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); // TV reports support for <Set Audio Volume Level> mNativeWrapper.onCecMessage(ReportFeaturesMessage.build( @@ -85,7 +86,8 @@ public class PlaybackDeviceToAudioSystemAvbTest extends BasePlaybackDeviceAvbTes false)); mTestLooper.dispatchAll(); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java index 9b343e34706a..092618072744 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.clearInvocations; import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; -import android.media.AudioManager; +import android.media.AudioDeviceVolumeManager; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -65,8 +65,9 @@ public class PlaybackDeviceToTvAvbTest extends BasePlaybackDeviceAvbTest { // Audio System enables System Audio Mode. AVB should be disabled. receiveSetSystemAudioMode(true); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL); clearInvocations(mAudioManager, mAudioDeviceVolumeManager); @@ -88,7 +89,8 @@ public class PlaybackDeviceToTvAvbTest extends BasePlaybackDeviceAvbTest { false)); mTestLooper.dispatchAll(); - assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( - AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); + assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior( + getAudioOutputDevice())).isEqualTo( + AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java index d1b2e8e6d868..1fb84113e278 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java @@ -274,7 +274,7 @@ public class UserManagerServiceUserInfoTest { /** Test UserInfo.canHaveProfile for main user */ @Test public void testCanHaveProfile() throws Exception { - UserInfo userInfo = createUser(100, FLAG_MAIN, null); + UserInfo userInfo = createUser(100, FLAG_FULL | FLAG_MAIN, null); assertTrue("Main users can have profile", userInfo.canHaveProfile()); } |