diff options
200 files changed, 3083 insertions, 1495 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 fbc8eefdd945..216bbab882a9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -34264,6 +34264,7 @@ package android.os { method public boolean hasFileDescriptors(); method public boolean hasFileDescriptors(int, int); method public byte[] marshall(); + method @FlaggedApi("android.os.parcel_marshall_bytebuffer") public void marshall(@NonNull java.nio.ByteBuffer); method @NonNull public static android.os.Parcel obtain(); method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder); method @Deprecated @Nullable public Object[] readArray(@Nullable ClassLoader); @@ -34333,6 +34334,7 @@ package android.os { method public void setDataSize(int); method public void setPropagateAllowBlocking(); method public void unmarshall(@NonNull byte[], int, int); + method @FlaggedApi("android.os.parcel_marshall_bytebuffer") public void unmarshall(@NonNull java.nio.ByteBuffer); method public void writeArray(@Nullable Object[]); method public void writeBinderArray(@Nullable android.os.IBinder[]); method public void writeBinderList(@Nullable java.util.List<android.os.IBinder>); 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/ActivityManager.java b/core/java/android/app/ActivityManager.java index 62816a2fa0f5..9cc7b8ff47c2 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1188,7 +1188,7 @@ public class ActivityManager { /** @hide Should this process state be considered jank perceptible? */ public static final boolean isProcStateJankPerceptible(int procState) { - if (Flags.jankPerceptibleNarrow()) { + if (Flags.jankPerceptibleNarrow() && !Flags.jankPerceptibleNarrowHoldback()) { return procState == PROCESS_STATE_PERSISTENT_UI || procState == PROCESS_STATE_TOP || procState == PROCESS_STATE_IMPORTANT_FOREGROUND diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 96b50960f0a2..f44c2305591d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4014,7 +4014,7 @@ public final class ActivityThread extends ClientTransactionHandler return VM_PROCESS_STATE_JANK_PERCEPTIBLE; } - if (Flags.jankPerceptibleNarrow()) { + if (Flags.jankPerceptibleNarrow() && !Flags.jankPerceptibleNarrowHoldback()) { // Unlike other persistent processes, system server is often on // the critical path for application startup. Mark it explicitly // as jank perceptible regardless of processState. diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 8af5b1bd40f8..19fecb9bf7c2 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -101,14 +101,20 @@ public class Instrumentation { */ public static final String REPORT_KEY_STREAMRESULT = "stream"; - static final String TAG = "Instrumentation"; + /** + * @hide + */ + public static final String TAG = "Instrumentation"; private static final long CONNECT_TIMEOUT_MILLIS = 60_000; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); - // If set, will print the stack trace for activity starts within the process - static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE && + /** + * If set, will print the stack trace for activity starts within the process + * @hide + */ + public static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE && SystemProperties.getBoolean("persist.wm.debug.start_activity", false); static final boolean DEBUG_FINISH_ACTIVITY = Build.IS_DEBUGGABLE && SystemProperties.getBoolean("persist.wm.debug.finish_activity", false); diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java index afe915eece26..dd87d288566e 100644 --- a/core/java/android/app/PictureInPictureParams.java +++ b/core/java/android/app/PictureInPictureParams.java @@ -594,7 +594,7 @@ public final class PictureInPictureParams implements Parcelable { * @see PictureInPictureParams.Builder#setSeamlessResizeEnabled(boolean) */ public boolean isSeamlessResizeEnabled() { - return mSeamlessResizeEnabled == null ? true : mSeamlessResizeEnabled; + return mSeamlessResizeEnabled == null ? false : mSeamlessResizeEnabled; } /** 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/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 720e045dc944..e431426d5d09 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -163,3 +163,10 @@ flag { description: "Narrow the scope of Jank Perceptible" bug: "304837972" } + +flag { + name: "jank_perceptible_narrow_holdback" + namespace: "system_performance" + description: "Holdback study for jank_perceptible_narrow" + bug: "304837972" +} diff --git a/core/java/android/app/admin/StringSetIntersection.java b/core/java/android/app/admin/StringSetIntersection.java new file mode 100644 index 000000000000..5f2031e93ad9 --- /dev/null +++ b/core/java/android/app/admin/StringSetIntersection.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Set; + +/** + * Class to identify a intersection resolution mechanism for {@code Set<String>} policies, it's + * used to resolve the enforced policy when being set by multiple admins (see {@link + * PolicyState#getResolutionMechanism()}). + * + * @hide + */ +public final class StringSetIntersection extends ResolutionMechanism<Set<String>> { + + /** + * Intersection resolution for policies represented {@code Set<String>} which resolves as the + * intersection of all sets. + */ + @NonNull + public static final StringSetIntersection STRING_SET_INTERSECTION = new StringSetIntersection(); + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + return o != null && getClass() == o.getClass(); + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public String toString() { + return "StringSetIntersection {}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) {} + + @NonNull + public static final Parcelable.Creator<StringSetIntersection> CREATOR = + new Parcelable.Creator<StringSetIntersection>() { + @Override + public StringSetIntersection createFromParcel(Parcel source) { + return new StringSetIntersection(); + } + + @Override + public StringSetIntersection[] newArray(int size) { + return new StringSetIntersection[size]; + } + }; +} diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 572bffe6c6a4..b87ef7061c39 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -412,3 +412,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "use_policy_intersection_for_permitted_input_methods" + namespace: "enterprise" + description: "When deciding on permitted input methods, use policy intersection instead of last recorded policy." + bug: "340914586" + metadata { + purpose: PURPOSE_BUGFIX + } +} 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/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 2e7bc6d9b9f7..84d96bd1e155 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -3504,6 +3504,10 @@ public class InputMethodService extends AbstractInputMethodService { mInlineSuggestionSessionController.notifyOnStartInputView(); onStartInputView(mInputEditorInfo, restarting); startExtractingText(true); + // Back callback is typically registered in {@link #showWindow()}, but it's possible + // for {@link #doStartInput()} to be called without {@link #showWindow()} so we also + // register here. + registerDefaultOnBackInvokedCallback(); } else if (mCandidatesVisibility == View.VISIBLE) { if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView"); mCandidatesViewStarted = true; 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/os/Parcel.java b/core/java/android/os/Parcel.java index 6cb49b3ea166..4a9928532b93 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,6 +28,7 @@ import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.AppOpsManager; import android.compat.annotation.UnsupportedAppUsage; +import android.os.Flags; import android.ravenwood.annotation.RavenwoodClassLoadHook; import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.ravenwood.annotation.RavenwoodReplace; @@ -837,9 +839,8 @@ public final class Parcel { * @param buffer The ByteBuffer to write the data to. * @throws ReadOnlyBufferException if the buffer is read-only. * @throws BufferOverflowException if the buffer is too small. - * - * @hide */ + @FlaggedApi(Flags.FLAG_PARCEL_MARSHALL_BYTEBUFFER) public final void marshall(@NonNull ByteBuffer buffer) { if (buffer == null) { throw new NullPointerException(); @@ -875,9 +876,8 @@ public final class Parcel { * Fills the raw bytes of this Parcel with data from the supplied buffer. * * @param buffer will read buffer.remaining() bytes from the buffer. - * - * @hide */ + @FlaggedApi(Flags.FLAG_PARCEL_MARSHALL_BYTEBUFFER) public final void unmarshall(@NonNull ByteBuffer buffer) { if (buffer == null) { throw new NullPointerException(); diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 86acb2b21cfa..0150d171d51c 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -354,6 +354,15 @@ flag { flag { namespace: "system_performance" + name: "parcel_marshall_bytebuffer" + is_exported: true + description: "Parcel marshal/unmarshall APIs that use ByteBuffer." + is_fixed_read_only: true + bug: "401362825" +} + +flag { + namespace: "system_performance" name: "perfetto_sdk_tracing" description: "Tracing using Perfetto SDK." bug: "303199244" diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 2ed9c3a48add..8f7f94196eb1 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -16,6 +16,7 @@ package android.window; +import static android.app.Instrumentation.DEBUG_START_ACTIVITY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; @@ -32,6 +33,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TestApi; +import android.app.Instrumentation; import android.app.PendingIntent; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; @@ -45,6 +47,7 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import android.util.Log; import android.view.InsetsFrameProvider; import android.view.InsetsSource; import android.view.SurfaceControl; @@ -642,6 +645,10 @@ public final class WindowContainerTransaction implements Parcelable { */ @NonNull public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) { + if (DEBUG_START_ACTIVITY) { + Log.d(Instrumentation.TAG, "WCT.startTask: taskId=" + taskId + + " options=" + options, new Throwable()); + } mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options)); return this; } @@ -655,11 +662,15 @@ public final class WindowContainerTransaction implements Parcelable { */ @NonNull public WindowContainerTransaction sendPendingIntent(@Nullable PendingIntent sender, - @Nullable Intent intent, @Nullable Bundle options) { + @Nullable Intent fillInIntent, @Nullable Bundle options) { + if (DEBUG_START_ACTIVITY) { + Log.d(Instrumentation.TAG, "WCT.sendPendingIntent: sender=" + sender.getIntent() + + " fillInIntent=" + fillInIntent + " options=" + options, new Throwable()); + } mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT) .setLaunchOptions(options) .setPendingIntent(sender) - .setActivityIntent(intent) + .setActivityIntent(fillInIntent) .build()); return this; } @@ -674,6 +685,10 @@ public final class WindowContainerTransaction implements Parcelable { @NonNull public WindowContainerTransaction startShortcut(@NonNull String callingPackage, @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) { + if (DEBUG_START_ACTIVITY) { + Log.d(Instrumentation.TAG, "WCT.startShortcut: shortcutInfo=" + shortcutInfo + + " options=" + options, new Throwable()); + } mHierarchyOps.add(HierarchyOp.createForStartShortcut( callingPackage, shortcutInfo, options)); return this; 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/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java index 3472d681a486..f6e2a4df8cca 100644 --- a/core/java/com/android/internal/widget/NotificationProgressBar.java +++ b/core/java/com/android/internal/widget/NotificationProgressBar.java @@ -78,6 +78,13 @@ public final class NotificationProgressBar extends ProgressBar implements @Nullable private List<DrawablePart> mProgressDrawableParts = null; + /** @see R.styleable#NotificationProgressBar_segMinWidth */ + private final float mSegMinWidth; + /** @see R.styleable#NotificationProgressBar_segSegGap */ + private final float mSegSegGap; + /** @see R.styleable#NotificationProgressBar_segPointGap */ + private final float mSegPointGap; + @Nullable private Drawable mTracker = null; private boolean mHasTrackerIcon = false; @@ -128,6 +135,10 @@ public final class NotificationProgressBar extends ProgressBar implements Log.e(TAG, "Can't get NotificationProgressDrawable", ex); } + mSegMinWidth = a.getDimension(R.styleable.NotificationProgressBar_segMinWidth, 0f); + mSegSegGap = a.getDimension(R.styleable.NotificationProgressBar_segSegGap, 0f); + mSegPointGap = a.getDimension(R.styleable.NotificationProgressBar_segPointGap, 0f); + // Supports setting the tracker in xml, but ProgressStyle notifications set/override it // via {@code #setProgressTrackerIcon}. final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker); @@ -444,30 +455,26 @@ public final class NotificationProgressBar extends ProgressBar implements return; } - final float segSegGap = mNotificationProgressDrawable.getSegSegGap(); - final float segPointGap = mNotificationProgressDrawable.getSegPointGap(); final float pointRadius = mNotificationProgressDrawable.getPointRadius(); mProgressDrawableParts = processPartsAndConvertToDrawableParts( mParts, width, - segSegGap, - segPointGap, + mSegSegGap, + mSegPointGap, pointRadius, mHasTrackerIcon, mTrackerDrawWidth ); - final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth(); final float progressFraction = getProgressFraction(); final boolean isStyledByProgress = mProgressModel.isStyledByProgress(); - final float progressGap = - mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap(); + final float progressGap = mHasTrackerIcon ? 0F : mSegSegGap; Pair<List<DrawablePart>, Float> p = null; try { p = maybeStretchAndRescaleSegments( mParts, mProgressDrawableParts, - segmentMinWidth, + mSegMinWidth, pointRadius, progressFraction, isStyledByProgress, @@ -492,11 +499,11 @@ public final class NotificationProgressBar extends ProgressBar implements mProgressModel.getProgress(), getMax(), width, - segSegGap, - segPointGap, + mSegSegGap, + mSegPointGap, pointRadius, mHasTrackerIcon, - segmentMinWidth, + mSegMinWidth, isStyledByProgress, mTrackerDrawWidth); } catch (NotEnoughWidthToFitAllPartsException ex) { @@ -521,11 +528,11 @@ public final class NotificationProgressBar extends ProgressBar implements mProgressModel.getProgress(), getMax(), width, - segSegGap, - segPointGap, + mSegSegGap, + mSegPointGap, pointRadius, mHasTrackerIcon, - segmentMinWidth, + mSegMinWidth, isStyledByProgress, mTrackerDrawWidth); } catch (NotEnoughWidthToFitAllPartsException ex) { diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java index b1096107f04b..32b283af29c5 100644 --- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java +++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java @@ -84,27 +84,6 @@ public final class NotificationProgressDrawable extends Drawable { } /** - * Returns the gap between two segments. - */ - public float getSegSegGap() { - return mState.mSegSegGap; - } - - /** - * Returns the gap between a segment and a point. - */ - public float getSegPointGap() { - return mState.mSegPointGap; - } - - /** - * Returns the gap between a segment and a point. - */ - public float getSegmentMinWidth() { - return mState.mSegmentMinWidth; - } - - /** * Returns the radius for the points. */ public float getPointRadius() { @@ -241,11 +220,6 @@ public final class NotificationProgressDrawable extends Drawable { mState.setDensity(resolveDensity(r, 0)); - final TypedArray a = obtainAttributes(r, theme, attrs, - R.styleable.NotificationProgressDrawable); - updateStateFromTypedArray(a); - a.recycle(); - inflateChildElements(r, parser, attrs, theme); updateLocalState(); @@ -262,13 +236,6 @@ public final class NotificationProgressDrawable extends Drawable { state.setDensity(resolveDensity(t.getResources(), 0)); - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes( - state.mThemeAttrs, R.styleable.NotificationProgressDrawable); - updateStateFromTypedArray(a); - a.recycle(); - } - applyThemeChildElements(t); updateLocalState(); @@ -279,21 +246,6 @@ public final class NotificationProgressDrawable extends Drawable { return (mState.canApplyTheme()) || super.canApplyTheme(); } - private void updateStateFromTypedArray(TypedArray a) { - final State state = mState; - - // Account for any configuration changes. - state.mChangingConfigurations |= a.getChangingConfigurations(); - - // Extract the theme attributes, if any. - state.mThemeAttrs = a.extractThemeAttrs(); - - state.mSegSegGap = a.getDimension(R.styleable.NotificationProgressDrawable_segSegGap, - state.mSegSegGap); - state.mSegPointGap = a.getDimension(R.styleable.NotificationProgressDrawable_segPointGap, - state.mSegPointGap); - } - private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { TypedArray a; @@ -357,8 +309,6 @@ public final class NotificationProgressDrawable extends Drawable { // Extract the theme attributes, if any. state.mThemeAttrsSegments = a.extractThemeAttrs(); - state.mSegmentMinWidth = a.getDimension( - R.styleable.NotificationProgressDrawableSegments_minWidth, state.mSegmentMinWidth); state.mSegmentHeight = a.getDimension( R.styleable.NotificationProgressDrawableSegments_height, state.mSegmentHeight); state.mFadedSegmentHeight = a.getDimension( @@ -588,9 +538,6 @@ public final class NotificationProgressDrawable extends Drawable { static final class State extends ConstantState { @Config int mChangingConfigurations; - float mSegSegGap = 0.0f; - float mSegPointGap = 0.0f; - float mSegmentMinWidth = 0.0f; float mSegmentHeight; float mFadedSegmentHeight; float mSegmentCornerRadius; @@ -610,9 +557,6 @@ public final class NotificationProgressDrawable extends Drawable { State(@NonNull State orig, @Nullable Resources res) { mChangingConfigurations = orig.mChangingConfigurations; - mSegSegGap = orig.mSegSegGap; - mSegPointGap = orig.mSegPointGap; - mSegmentMinWidth = orig.mSegmentMinWidth; mSegmentHeight = orig.mSegmentHeight; mFadedSegmentHeight = orig.mFadedSegmentHeight; mSegmentCornerRadius = orig.mSegmentCornerRadius; @@ -631,18 +575,6 @@ public final class NotificationProgressDrawable extends Drawable { } private void applyDensityScaling(int sourceDensity, int targetDensity) { - if (mSegSegGap > 0) { - mSegSegGap = scaleFromDensity( - mSegSegGap, sourceDensity, targetDensity); - } - if (mSegPointGap > 0) { - mSegPointGap = scaleFromDensity( - mSegPointGap, sourceDensity, targetDensity); - } - if (mSegmentMinWidth > 0) { - mSegmentMinWidth = scaleFromDensity( - mSegmentMinWidth, sourceDensity, targetDensity); - } if (mSegmentHeight > 0) { mSegmentHeight = scaleFromDensity( mSegmentHeight, sourceDensity, targetDensity); 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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e16ce9849ff2..9e0200481421 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5394,13 +5394,13 @@ corresponding permission such as {@link #HEAD_TRACKING} or {@link #FACE_TRACKING} for the data being accessed. - <p>Protection level: normal|appop + <p>Protection level: signature|privileged @SystemApi @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) @hide --> <permission android:name="android.permission.XR_TRACKING_IN_BACKGROUND" - android:protectionLevel="normal|appop" + android:protectionLevel="signature|privileged" android:description="@string/permdesc_xr_tracking_in_background" android:label="@string/permlab_xr_tracking_in_background" android:featureFlag="android.xr.xr_manifest_entries" /> diff --git a/core/res/res/drawable/notification_progress.xml b/core/res/res/drawable/notification_progress.xml index ff5450ee106f..92a0a6a4e21b 100644 --- a/core/res/res/drawable/notification_progress.xml +++ b/core/res/res/drawable/notification_progress.xml @@ -19,12 +19,9 @@ android:gravity="center_vertical|fill_horizontal"> <com.android.internal.widget.NotificationProgressDrawable android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:segSegGap="@dimen/notification_progress_segSeg_gap" - android:segPointGap="@dimen/notification_progress_segPoint_gap"> + android:layout_height="wrap_content"> <segments android:color="?attr/colorProgressBackgroundNormal" - android:minWidth="@dimen/notification_progress_segments_min_width" android:height="@dimen/notification_progress_segments_height" android:fadedHeight="@dimen/notification_progress_segments_faded_height" android:cornerRadius="@dimen/notification_progress_segments_corner_radius"/> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index d2c993aecb0d..647e3dc2d268 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5573,6 +5573,14 @@ <!-- @hide internal use only --> <declare-styleable name="NotificationProgressBar"> + <!-- Minimum required drawing width for segments. The drawing width refers to the width + after the original segments have been adjusted for the neighboring Points and gaps. + This is enforced by stretching the segments that are too short. --> + <attr name="segMinWidth" format="dimension" /> + <!-- The gap between two segments. --> + <attr name="segSegGap" format="dimension" /> + <!-- The gap between a segment and a point. --> + <attr name="segPointGap" format="dimension" /> <!-- Draws the tracker on a NotificationProgressBar. --> <attr name="tracker" format="reference" /> <!-- Height of the tracker. --> @@ -7580,25 +7588,9 @@ <!-- NotificationProgressDrawable class --> <!-- ================================== --> - <!-- Drawable used to render a notification progress bar, with segments and points. --> - <!-- @hide internal use only --> - <declare-styleable name="NotificationProgressDrawable"> - <!-- The gap between two segments. --> - <attr name="segSegGap" format="dimension" /> - <!-- The gap between a segment and a point. --> - <attr name="segPointGap" format="dimension" /> - </declare-styleable> - <!-- Used to config the segments of a NotificationProgressDrawable. --> <!-- @hide internal use only --> <declare-styleable name="NotificationProgressDrawableSegments"> - <!-- TODO: b/390196782 - maybe move this to NotificationProgressBar, because that's the only - place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap - above. --> - <!-- Minimum required drawing width. The drawing width refers to the width after - the original segments have been adjusted for the neighboring Points and gaps. This is - enforced by stretching the segments that are too short. --> - <attr name="minWidth" /> <!-- Height of the solid segments. --> <attr name="height" /> <!-- Height of the faded segments. --> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 73681d26f297..8f13ee1ccb49 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -503,6 +503,9 @@ please see styles_device_defaults.xml. <style name="Widget.Material.Notification.ProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal" /> <style name="Widget.Material.Notification.NotificationProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal"> + <item name="segMinWidth">@dimen/notification_progress_segments_min_width</item> + <item name="segSegGap">@dimen/notification_progress_segSeg_gap</item> + <item name="segPointGap">@dimen/notification_progress_segPoint_gap</item> <item name="progressDrawable">@drawable/notification_progress</item> <item name="trackerHeight">@dimen/notification_progress_tracker_height</item> </style> 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/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index bb059108d4b6..3e6520106ab0 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -29,8 +29,6 @@ import android.util.Log; import androidx.test.ext.junit.runners.AndroidJUnit4; -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -418,63 +416,4 @@ public class ParcelTest { int binderEndPos = pA.dataPosition(); assertTrue(pA.hasBinders(binderStartPos, binderEndPos - binderStartPos)); } - - private static final byte[] TEST_DATA = new byte[] {4, 8, 15, 16, 23, 42}; - - // Allow for some Parcel overhead - private static final int TEST_DATA_LENGTH = TEST_DATA.length + 100; - - @Test - public void testMarshall_ByteBuffer_wrapped() { - ByteBuffer bb = ByteBuffer.allocate(TEST_DATA_LENGTH); - testMarshall_ByteBuffer(bb); - } - - @Test - public void testMarshall_DirectByteBuffer() { - ByteBuffer bb = ByteBuffer.allocateDirect(TEST_DATA_LENGTH); - testMarshall_ByteBuffer(bb); - } - - private void testMarshall_ByteBuffer(ByteBuffer bb) { - // Ensure that Parcel respects the starting offset by not starting at 0 - bb.position(1); - bb.mark(); - - // Parcel test data, then marshall into the ByteBuffer - Parcel p1 = Parcel.obtain(); - p1.writeByteArray(TEST_DATA); - p1.marshall(bb); - p1.recycle(); - - assertTrue(bb.position() > 1); - bb.reset(); - - // Unmarshall test data into a new Parcel - Parcel p2 = Parcel.obtain(); - bb.reset(); - p2.unmarshall(bb); - assertTrue(bb.position() > 1); - p2.setDataPosition(0); - byte[] marshalled = p2.marshall(); - - bb.reset(); - for (int i = 0; i < TEST_DATA.length; i++) { - assertEquals(bb.get(), marshalled[i]); - } - - byte[] testDataCopy = new byte[TEST_DATA.length]; - p2.setDataPosition(0); - p2.readByteArray(testDataCopy); - for (int i = 0; i < TEST_DATA.length; i++) { - assertEquals(TEST_DATA[i], testDataCopy[i]); - } - - // Test that overflowing the buffer throws an exception - bb.reset(); - // Leave certainly not enough room for the test data - bb.limit(bb.position() + TEST_DATA.length - 1); - assertThrows(BufferOverflowException.class, () -> p2.marshall(bb)); - p2.recycle(); - } } 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/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt index 14c15210252a..90011f4018f6 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt @@ -24,6 +24,9 @@ import android.graphics.PointF import android.graphics.Rect import android.os.Handler import android.os.UserManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.view.IWindowManager import android.view.MotionEvent import android.view.View @@ -36,6 +39,7 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService +import com.android.wm.shell.Flags import com.android.wm.shell.R import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.bubbles.Bubble @@ -64,6 +68,10 @@ import com.android.wm.shell.shared.TransactionPool import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils import com.android.wm.shell.shared.bubbles.BubbleBarLocation import com.android.wm.shell.shared.bubbles.DeviceConfig +import com.android.wm.shell.shared.bubbles.DragZone +import com.android.wm.shell.shared.bubbles.DragZoneFactory +import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode +import com.android.wm.shell.shared.bubbles.DraggedObject import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit @@ -88,6 +96,8 @@ class BubbleBarLayerViewTest { const val SCREEN_HEIGHT = 1000 } + @get:Rule val setFlagsRule = SetFlagsRule() + @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this) private val context = ApplicationProvider.getApplicationContext<Context>() @@ -101,6 +111,7 @@ class BubbleBarLayerViewTest { private lateinit var bgExecutor: TestShellExecutor private lateinit var bubbleLogger: BubbleLogger private lateinit var testBubblesList: MutableList<Bubble> + private lateinit var dragZoneFactory: DragZoneFactory @Before fun setUp() { @@ -134,6 +145,10 @@ class BubbleBarLayerViewTest { whenever(bubbleData.bubbles).thenReturn(testBubblesList) whenever(bubbleData.hasBubbles()).thenReturn(!testBubblesList.isEmpty()) + dragZoneFactory = DragZoneFactory(context, deviceConfig, + { SplitScreenMode.UNSUPPORTED }, + { false }) + bubbleController = createBubbleController( bubbleData, @@ -280,6 +295,7 @@ class BubbleBarLayerViewTest { assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) } + @DisableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_BUBBLE_ANYTHING) @Test fun testEventLogging_dragExpandedViewLeft() { val bubble = createBubble("first") @@ -287,7 +303,7 @@ class BubbleBarLayerViewTest { getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(bubble) - bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true) + bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */) } waitForExpandedViewAnimation() @@ -305,6 +321,7 @@ class BubbleBarLayerViewTest { assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) } + @DisableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_BUBBLE_ANYTHING) @Test fun testEventLogging_dragExpandedViewRight() { val bubble = createBubble("first") @@ -312,7 +329,7 @@ class BubbleBarLayerViewTest { getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(bubble) - bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true) + bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */) } waitForExpandedViewAnimation() @@ -330,6 +347,76 @@ class BubbleBarLayerViewTest { assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) } + @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE) + @Test + fun testEventLogging_dragExpandedViewLeft_bubbleAnything() { + val bubble = createBubble("first") + bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT + + getInstrumentation().runOnMainSync { + bubbleBarLayerView.showExpandedView(bubble) + bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */) + } + waitForExpandedViewAnimation() + + val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view) + assertThat(handleView).isNotNull() + + val dragZones = dragZoneFactory.createSortedDragZones( + DraggedObject.ExpandedView(BubbleBarLocation.RIGHT)) + val rightDragZone = dragZones.filterIsInstance<DragZone.Bubble.Right>().first() + val rightPoint = PointF(rightDragZone.bounds.centerX().toFloat(), + rightDragZone.bounds.centerY().toFloat()) + val leftDragZone = dragZones.filterIsInstance<DragZone.Bubble.Left>().first() + val leftPoint = PointF(leftDragZone.bounds.centerX().toFloat(), + leftDragZone.bounds.centerY().toFloat()) + + // Drag from right to left + handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, rightPoint) + handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, leftPoint) + handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, leftPoint) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE) + @Test + fun testEventLogging_dragExpandedViewRight_bubbleAnything() { + val bubble = createBubble("first") + bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT + + getInstrumentation().runOnMainSync { + bubbleBarLayerView.showExpandedView(bubble) + bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */) + } + waitForExpandedViewAnimation() + + val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view) + assertThat(handleView).isNotNull() + + val dragZones = dragZoneFactory.createSortedDragZones( + DraggedObject.ExpandedView(BubbleBarLocation.LEFT)) + val rightDragZone = dragZones.filterIsInstance<DragZone.Bubble.Right>().first() + val rightPoint = PointF(rightDragZone.bounds.centerX().toFloat(), + rightDragZone.bounds.centerY().toFloat()) + val leftDragZone = dragZones.filterIsInstance<DragZone.Bubble.Left>().first() + val leftPoint = PointF(leftDragZone.bounds.centerX().toFloat(), + leftDragZone.bounds.centerY().toFloat()) + + // Drag from left to right + handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, leftPoint) + handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, rightPoint) + handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, rightPoint) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + @Test fun testUpdateExpandedView_updateLocation() { bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT @@ -385,7 +472,7 @@ class BubbleBarLayerViewTest { bubbleLogger, ) // Mark visible so we don't wait for task view before animations can start - bubbleBarExpandedView.onContentVisibilityChanged(true) + bubbleBarExpandedView.onContentVisibilityChanged(true /* visible */) val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView) return FakeBubbleFactory.createChatBubble(context, key, viewInfo).also { diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml index 9bb51a87c08f..ef30d8965452 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml @@ -34,6 +34,7 @@ android:src="@drawable/bubble_ic_settings"/> <TextView + android:id="@+id/education_manage_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/bubble_popup_text_margin" @@ -45,6 +46,7 @@ android:text="@string/bubble_bar_education_manage_title"/> <TextView + android:id="@+id/education_manage_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/bubble_popup_text_margin" diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml index 1616707954f5..9076d6a87678 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml @@ -34,6 +34,7 @@ android:src="@drawable/ic_floating_landscape"/> <TextView + android:id="@+id/education_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/bubble_popup_text_margin" @@ -45,6 +46,7 @@ android:text="@string/bubble_bar_education_stack_title"/> <TextView + android:id="@+id/education_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/bubble_popup_text_margin" diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml index 225303b2d942..17ebac95e1dd 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml @@ -40,6 +40,7 @@ android:tint="@color/bubbles_icon_tint"/> <TextView + android:id="@+id/manage_dismiss" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" @@ -67,6 +68,7 @@ android:tint="@color/bubbles_icon_tint"/> <TextView + android:id="@+id/manage_dont_bubble" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 426c3ee5b853..290ef1633819 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -27,6 +27,7 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; +import static com.android.wm.shell.shared.TypefaceUtils.setTypeface; import android.annotation.NonNull; import android.annotation.SuppressLint; @@ -71,6 +72,7 @@ import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.common.AlphaOptimizedButton; import com.android.wm.shell.shared.TriangleShape; +import com.android.wm.shell.shared.TypefaceUtils; import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; import com.android.wm.shell.taskview.TaskView; @@ -551,6 +553,7 @@ public class BubbleExpandedView extends LinearLayout { mManageButton = (AlphaOptimizedButton) LayoutInflater.from(ctw).inflate( R.layout.bubble_manage_button, this /* parent */, false /* attach */); addView(mManageButton); + setTypeface(mManageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE); mManageButton.setVisibility(visibility); setManageClickListener(); post(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java index da6948d947d8..92007a4df71e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java @@ -50,6 +50,7 @@ import androidx.annotation.Nullable; import com.android.wm.shell.R; import com.android.wm.shell.shared.TriangleShape; +import com.android.wm.shell.shared.TypefaceUtils; /** * Flyout view that appears as a 'chat bubble' alongside the bubble stack. The flyout can visually @@ -165,8 +166,10 @@ public class BubbleFlyoutView extends FrameLayout { LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true); mFlyoutTextContainer = findViewById(R.id.bubble_flyout_text_container); mSenderText = findViewById(R.id.bubble_flyout_name); + TypefaceUtils.setTypeface(mSenderText, TypefaceUtils.FontFamily.GSF_LABEL_LARGE); mSenderAvatar = findViewById(R.id.bubble_flyout_avatar); mMessageText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text); + TypefaceUtils.setTypeface(mMessageText, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM); final Resources res = getResources(); mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java index 64f54b8ab5be..e901e0c07fe4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java @@ -46,6 +46,7 @@ import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ContrastColorUtil; import com.android.wm.shell.Flags; import com.android.wm.shell.R; +import com.android.wm.shell.shared.TypefaceUtils; import java.util.ArrayList; import java.util.List; @@ -234,6 +235,10 @@ public class BubbleOverflowContainerView extends LinearLayout { setBackgroundColor(bgColor); mEmptyStateTitle.setTextColor(textColor); mEmptyStateSubtitle.setTextColor(textColor); + TypefaceUtils.setTypeface(mEmptyStateTitle, + TypefaceUtils.FontFamily.GSF_BODY_MEDIUM_EMPHASIZED); + TypefaceUtils.setTypeface(mEmptyStateSubtitle, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM); + } public void updateFontSize() { @@ -322,6 +327,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V TextView viewName = overflowView.findViewById(R.id.bubble_view_name); viewName.setTextColor(textColor); + TypefaceUtils.setTypeface(viewName, TypefaceUtils.FontFamily.GSF_LABEL_LARGE); return new ViewHolder(overflowView, mPositioner); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index dd5a23aae7f9..3dce45690cf2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -89,6 +89,8 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout; import com.android.wm.shell.bubbles.animation.StackAnimationController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.shared.TypefaceUtils; +import com.android.wm.shell.shared.TypefaceUtils.FontFamily; import com.android.wm.shell.shared.animation.Interpolators; import com.android.wm.shell.shared.animation.PhysicsAnimator; import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; @@ -1397,6 +1399,14 @@ public class BubbleStackView extends FrameLayout // The menu itself should respect locale direction so the icons are on the correct side. mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE); addView(mManageMenu); + + // Doesn't seem to work unless view is added; so set font after. + TypefaceUtils.setTypeface(findViewById(R.id.manage_dismiss), FontFamily.GSF_LABEL_LARGE); + TypefaceUtils.setTypeface(findViewById(R.id.manage_dont_bubble), + FontFamily.GSF_LABEL_LARGE); + TypefaceUtils.setTypeface(mManageSettingsText, FontFamily.GSF_LABEL_LARGE); + TypefaceUtils.setTypeface(findViewById(R.id.bubble_manage_menu_fullscreen_title), + FontFamily.GSF_LABEL_LARGE); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt index 39a2a7b868a0..d2ad70886fa1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt @@ -27,6 +27,7 @@ import android.widget.Button import android.widget.LinearLayout import com.android.internal.R.color.system_neutral1_900 import com.android.wm.shell.R +import com.android.wm.shell.shared.TypefaceUtils import com.android.wm.shell.shared.animation.Interpolators /** @@ -53,6 +54,12 @@ class ManageEducationView( init { LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this) + TypefaceUtils.setTypeface(findViewById(R.id.user_education_title), + TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED) + TypefaceUtils.setTypeface(findViewById(R.id.user_education_description), + TypefaceUtils.FontFamily.GSF_BODY_MEDIUM) + TypefaceUtils.setTypeface(manageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED) + TypefaceUtils.setTypeface(gotItButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED) visibility = View.GONE elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt index 16606198b240..9ac059890dd7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt @@ -26,6 +26,7 @@ import android.widget.LinearLayout import android.widget.TextView import com.android.internal.util.ContrastColorUtil import com.android.wm.shell.R +import com.android.wm.shell.shared.TypefaceUtils import com.android.wm.shell.shared.animation.Interpolators /** @@ -59,6 +60,9 @@ class StackEducationView( init { LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this) + TypefaceUtils.setTypeface(titleTextView, + TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED) + TypefaceUtils.setTypeface(descTextView, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM) visibility = View.GONE elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt index 9d4f904e55d0..35435569d8b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt @@ -203,7 +203,11 @@ class BubbleBarExpandedViewDragController( draggedObject: MagnetizedObject<*>, ) { dragListener.onReleased(inDismiss = true) - pinController.onDragEnd() + if (dropTargetManager != null) { + dropTargetManager.onDragEnded() + } else { + pinController.onDragEnd() + } dismissView.hide() } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 3997412ab459..2cc9387bd1e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -147,15 +147,23 @@ public class BubbleBarLayerView extends FrameLayout Log.w(TAG, "dropped invalid bubble: " + mExpandedBubble); return; } + + final boolean isBubbleLeft = zone instanceof DragZone.Bubble.Left; + final boolean isBubbleRight = zone instanceof DragZone.Bubble.Right; + if (!isBubbleLeft && !isBubbleRight) { + // If we didn't finish the "change" animation make sure to animate + // it back to the right spot + locationChangeListener.onChange(mInitialLocation); + } if (zone instanceof DragZone.FullScreen) { ((Bubble) mExpandedBubble).getTaskView().moveToFullscreen(); // Make sure location change listener is updated with the initial // location -- even if we "switched sides" during the drag, since // we've ended up in fullscreen, the location shouldn't change. locationChangeListener.onRelease(mInitialLocation); - } else if (zone instanceof DragZone.Bubble.Left) { + } else if (isBubbleLeft) { locationChangeListener.onRelease(BubbleBarLocation.LEFT); - } else if (zone instanceof DragZone.Bubble.Right) { + } else if (isBubbleRight) { locationChangeListener.onRelease(BubbleBarLocation.RIGHT); } } @@ -189,7 +197,7 @@ public class BubbleBarLayerView extends FrameLayout @NonNull @Override public SplitScreenMode getSplitScreenMode() { - return SplitScreenMode.NONE; + return SplitScreenMode.UNSUPPORTED; } }, new DragZoneFactory.DesktopWindowModeChecker() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java index 6c14d83dfafa..bccc6dcd91db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java @@ -25,6 +25,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.wm.shell.R; +import com.android.wm.shell.shared.TypefaceUtils; /** * Bubble bar expanded view menu item view to display menu action details @@ -55,6 +56,7 @@ public class BubbleBarMenuItemView extends LinearLayout { super.onFinishInflate(); mImageView = findViewById(R.id.bubble_bar_menu_item_icon); mTextView = findViewById(R.id.bubble_bar_menu_item_title); + TypefaceUtils.setTypeface(mTextView, TypefaceUtils.FontFamily.GSF_TITLE_MEDIUM); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java index dfbf655bb6fc..7c0f8e138aae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java @@ -33,6 +33,7 @@ import androidx.core.widget.ImageViewCompat; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; +import com.android.wm.shell.shared.TypefaceUtils; import java.util.ArrayList; @@ -75,6 +76,7 @@ public class BubbleBarMenuView extends LinearLayout { mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section); mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon); mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title); + TypefaceUtils.setTypeface(mBubbleTitleView, TypefaceUtils.FontFamily.GSF_TITLE_MEDIUM); mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon); updateThemeColors(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt index 7adec39c9d66..0bd3a54ceeaa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt @@ -35,6 +35,7 @@ import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME import com.android.wm.shell.bubbles.BubbleEducationController import com.android.wm.shell.bubbles.BubbleViewProvider import com.android.wm.shell.bubbles.setup +import com.android.wm.shell.shared.TypefaceUtils import com.android.wm.shell.shared.animation.PhysicsAnimator import com.android.wm.shell.shared.bubbles.BubblePopupDrawable import com.android.wm.shell.shared.bubbles.BubblePopupView @@ -108,6 +109,10 @@ class BubbleEducationViewController(private val context: Context, private val li root.getBoundsOnScreen(rootBounds) educationView = createEducationView(R.layout.bubble_bar_stack_education, root).apply { + TypefaceUtils.setTypeface(findViewById(R.id.education_title), + TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED) + TypefaceUtils.setTypeface(findViewById(R.id.education_text), + TypefaceUtils.FontFamily.GSF_BODY_MEDIUM) setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN) updateEducationPosition(view = this, position, rootBounds) val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f @@ -153,6 +158,10 @@ class BubbleEducationViewController(private val context: Context, private val li educationView = createEducationView(R.layout.bubble_bar_manage_education, root).apply { + TypefaceUtils.setTypeface(findViewById(R.id.education_manage_title), + TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED) + TypefaceUtils.setTypeface(findViewById(R.id.education_manage_text), + TypefaceUtils.FontFamily.GSF_BODY_MEDIUM) pivotY = 0f doOnLayout { it.pivotX = it.width / 2f } setOnClickListener { hideEducation(animated = true) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md index dd5827af97d9..320de2ae5945 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md @@ -142,8 +142,8 @@ transaction which is applied. ## Tracing activity starts & finishes in the app process It's sometimes useful to know when to see a stack trace of when an activity starts in the app code -(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to -get this trace: +or via a `WindowContainerTransaction` (ie. if you are repro'ing a bug related to activity starts). +You can enable this system property to get this trace: ```shell # Enabling adb shell setprop persist.wm.debug.start_activity true @@ -168,6 +168,21 @@ adb shell setprop persist.wm.debug.finish_activity \"\" adb reboot ``` +## Tracing transition requests in the Shell + +To trace where a new WM transition is started in the Shell, you can enable this system property: +```shell +# Enabling +adb shell setprop persist.wm.debug.start_shell_transition true +adb reboot +adb logcat -s "ShellTransitions" + +# Disabling +adb shell setprop persist.wm.debug.start_shell_transition \"\" +adb reboot +``` + + ## Dumps Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index cef18f55b86d..c58bb6e3bd31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -40,7 +40,6 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; -import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -341,23 +340,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH return false; } - /** - * @return a change representing a config-at-end activity for a given parent. - */ - @Nullable - public TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info, - @android.annotation.NonNull WindowContainerToken parent) { - for (TransitionInfo.Change change : info.getChanges()) { - if (change.getTaskInfo() == null - && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END) - && change.getParent() != null && change.getParent().equals(parent)) { - return change; - } - } - return null; - } - - /** Whether a particular package is same as current pip package. */ public boolean isPackageActiveInPip(@Nullable String packageName) { // No-op, to be handled differently in PIP1 and PIP2 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index 880e143a0e13..92f36d08a941 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -43,6 +43,7 @@ import com.android.wm.shell.shared.annotations.ShellMainThread; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; /** * A Task Listener implementation used only for CUJs and trigger paths that cannot be initiated via @@ -114,6 +115,17 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, // Set the new params but make sure mPictureInPictureParams is not null. mPictureInPictureParams = params == null ? new PictureInPictureParams.Builder().build() : params; + logRemoteActions(mPictureInPictureParams); + } + + private void logRemoteActions(@android.annotation.NonNull PictureInPictureParams params) { + StringJoiner sj = new StringJoiner("|", "[", "]"); + if (params.hasSetActions()) { + params.getActions().forEach((action) -> sj.add(action.getTitle())); + } + + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "PIP remote actions=%s", sj.toString()); } /** Add a PipParamsChangedCallback listener. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index cfcd56393bc2..5d8d8b685a23 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -76,6 +76,7 @@ import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; import com.android.wm.shell.pip2.animation.PipEnterAnimator; import com.android.wm.shell.pip2.phone.transition.PipExpandHandler; +import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; @@ -387,8 +388,8 @@ public class PipTransition extends PipTransitionController implements mFinishCallback = finishCallback; // We expect the PiP activity as a separate change in a config-at-end transition; // only flings are not using config-at-end for resize bounds changes - TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info, - pipChange.getTaskInfo().getToken()); + TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange( + info, pipChange.getTaskInfo().getToken()); if (pipActivityChange != null) { // Transform calculations use PiP params by default, so make sure they are null to // default to using bounds for scaling calculations instead. @@ -427,8 +428,8 @@ public class PipTransition extends PipTransitionController implements } // We expect the PiP activity as a separate change in a config-at-end transition. - TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info, - pipChange.getTaskInfo().getToken()); + TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange( + info, pipChange.getTaskInfo().getToken()); if (pipActivityChange == null) { return false; } @@ -497,8 +498,8 @@ public class PipTransition extends PipTransitionController implements } // We expect the PiP activity as a separate change in a config-at-end transition. - TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info, - pipChange.getTaskInfo().getToken()); + TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange( + info, pipChange.getTaskInfo().getToken()); if (pipActivityChange == null) { return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java index 01cda6c91108..e562f3340217 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java @@ -67,6 +67,36 @@ public class PipTransitionUtils { } /** + * @return a change representing a config-at-end activity for ancestor. + */ + @Nullable + public static TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info, + @NonNull WindowContainerToken ancestor) { + final TransitionInfo.Change ancestorChange = + PipTransitionUtils.getChangeByToken(info, ancestor); + if (ancestorChange == null) return null; + + // Iterate through changes bottom-to-top, going up the parent chain starting with ancestor. + TransitionInfo.Change lastPipChildChange = ancestorChange; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == ancestorChange) continue; + + if (change.getParent() != null + && change.getParent().equals(lastPipChildChange.getContainer())) { + // Found a child of the last cached child along the ancestral chain. + lastPipChildChange = change; + if (change.getTaskInfo() == null + && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END)) { + // If this is a config-at-end activity change, then we found the chain leaf. + return change; + } + } + } + return null; + } + + /** * @return the leash to interact with the container this change represents. * @throws NullPointerException if the leash is null. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index 1853ffa96dfc..320a63a95302 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -34,6 +34,7 @@ import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.unfold.UnfoldTransitionHandler; @@ -132,7 +133,7 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { TransitionInfo.Change pipActivityChange = null; if (pipChange != null) { - pipActivityChange = mPipHandler.getDeferConfigActivityChange( + pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange( info, pipChange.getContainer()); everythingElse.getChanges().remove(pipActivityChange); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index e28a7fa159c5..003ef1d453fc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -39,6 +39,7 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived; import static com.android.window.flags.Flags.ensureWallpaperInTransitions; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; @@ -52,6 +53,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.database.ContentObserver; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -138,6 +140,10 @@ public class Transitions implements RemoteCallable<Transitions>, ShellCommandHandler.ShellCommandActionHandler { static final String TAG = "ShellTransitions"; + // If set, will print the stack trace for transition starts within the process + static final boolean DEBUG_START_TRANSITION = Build.IS_DEBUGGABLE && + SystemProperties.getBoolean("persist.wm.debug.start_shell_transition", false); + /** Set to {@code true} to enable shell transitions. */ public static final boolean ENABLE_SHELL_TRANSITIONS = getShellTransitEnabled(); public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS @@ -346,10 +352,10 @@ public class Transitions implements RemoteCallable<Transitions>, mShellController = shellController; // The very last handler (0 in the list) should be the default one. mHandlers.add(mDefaultTransitionHandler); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default"); + ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: Default"); // Next lowest priority is remote transitions. mHandlers.add(mRemoteTransitionHandler); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote"); + ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: Remote"); shellInit.addInitCallback(this::onInit, this); mHomeTransitionObserver = homeTransitionObserver; mFocusTransitionObserver = focusTransitionObserver; @@ -439,7 +445,7 @@ public class Transitions implements RemoteCallable<Transitions>, mHandlers.add(handler); // Set initial scale settings. handler.setAnimScaleSetting(mTransitionAnimationScaleSetting); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: %s", handler.getClass().getSimpleName()); } @@ -691,7 +697,7 @@ public class Transitions implements RemoteCallable<Transitions>, void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady"); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s", info.getDebugId(), transitionToken, info.toString(" " /* prefix */)); int activeIdx = findByToken(mPendingTransitions, transitionToken); if (activeIdx < 0) { @@ -753,7 +759,7 @@ public class Transitions implements RemoteCallable<Transitions>, if (tr.isIdle()) continue; hadPreceding = true; // Sleep starts a process of forcing all prior transitions to finish immediately - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + ProtoLog.v(WM_SHELL_TRANSITIONS, "Start finish-for-sync track %d", i); finishForSync(active.mToken, i, null /* forceFinish */); } @@ -797,7 +803,7 @@ public class Transitions implements RemoteCallable<Transitions>, if (info.getRootCount() == 0 && !KeyguardTransitionHandler.handles(info)) { // No root-leashes implies that the transition is empty/no-op, so just do // housekeeping and return. - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so" + ProtoLog.v(WM_SHELL_TRANSITIONS, "No transition roots in %s so" + " abort", active); onAbort(active); return true; @@ -839,7 +845,7 @@ public class Transitions implements RemoteCallable<Transitions>, && allOccluded)) { // Treat this as an abort since we are bypassing any merge logic and effectively // finishing immediately. - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + ProtoLog.v(WM_SHELL_TRANSITIONS, "Non-visible anim so abort: %s", active); onAbort(active); return true; @@ -873,7 +879,7 @@ public class Transitions implements RemoteCallable<Transitions>, void processReadyQueue(Track track) { if (track.mReadyTransitions.isEmpty()) { if (track.mActiveTransition == null) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Track %d became idle", + ProtoLog.v(WM_SHELL_TRANSITIONS, "Track %d became idle", mTracks.indexOf(track)); if (areTracksIdle()) { if (!mReadyDuringSync.isEmpty()) { @@ -885,7 +891,7 @@ public class Transitions implements RemoteCallable<Transitions>, if (!success) break; } } else if (mPendingTransitions.isEmpty()) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition " + ProtoLog.v(WM_SHELL_TRANSITIONS, "All active transition " + "animations finished"); mKnownTransitions.clear(); // Run all runnables from the run-when-idle queue. @@ -926,7 +932,7 @@ public class Transitions implements RemoteCallable<Transitions>, onMerged(playingToken, readyToken); return; } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while" + ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition %s ready while" + " %s is still animating. Notify the animating transition" + " in case they can be merged", ready, playing); mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId()); @@ -955,7 +961,7 @@ public class Transitions implements RemoteCallable<Transitions>, } final Track track = mTracks.get(playing.getTrack()); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s", merged, playing); int readyIdx = 0; if (track.mReadyTransitions.isEmpty() || track.mReadyTransitions.get(0) != merged) { @@ -996,7 +1002,7 @@ public class Transitions implements RemoteCallable<Transitions>, } private void playTransition(@NonNull ActiveTransition active) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active); + ProtoLog.v(WM_SHELL_TRANSITIONS, "Playing animation for %s", active); final var token = active.mToken; for (int i = 0; i < mObservers.size(); ++i) { @@ -1007,12 +1013,12 @@ public class Transitions implements RemoteCallable<Transitions>, // If a handler already chose to run this animation, try delegating to it first. if (active.mHandler != null) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, " try firstHandler %s", active.mHandler); boolean consumed = active.mHandler.startAnimation(token, active.mInfo, active.mStartT, active.mFinishT, (wct) -> onFinish(token, wct)); if (consumed) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler"); + ProtoLog.v(WM_SHELL_TRANSITIONS, " animated by firstHandler"); mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler); if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { Trace.instant(TRACE_TAG_WINDOW_MANAGER, @@ -1042,14 +1048,14 @@ public class Transitions implements RemoteCallable<Transitions>, ) { for (int i = mHandlers.size() - 1; i >= 0; --i) { if (mHandlers.get(i) == skip) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " skip handler %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, " skip handler %s", mHandlers.get(i)); continue; } boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT, finishCB); if (consumed) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, " animated by %s", mHandlers.get(i)); mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i)); if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { @@ -1155,7 +1161,7 @@ public class Transitions implements RemoteCallable<Transitions>, for (int i = 0; i < mObservers.size(); ++i) { mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted); } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished " + ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition animation finished " + "(aborted=%b), notifying core %s", active.mAborted, active); if (active.mStartT != null) { // Applied by now, so clear immediately to remove any references. Do not set to null @@ -1209,7 +1215,7 @@ public class Transitions implements RemoteCallable<Transitions>, void requestStartTransition(@NonNull IBinder transitionToken, @Nullable TransitionRequestInfo request) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s", + ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s", request.getDebugId(), transitionToken, request); if (mKnownTransitions.containsKey(transitionToken)) { throw new RuntimeException("Transition already started " + transitionToken); @@ -1228,6 +1234,8 @@ public class Transitions implements RemoteCallable<Transitions>, if (requestResult != null) { active.mHandler = requestResult.first; wct = requestResult.second; + ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition (#%d): request handled by %s", + request.getDebugId(), active.mHandler.getClass().getSimpleName()); } if (request.getDisplayChange() != null) { TransitionRequestInfo.DisplayChange change = request.getDisplayChange(); @@ -1273,8 +1281,12 @@ public class Transitions implements RemoteCallable<Transitions>, */ public IBinder startTransition(@WindowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition " + ProtoLog.v(WM_SHELL_TRANSITIONS, "Directly starting a new transition " + "type=%s wct=%s handler=%s", transitTypeToString(type), wct, handler); + if (DEBUG_START_TRANSITION) { + Log.d(TAG, "startTransition: type=" + transitTypeToString(type) + + " wct=" + wct + " handler=" + handler.getClass().getName(), new Throwable()); + } final ActiveTransition active = new ActiveTransition(mOrganizer.startNewTransition(type, wct)); active.mHandler = handler; @@ -1362,7 +1374,7 @@ public class Transitions implements RemoteCallable<Transitions>, } // Attempt to merge a SLEEP info to signal that the playing transition needs to // fast-forward. - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s" + ProtoLog.v(WM_SHELL_TRANSITIONS, " Attempt to merge sync %s" + " into %s via a SLEEP proxy", nextSync, playing); playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT, dummyT, playing.mToken, (wct) -> {}); @@ -1598,7 +1610,7 @@ public class Transitions implements RemoteCallable<Transitions>, public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT) throws RemoteException { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)", + ProtoLog.v(WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)", t.getId()); mMainExecutor.execute(() -> Transitions.this.onTransitionReady( iBinder, transitionInfo, t, finishT)); @@ -1784,8 +1796,9 @@ public class Transitions implements RemoteCallable<Transitions>, pw.println(prefix + TAG); final String innerPrefix = prefix + " "; - pw.println(prefix + "Handlers:"); - for (TransitionHandler handler : mHandlers) { + pw.println(prefix + "Handlers (ordered by priority):"); + for (int i = mHandlers.size() - 1; i >= 0; i--) { + final TransitionHandler handler = mHandlers.get(i); pw.print(innerPrefix); pw.print(handler.getClass().getSimpleName()); pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")"); 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 bcf9396ff0c1..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. @@ -1756,8 +1782,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId); disposeResizeVeil(); disposeStatusBarInputLayer(); - mWindowDecorViewHolder.close(); - mWindowDecorViewHolder = null; + if (mWindowDecorViewHolder != null) { + mWindowDecorViewHolder.close(); + mWindowDecorViewHolder = null; + } if (canEnterDesktopMode(mContext) && isEducationEnabled()) { notifyNoCaptionHandle(); } 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/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt index 509f4f202b6b..8e1cf167318e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt @@ -254,6 +254,16 @@ fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider( } } +/** + * Checks that surfaces are still within the expected region after snapping to a snap point. + * + * @param component The component we are checking (should be one of the two split apps) + * @param landscapePosLeft If [true], and device is in left/right split, app is on the left side of + * the screen. Has no meaning if device is in top/bottom split. + * @param portraitPosTop If [true], and device is in top/bottom split, app is on the top side of + * the screen. Has no meaning if device is in left/right split. + * @param rotation The rotation state of the display. + */ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( component: IComponentMatcher, landscapePosLeft: Boolean, @@ -268,10 +278,12 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( visibleRegion(component).isNotEmpty() visibleRegion(component) .coversAtMost( + // TODO (b/403082705): Should use the new method for determining left/right split. if (displayBounds.width() > displayBounds.height()) { if (landscapePosLeft) { Region( - 0, + // TODO (b/403304310): Check if we're in an offscreen-enabled mode. + -displayBounds.right, // the receding app can go offscreen 0, (dividerRegion.left + dividerRegion.right) / 2, displayBounds.bottom @@ -280,7 +292,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( Region( (dividerRegion.left + dividerRegion.right) / 2, 0, - displayBounds.right, + displayBounds.right * 2, // the receding app can go offscreen displayBounds.bottom ) } @@ -288,7 +300,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( if (portraitPosTop) { Region( 0, - 0, + -displayBounds.bottom, // the receding app can go offscreen displayBounds.right, (dividerRegion.top + dividerRegion.bottom) / 2 ) @@ -297,7 +309,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( 0, (dividerRegion.top + dividerRegion.bottom) / 2, displayBounds.right, - displayBounds.bottom + displayBounds.bottom * 2 // the receding app can go offscreen ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt index 49d6877a1654..e4183f16ba14 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt @@ -310,12 +310,18 @@ object SplitScreenUtils { } } + /** + * Drags the divider, then releases, making it snap to a new snap point. + */ fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) { + // Find the first display that is turned on (making the assumption that there is only one). val displayBounds = - wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace - ?: error("Display not found") + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual && it.isOn } + ?.layerStackSpace ?: error("Display not found") val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) - dividerBar.drag(Point(displayBounds.width() * 1 / 3, displayBounds.height() * 2 / 3), 200) + // Drag to a point on the lower left of the screen -- this will cause the divider to snap + // to the left- or bottom-side snap point, shrinking the "primary" test app. + dividerBar.drag(Point(displayBounds.width() * 1 / 4, displayBounds.height() * 3 / 4), 200) wmHelper .StateSyncBuilder() 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/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/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/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index d3fc91b65829..b3badd0bd51d 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -203,4 +203,11 @@ flag { description: "Initialize GraphicBufferAllocater on ViewRootImpl init, to avoid blocking on init during buffer allocation, improving app launch latency." bug: "389908734" is_fixed_read_only: true +} + +flag { + name: "bitmap_parcel_ashmem_as_immutable" + namespace: "system_performance" + description: "Whether to parcel implicit copies of bitmaps to ashmem as immutable" + bug: "400807118" }
\ No newline at end of file diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 27d4ac7cef4b..104ece6582f5 100644 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -28,8 +28,18 @@ #include "SkRefCnt.h" #include "SkStream.h" #include "SkTypes.h" +#include "android/binder_parcel.h" #include "android_nio_utils.h" +#ifdef __ANDROID__ +#include <com_android_graphics_hwui_flags.h> +namespace hwui_flags = com::android::graphics::hwui::flags; +#else +namespace hwui_flags { +constexpr bool bitmap_parcel_ashmem_as_immutable() { return false; } +} +#endif + #define DEBUG_PARCEL 0 static jclass gBitmap_class; @@ -841,6 +851,23 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { #endif } +// Returns whether this bitmap should be written to the parcel as mutable. +static bool shouldParcelAsMutable(SkBitmap& bitmap, AParcel* parcel) { + // If the bitmap is immutable, then parcel as immutable. + if (bitmap.isImmutable()) { + return false; + } + + if (!hwui_flags::bitmap_parcel_ashmem_as_immutable()) { + return true; + } + + // If we're going to copy the bitmap to ashmem and write that to the parcel, + // then parcel as immutable, since we won't be mutating the bitmap after + // writing it to the parcel. + return !shouldUseAshmem(parcel, bitmap.computeByteSize()); +} + static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jint density, jobject parcel) { #ifdef __linux__ // Only Linux support parcel @@ -855,7 +882,7 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, j auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle); bitmapWrapper->getSkBitmap(&bitmap); - p.writeInt32(!bitmap.isImmutable()); + p.writeInt32(shouldParcelAsMutable(bitmap, p.get())); p.writeInt32(bitmap.colorType()); p.writeInt32(bitmap.alphaType()); SkColorSpace* colorSpace = bitmap.colorSpace(); 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/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml b/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml new file mode 100644 index 000000000000..6b534aa9647d --- /dev/null +++ b/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2025 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="34dp" + android:height="42dp" + android:viewportWidth="34" + android:viewportHeight="42"> + <path + android:pathData="M0.856,17.569C0.887,19.083 1.004,20.593 1.206,22.094C2.166,28.584 5.804,35.937 15.774,41.089C16.171,41.293 16.61,41.4 17.056,41.4C17.503,41.4 17.942,41.293 18.339,41.089C28.309,35.936 31.947,28.583 32.907,22.093C33.109,20.593 33.226,19.083 33.256,17.569V8.605C33.257,7.919 33.046,7.25 32.652,6.688C32.259,6.127 31.703,5.7 31.059,5.467L18.191,0.8C17.458,0.534 16.655,0.534 15.922,0.8L3.054,5.467C2.41,5.7 1.854,6.127 1.461,6.688C1.067,7.25 0.856,7.919 0.856,8.605V17.569Z" + android:fillColor="#D1C2CB"/> + <path + android:pathData="M15.067,24.333V10.733H18.933V24.333H15.067ZM15.067,31.267V27.433H18.933V31.267H15.067Z" + android:fillColor="@color/settingslib_materialColorSurfaceContainerLowest"/> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml b/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml index 54860d4af20f..deda2586c2e0 100644 --- a/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml +++ b/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml @@ -21,6 +21,7 @@ <enum name="low" value="1"/> <enum name="medium" value="2"/> <enum name="high" value="3"/> + <enum name="off" value="4"/> </attr> <attr name="buttonLevel" format="enum"> <enum name="generic" value="0"/> diff --git a/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml b/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml index 19181dd55852..abc458bc1e27 100644 --- a/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml +++ b/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml @@ -22,4 +22,5 @@ <color name="settingslib_expressive_color_status_level_medium">#FCBD00</color> <!-- static palette red50 --> <color name="settingslib_expressive_color_status_level_high">#DB372D</color> + <color name="settingslib_expressive_color_status_level_off">#D1C2CB</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt index 1f8cfb5e432e..eda281c07053 100644 --- a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt +++ b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt @@ -40,7 +40,8 @@ class StatusBannerPreference @JvmOverloads constructor( GENERIC, LOW, MEDIUM, - HIGH + HIGH, + OFF } var iconLevel: BannerStatus = BannerStatus.GENERIC set(value) { @@ -87,6 +88,7 @@ class StatusBannerPreference @JvmOverloads constructor( 1 -> BannerStatus.LOW 2 -> BannerStatus.MEDIUM 3 -> BannerStatus.HIGH + 4 -> BannerStatus.OFF else -> BannerStatus.GENERIC } @@ -104,7 +106,10 @@ class StatusBannerPreference @JvmOverloads constructor( } (holder.findViewById(R.id.status_banner_button) as? MaterialButton)?.apply { - setBackgroundColor(getBackgroundColor(buttonLevel)) + setBackgroundColor( + if (buttonLevel == BannerStatus.OFF) getBackgroundColor(BannerStatus.GENERIC) + else getBackgroundColor(buttonLevel) + ) text = buttonText setOnClickListener(listener) visibility = if (listener != null) View.VISIBLE else View.GONE @@ -143,6 +148,11 @@ class StatusBannerPreference @JvmOverloads constructor( R.color.settingslib_expressive_color_status_level_high ) + BannerStatus.OFF -> ContextCompat.getColor( + context, + R.color.settingslib_expressive_color_status_level_off + ) + else -> ContextCompat.getColor( context, com.android.settingslib.widget.theme.R.color.settingslib_materialColorPrimary @@ -167,6 +177,11 @@ class StatusBannerPreference @JvmOverloads constructor( R.drawable.settingslib_expressive_icon_status_level_high ) + BannerStatus.OFF -> ContextCompat.getDrawable( + context, + R.drawable.settingslib_expressive_icon_status_level_off + ) + else -> null } } @@ -188,6 +203,7 @@ class StatusBannerPreference @JvmOverloads constructor( R.drawable.settingslib_expressive_background_level_high ) + // GENERIC and OFF are using the same background drawable. else -> ContextCompat.getDrawable( context, R.drawable.settingslib_expressive_background_generic diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 55f7317f25e4..758ad797f761 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -1015,6 +1015,10 @@ <uses-permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE" /> <uses-permission android:name="android.permission.READ_COLOR_ZONES" /> + <!-- Permissions required for CTS test - CtsModernMediaProviderTests --> + <uses-permission android:name="com.android.providers.media.permission.ACCESS_OEM_METADATA" /> + <uses-permission android:name="com.android.providers.media.permission.UPDATE_OEM_METADATA" /> + <!-- Permission required for trade-in mode testing --> <uses-permission android:name="android.permission.ENTER_TRADE_IN_MODE" /> diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 1362ffebe107..86559fd557d6 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -1,22 +1,6 @@ { - // Curious where your @Scenario tests are running? - // - // @Ignore: Will not run in any configuration - // - // @FlakyTest: Tests that don't block pre/postsubmit but are staged to run known failures. - // Tests will run in postsubmit on sysui-e2e-staged suite. - // - // - // @PlatinumTest: Marking your test with this annotation will put your tests in presubmit. - // Please DO NOT annotate new or old tests with @PlatinumTest annotation - // without discussing with mdb:android-platinum - // - // @Postsubmit: Do not use this annotation for e2e tests. This won't have any affect. - - // For all other e2e tests which are not platinum, they run in sysui-silver suite,that - // primarily runs in postsubmit with an exception to e2e test related changes. - // If you want to see one shot place to monitor all e2e tests, look for - // sysui-e2e-staged suite. + // Test mappings for SystemUI unit tests. + // For e2e mappings, see go/sysui-e2e-test-mapping // v2/android-virtual-infra/test_mapping/presubmit-avd "presubmit": [ diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 4693377654f8..436e92bc0efa 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -398,13 +398,6 @@ flag { } flag { - name: "light_reveal_migration" - namespace: "systemui" - description: "Move LightRevealScrim to recommended architecture" - bug: "281655028" -} - -flag { name: "theme_overlay_controller_wakefulness_deprecation" namespace: "systemui" description: "Replacing WakefulnessLifecycle by KeyguardTransitionInteractor in " @@ -2136,3 +2129,14 @@ flag { description: "Enables return animations for status bar chips" bug: "202516970" } + +flag { + name: "media_projection_grey_error_text" + namespace: "systemui" + description: "Set the error text color to grey when app sharing is hidden by the requesting app" + bug: "390624334" + metadata { + purpose: PURPOSE_BUGFIX + } +} + diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt index 82e5f5bb6dc8..cd9fcefcdda3 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt @@ -127,6 +127,8 @@ import kotlin.math.min * * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen * @sample com.android.systemui.compose.gallery.DialogLaunchScreen + * @param defaultMinSize true if a default minimum size should be enforced even if this Expandable + * isn't currently clickable and false otherwise. */ @Composable fun Expandable( @@ -140,6 +142,7 @@ fun Expandable( // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have // proven that the new implementation is robust. useModifierBasedImplementation: Boolean = false, + defaultMinSize: Boolean = true, transitionControllerFactory: ComposableControllerFactory? = null, content: @Composable (Expandable) -> Unit, ) { @@ -155,6 +158,7 @@ fun Expandable( onClick, interactionSource, useModifierBasedImplementation, + defaultMinSize, content, ) } @@ -182,6 +186,8 @@ fun Expandable( * * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen * @sample com.android.systemui.compose.gallery.DialogLaunchScreen + * @param defaultMinSize true if a default minimum size should be enforced even if this Expandable + * isn't currently clickable and false otherwise. */ @Composable fun Expandable( @@ -192,6 +198,7 @@ fun Expandable( // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have // proven that the new implementation is robust. useModifierBasedImplementation: Boolean = false, + defaultMinSize: Boolean = true, content: @Composable (Expandable) -> Unit, ) { val controller = controller as ExpandableControllerImpl @@ -209,7 +216,12 @@ fun Expandable( if (useModifierBasedImplementation) { Box(modifier.expandable(controller, onClick, interactionSource)) { - WrappedContent(controller.expandable, controller.contentColor, content) + WrappedContent( + controller.expandable, + controller.contentColor, + defaultMinSize = defaultMinSize, + content, + ) } return } @@ -221,7 +233,7 @@ fun Expandable( val wrappedContent = remember(content) { movableContentOf { expandable: Expandable -> - WrappedContent(expandable, contentColor, content) + WrappedContent(expandable, contentColor, defaultMinSize = defaultMinSize, content) } } @@ -306,21 +318,24 @@ fun Expandable( private fun WrappedContent( expandable: Expandable, contentColor: Color, + defaultMinSize: Boolean, content: @Composable (Expandable) -> Unit, ) { val minSizeContent = @Composable { - // We make sure that the content itself (wrapped by the background) is at least 40.dp, - // which is the same as the M3 buttons. This applies even if onClick is null, to make it - // easier to write expandables that are sometimes clickable and sometimes not. There - // shouldn't be any Expandable smaller than 40dp because if the expandable is not - // clickable directly, then something in its content should be (and with a size >= - // 40dp). - val minSize = 40.dp - Box( - Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize), - contentAlignment = Alignment.Center, - ) { + if (defaultMinSize) { + // We make sure that the content itself (wrapped by the background) is at + // least 40.dp, which is the same as the M3 buttons. This applies even if + // onClick is null, to make it easier to write expandables that are + // sometimes clickable and sometimes not. + val minSize = 40.dp + Box( + modifier = Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize), + contentAlignment = Alignment.Center, + ) { + content(expandable) + } + } else { content(expandable) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 7782705d4c61..336f9e1ad6e3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme @@ -81,6 +82,7 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.flags.QSComposeFragment +import com.android.systemui.qs.flags.QsInCompose import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel @@ -388,6 +390,7 @@ private fun NewChangesDot(modifier: Modifier = Modifier) { } /** A larger button with an icon, some text and an optional dot (to indicate new changes). */ +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun TextButton( icon: Icon, @@ -422,10 +425,13 @@ private fun TextButton( Text( text, Modifier.weight(1f), - style = MaterialTheme.typography.bodyMedium, - // TODO(b/242040009): Remove this letter spacing. We should only use the M3 text - // styles without modifying them. - letterSpacing = 0.01.em, + style = + if (QsInCompose.isEnabled) { + MaterialTheme.typography.labelLarge + } else { + MaterialTheme.typography.bodyMedium + }, + letterSpacing = if (QsInCompose.isEnabled) 0.em else 0.01.em, color = colorAttr(R.attr.onShadeInactiveVariant), maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 4b3ebc2bd53d..da54cb8e4679 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -58,6 +58,7 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.progressBarRangeInfo +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.setProgress import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp @@ -106,7 +107,10 @@ fun VolumeSlider( return } - Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) { + Column( + modifier = modifier.animateContentSize().semantics(true) {}, + verticalArrangement = Arrangement.Top, + ) { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth().height(40.dp), @@ -123,7 +127,7 @@ fun VolumeSlider( text = state.label, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.weight(1f), + modifier = Modifier.weight(1f).clearAndSetSemantics {}, ) button?.invoke() } @@ -134,12 +138,11 @@ fun VolumeSlider( onValueChanged = onValueChange, onValueChangeFinished = { onValueChangeFinished?.invoke() }, isEnabled = state.isEnabled, - stepDistance = state.a11yStep, + stepDistance = state.step, accessibilityParams = AccessibilityParams( - label = state.label, - disabledMessage = state.disabledMessage, - currentStateDescription = state.a11yStateDescription, + contentDescription = state.a11yContentDescription, + stateDescription = state.a11yStateDescription, ), haptics = hapticsViewModelFactory?.let { @@ -169,7 +172,7 @@ fun VolumeSlider( text = disabledMessage, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.labelSmall, - modifier = Modifier.basicMarquee(), + modifier = Modifier.basicMarquee().clearAndSetSemantics {}, ) } } @@ -229,7 +232,7 @@ private fun LegacyVolumeSlider( } val newValue = - (value + targetDirection * state.a11yStep).coerceIn( + (value + targetDirection * state.step).coerceIn( state.valueRange.start, state.valueRange.endInclusive, ) 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/collection/BundleEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt index 9faab58913f0..101874807474 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt @@ -127,7 +127,7 @@ class BundleEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isBubble() { - assertThat(underTest.isBubbleCapable).isFalse() + assertThat(underTest.isBubble).isFalse() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt index 12ade62c3570..7449064bc65b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt @@ -110,7 +110,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getRow_adapter() { - val row = Mockito.mock(ExpandableNotificationRow::class.java) + val row = mock(ExpandableNotificationRow::class.java) val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() @@ -128,7 +128,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isGroupRoot_adapter_groupSummary() { - val row = Mockito.mock(ExpandableNotificationRow::class.java) + val row = mock(ExpandableNotificationRow::class.java) val notification: Notification = Notification.Builder(mContext, "") .setSmallIcon(R.drawable.ic_person) @@ -175,7 +175,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isClearable_adapter() { - val row = Mockito.mock(ExpandableNotificationRow::class.java) + val row = mock(ExpandableNotificationRow::class.java) val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() @@ -193,7 +193,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getSummarization_adapter() { - val row = Mockito.mock(ExpandableNotificationRow::class.java) + val row = mock(ExpandableNotificationRow::class.java) val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() @@ -213,7 +213,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun getIcons_adapter() { - val row = Mockito.mock(ExpandableNotificationRow::class.java) + val row = mock(ExpandableNotificationRow::class.java) val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() @@ -258,7 +258,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun canDragAndDrop() { - val pi = Mockito.mock(PendingIntent::class.java) + val pi = mock(PendingIntent::class.java) Mockito.`when`(pi.isActivity).thenReturn(true) val notification: Notification = Notification.Builder(mContext, "") @@ -284,7 +284,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { val entry = NotificationEntryBuilder().setNotification(notification).build() underTest = factory.create(entry) as NotificationEntryAdapter - assertThat(underTest.isBubbleCapable).isEqualTo(entry.isBubble) + assertThat(underTest.isBubble).isEqualTo(entry.isBubble) } @Test @@ -350,7 +350,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { val notification: Notification = Notification.Builder(mContext, "") .setSmallIcon(R.drawable.ic_person) - .setFullScreenIntent(Mockito.mock(PendingIntent::class.java), true) + .setFullScreenIntent(mock(PendingIntent::class.java), true) .build() val entry = @@ -399,7 +399,7 @@ class NotificationEntryAdapterTest : SysuiTestCase() { val notification: Notification = Notification.Builder(mContext, "") .setSmallIcon(R.drawable.ic_person) - .addAction(Mockito.mock(Notification.Action::class.java)) + .addAction(mock(Notification.Action::class.java)) .build() val entry = NotificationEntryBuilder().setNotification(notification).build() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt index 893c17998a17..4c099b305332 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt @@ -455,6 +455,7 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() { assertThat(content).isNotNull() assertThat(content?.style).isEqualTo(Style.Call) + assertThat(content?.title).isEqualTo(TEST_PERSON_NAME) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index d306a5bea433..0d453352e882 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger import com.android.systemui.statusbar.notification.stack.NotificationListContainer @@ -318,7 +319,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { val notification = Notification.Builder(mContext).build() val sbn = SbnBuilder().setNotification(notification).setUser(UserHandle.of(USER_ALL)).build() - whenever(view.entry) + whenever(view.entryLegacy) .thenReturn( NotificationEntryBuilder().setSbn(sbn).setUser(UserHandle.of(USER_ALL)).build() ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 41cca19346f0..6ec1f919fb47 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -129,7 +129,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { whenever(notificationShelf.viewState).thenReturn(ExpandableViewState()) whenever(notificationRow.key).thenReturn("key") whenever(notificationRow.viewState).thenReturn(ExpandableViewState()) - whenever(notificationRow.entry).thenReturn(notificationEntry) + whenever(notificationRow.entryLegacy).thenReturn(notificationEntry) whenever(notificationRow.entryAdapter).thenReturn(notificationEntryAdapter) whenever(notificationRow.roundableState) .thenReturn(RoundableState(notificationRow, notificationRow, 0f)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java index c23e0e733b41..1cc291199531 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java @@ -221,6 +221,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.getEntryAdapter()).thenReturn(enrEntryAdapter); when(enr.isChildInGroup()).thenReturn(true); when(enr.areChildrenExpanded()).thenReturn(false); @@ -251,6 +252,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.isChildInGroup()).thenReturn(true); when(enr.areChildrenExpanded()).thenReturn(true); @@ -277,6 +279,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.isChildInGroup()).thenReturn(false); when(enr.isPinned()).thenReturn(false); when(enr.isExpanded()).thenReturn(false); @@ -305,6 +308,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.isChildInGroup()).thenReturn(false); when(enr.isPinned()).thenReturn(false); when(enr.isExpanded()).thenReturn(true); @@ -333,6 +337,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.isChildInGroup()).thenReturn(false); when(enr.isPinned()).thenReturn(true); when(enr.isPinnedAndExpanded()).thenReturn(false); @@ -361,6 +366,7 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { when(enr.getPrivateLayout()).thenReturn(privateLayout); when(enr.getEntry()).thenReturn(enrEntry); + when(enr.getEntryLegacy()).thenReturn(enrEntry); when(enr.isChildInGroup()).thenReturn(false); when(enr.isPinned()).thenReturn(true); when(enr.isPinnedAndExpanded()).thenReturn(true); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt index 04ab98889755..b1a3caf98f09 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel import android.bluetooth.BluetoothDevice +import android.graphics.drawable.TestStubDrawable import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.CachedBluetoothDevice @@ -63,6 +64,12 @@ class AudioSharingStreamSliderViewModelTest : SysuiTestCase() { assertThat(audioSharingSlider!!.label).isEqualTo("my headset 2") assertThat(audioSharingSlider!!.icon) - .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null)) + .isEqualTo( + Icon.Loaded( + drawable = TestStubDrawable(), + res = R.drawable.ic_volume_media_bt, + contentDescription = null, + ) + ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt index 9e8cde3bc936..ffe8e923815f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel import android.app.Flags import android.app.NotificationManager.INTERRUPTION_FILTER_NONE +import android.graphics.drawable.TestStubDrawable import android.media.AudioManager import android.platform.test.annotations.EnableFlags import android.service.notification.ZenPolicy @@ -28,7 +29,6 @@ import com.android.settingslib.notification.modes.TestModeBuilder import com.android.settingslib.volume.shared.model.AudioStream import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.collectLastValue @@ -39,8 +39,6 @@ import com.android.systemui.statusbar.policy.data.repository.fakeZenModeReposito import com.android.systemui.testKosmos import com.android.systemui.volume.data.repository.audioSharingRepository import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock @@ -173,6 +171,12 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() { assertThat(mediaSlider!!.label).isEqualTo("my headset 1") assertThat(mediaSlider!!.icon) - .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null)) + .isEqualTo( + Icon.Loaded( + drawable = TestStubDrawable(), + res = R.drawable.ic_volume_media_bt, + contentDescription = null, + ) + ) } } diff --git a/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml b/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml new file mode 100644 index 000000000000..2173641376fa --- /dev/null +++ b/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2025 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/hearing_device_ambient_icon_background" + android:insetTop="10dp" + android:insetBottom="10dp"/> diff --git a/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml b/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml new file mode 100644 index 000000000000..81ef3d7c11b7 --- /dev/null +++ b/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2025 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <solid android:color="@androidprv:color/materialColorSurfaceContainerHighest" /> + <corners + android:bottomLeftRadius="?android:attr/dialogCornerRadius" + android:topLeftRadius="?android:attr/dialogCornerRadius" + android:bottomRightRadius="?android:attr/dialogCornerRadius" + android:topRightRadius="?android:attr/dialogCornerRadius" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml b/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml index 73704f823033..f17cc96329c6 100644 --- a/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml +++ b/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml @@ -21,7 +21,7 @@ android:height="2dp" android:width="@dimen/rear_display_progress_width"> <shape android:shape="rectangle"> - <solid android:color="@androidprv:color/materialColorSurfaceContainer" /> + <solid android:color="?android:attr/colorAccent"/> </shape> </item> <item @@ -29,4 +29,4 @@ android:gravity="center_vertical|fill_horizontal"> <com.android.systemui.util.RoundedCornerProgressDrawable android:drawable="@drawable/rear_display_dialog_seekbar_progress" /> </item> -</layer-list>
\ No newline at end of file +</layer-list> diff --git a/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml index fd409a5a8bb1..d3e9db15dfdd 100644 --- a/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml +++ b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml @@ -36,7 +36,8 @@ android:padding="12dp" android:contentDescription="@string/hearing_devices_ambient_unmute" android:src="@drawable/ic_ambient_volume" - android:tint="@androidprv:color/materialColorOnSurface" /> + android:tint="@androidprv:color/materialColorOnSurface" + android:background="@drawable/hearing_device_ambient_icon_background"/> <TextView android:id="@+id/ambient_title" android:layout_width="0dp" @@ -56,7 +57,8 @@ android:padding="10dp" android:contentDescription="@string/hearing_devices_ambient_expand_controls" android:src="@drawable/ic_hearing_device_expand" - android:tint="@androidprv:color/materialColorOnSurface" /> + android:tint="@androidprv:color/materialColorOnSurface" + android:background="@drawable/hearing_device_ambient_expand_icon_background"/> </LinearLayout> <LinearLayout android:id="@+id/ambient_control_container" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 15519ff37d0c..7c6a1b1bf63d 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -146,7 +146,8 @@ <color name="smart_reply_button_stroke">@*android:color/accent_device_default</color> <!-- Magic Action colors --> - <color name="magic_action_button_text_color">@androidprv:color/materialColorOnSurfaceVariant</color> + <color name="magic_action_button_text_color">@androidprv:color/materialColorOnSurface</color> + <color name="magic_action_button_stroke_color">@androidprv:color/materialColorOnSurface</color> <!-- Biometric dialog colors --> <color name="biometric_dialog_gray">#ff757575</color> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3fdb98b4e00b..b627bdf22a6c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1483,6 +1483,8 @@ <string name="media_projection_entry_app_permission_dialog_continue_entire_screen">Share screen</string> <!-- 1P/3P apps disabled the single app projection option. [CHAR LIMIT=NONE] --> <string name="media_projection_entry_app_permission_dialog_single_app_disabled"><xliff:g id="app_name" example="Meet">%1$s</xliff:g> has disabled this option</string> + <!-- Explanation that the app requesting the projection does not support single app sharing[CHAR LIMIT=70] --> + <string name="media_projection_entry_app_permission_dialog_single_app_not_supported">Not supported by the app</string> <!-- Title of the activity that allows users to select an app to share to a 1P/3P app [CHAR LIMIT=70] --> <string name="media_projection_entry_share_app_selector_title">Choose app to share</string> @@ -2558,6 +2560,9 @@ <!-- Button to edit the tile ordering of quick settings [CHAR LIMIT=60] --> <string name="qs_edit">Edit</string> + <!-- Title for QS Edit mode screen [CHAR LIMIT=30] --> + <string name="qs_edit_tiles">Edit tiles</string> + <!-- SysUI Tuner: Options for how clock is displayed [CHAR LIMIT=NONE] --> <string name="tuner_time">Time</string> diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml index 98e5cea0e78f..a7ad48d17abb 100644 --- a/packages/SystemUI/shared/res/values/bools.xml +++ b/packages/SystemUI/shared/res/values/bools.xml @@ -22,7 +22,4 @@ <resources> <!-- Whether to add padding at the bottom of the complication clock --> <bool name="dream_overlay_complication_clock_bottom_padding">false</bool> - - <!-- Whether to mark tasks that are present in the UI as perceptible tasks. --> - <bool name="config_usePerceptibleTasks">false</bool> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 487d1ce2514e..b981f9837787 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -46,8 +46,6 @@ import android.view.Display; import android.window.TaskSnapshot; import com.android.internal.app.IVoiceInteractionManagerService; -import com.android.server.am.Flags; -import com.android.systemui.shared.R; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -324,14 +322,6 @@ public class ActivityManagerWrapper { } /** - * Returns true if tasks with a presence in the UI should be marked as perceptible tasks. - */ - public static boolean usePerceptibleTasks(Context context) { - return Flags.perceptibleTasks() - && context.getResources().getBoolean(R.bool.config_usePerceptibleTasks); - } - - /** * Returns true if the running task represents the home task */ public static boolean isHomeTask(RunningTaskInfo info) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java index bd3dfe049587..8025d4b0c2ea 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -317,14 +317,14 @@ class MenuViewAppearance { public static float avoidVerticalDisplayCutout( float y, float menuHeight, Rect bounds, Rect cutout) { if (cutout.top > y + menuHeight || cutout.bottom < y) { - return y; + return clampVerticalPosition(y, menuHeight, bounds.top, bounds.bottom); } boolean topAvailable = cutout.top - bounds.top >= menuHeight; boolean bottomAvailable = bounds.bottom - cutout.bottom >= menuHeight; boolean topOrBottom; if (!topAvailable && !bottomAvailable) { - return y; + return clampVerticalPosition(y, menuHeight, bounds.top, bounds.bottom); } else if (topAvailable && !bottomAvailable) { topOrBottom = true; } else if (!topAvailable && bottomAvailable) { @@ -332,7 +332,16 @@ class MenuViewAppearance { } else { topOrBottom = y + menuHeight * 0.5f < cutout.centerY(); } - return (topOrBottom) ? cutout.top - menuHeight : cutout.bottom; + + float finalPosition = (topOrBottom) ? cutout.top - menuHeight : cutout.bottom; + return clampVerticalPosition(finalPosition, menuHeight, bounds.top, bounds.bottom); + } + + private static float clampVerticalPosition( + float position, float height, float min, float max) { + position = Float.max(min + height / 2, position); + position = Float.min(max - height / 2, position); + return position; } boolean isMenuOnLeftSide() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 22d2aaf2a8e7..87e9784ddbc9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -32,7 +32,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.logging.KeyguardLogger import com.android.settingslib.Utils import com.android.systemui.CoreStartable -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.dagger.SysUISingleton @@ -43,6 +42,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R +import com.android.systemui.shared.Flags.ambientAod import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LiftReveal import com.android.systemui.statusbar.LightRevealEffect @@ -196,7 +196,7 @@ constructor( // This code path is not used if the KeyguardTransitionRepository is managing the light // reveal scrim. - if (!lightRevealMigration()) { + if (!ambientAod()) { if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) { circleReveal?.let { lightRevealScrim.revealAmount = 0f @@ -213,7 +213,7 @@ constructor( } override fun onKeyguardFadingAwayChanged() { - if (lightRevealMigration()) { + if (ambientAod()) { return } diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt index 2adaec21867f..6792f3188986 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt @@ -19,6 +19,7 @@ package com.android.systemui.common.shared.model import android.annotation.DrawableRes import android.graphics.drawable.Drawable import androidx.compose.runtime.Stable +import com.android.systemui.common.shared.model.Icon.Loaded /** * Models an icon, that can either be already [loaded][Icon.Loaded] or be a [reference] @@ -33,8 +34,37 @@ sealed class Icon { constructor( val drawable: Drawable, override val contentDescription: ContentDescription?, + /** + * Serves as an id to compare two instances. When provided this is used alongside + * [contentDescription] to determine equality. This is useful when comparing icons + * representing the same UI, but with different [drawable] instances. + */ @DrawableRes val res: Int? = null, - ) : Icon() + ) : Icon() { + + override fun equals(other: Any?): Boolean { + val that = other as? Loaded ?: return false + + if (this.res != null && that.res != null) { + return this.res == that.res && this.contentDescription == that.contentDescription + } + + return this.res == that.res && + this.drawable == that.drawable && + this.contentDescription == that.contentDescription + } + + override fun hashCode(): Int { + var result = contentDescription?.hashCode() ?: 0 + result = + if (res != null) { + 31 * result + res.hashCode() + } else { + 31 * result + drawable.hashCode() + } + return result + } + } data class Resource( @DrawableRes val res: Int, @@ -49,4 +79,4 @@ sealed class Icon { fun Drawable.asIcon( contentDescription: ContentDescription? = null, @DrawableRes res: Int? = null, -): Icon.Loaded = Icon.Loaded(this, contentDescription, res) +): Loaded = Loaded(this, contentDescription, res) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 756edb3d048d..5a4b0b0e2d24 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -229,7 +229,7 @@ constructor( MutableStateFlow(false) } - val blurRadiusPx: Float = blurConfig.maxBlurRadiusPx / 2.0f + val blurRadiusPx: Float = blurConfig.maxBlurRadiusPx init { // Initialize our media host for the UMO. This only needs to happen once and must be done diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index c61530c3dbcc..74cf7e4f7359 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -20,7 +20,6 @@ package com.android.systemui.keyguard import android.content.Context import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.CoreStartable -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.biometrics.ui.binder.DeviceEntryUnlockTrackerViewBinder import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton @@ -46,6 +45,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shared.Flags.ambientAod import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.VibratorHelper @@ -105,7 +105,7 @@ constructor( bindJankViewModel() initializeViews() - if (lightRevealMigration()) { + if (ambientAod()) { LightRevealScrimViewBinder.bind( lightRevealScrim, lightRevealScrimViewModel, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt index 45f8f10595e4..3cf05062325d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.communal.ui.compose.TransitionDuration import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.dagger.GlanceableHubBlurComponent @@ -28,6 +27,7 @@ import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.transitions.GlanceableHubTransition import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shared.Flags.ambientAod import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -56,14 +56,14 @@ constructor( return transitionAnimation.sharedFlow( duration = 250.milliseconds, startTime = - if (lightRevealMigration()) { + if (ambientAod()) { 100.milliseconds // Wait for the light reveal to "hit" the LS elements. } else { 0.milliseconds }, onStart = { currentAlpha = - if (lightRevealMigration()) { + if (ambientAod()) { viewState.alpha() } else { 0f diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt index d981eeb0989b..ba6bda8a2a04 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -25,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.shared.Flags.ambientAod import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -52,13 +52,13 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra return transitionAnimation.sharedFlow( duration = 250.milliseconds, startTime = - if (lightRevealMigration()) { + if (ambientAod()) { 100.milliseconds // Wait for the light reveal to "hit" the LS elements. } else { 0.milliseconds }, onStart = { - if (lightRevealMigration()) { + if (ambientAod()) { currentAlpha = viewState.alpha() } else { currentAlpha = 0f diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt index b15cacf077a4..2eb5bf9328e6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -25,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.shared.Flags.ambientAod import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -56,7 +56,7 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra duration = 250.milliseconds, startTime = 0.milliseconds, onStart = { - if (lightRevealMigration()) { + if (ambientAod()) { currentAlpha = viewState.alpha() } else { currentAlpha = 0f diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt index c6e4db7af2d9..324a3efc2989 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt @@ -150,6 +150,13 @@ private class OptionsAdapter(context: Context, private val options: List<ScreenS titleTextView.isEnabled = true } else { errorTextView.visibility = View.VISIBLE + if (com.android.systemui.Flags.mediaProjectionGreyErrorText()) { + errorTextView.isEnabled = false + errorTextView.setTextColor(context.getColorStateList(R.color.menu_item_text)) + errorTextView.setText( + R.string.media_projection_entry_app_permission_dialog_single_app_not_supported + ) + } titleTextView.isEnabled = false } return view diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt index d40ecc9565ae..1176095fbb1c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.panels.ui.compose +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -23,11 +24,13 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -63,6 +66,7 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode val title = tileDetailedViewModel.title val subTitle = tileDetailedViewModel.subTitle + val colors = MaterialTheme.colorScheme Column( modifier = @@ -70,20 +74,33 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode .fillMaxWidth() // The height of the details view is TBD. .fillMaxHeight() + .background(color = colors.onPrimary) ) { CompositionLocalProvider( value = LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant ) { Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding( + start = TileDetailsDefaults.TitleRowStart, + top = TileDetailsDefaults.TitleRowTop, + end = TileDetailsDefaults.TitleRowEnd, + bottom = TileDetailsDefaults.TitleRowBottom + ), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { IconButton( onClick = { detailsViewModel.closeDetailedView() }, + colors = IconButtonDefaults.iconButtonColors( + contentColor = colors.onSurface + ), modifier = - Modifier.align(Alignment.CenterVertically) + Modifier + .align(Alignment.CenterVertically) .height(TileDetailsDefaults.IconHeight) + .width(TileDetailsDefaults.IconWidth) .padding(start = TileDetailsDefaults.IconPadding), ) { Icon( @@ -96,13 +113,19 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode text = title, modifier = Modifier.align(Alignment.CenterVertically), textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleLarge, + style = MaterialTheme.typography.titleMedium, + color = colors.onSurface, ) IconButton( onClick = { tileDetailedViewModel.clickOnSettingsButton() }, + colors = IconButtonDefaults.iconButtonColors( + contentColor = colors.onSurface + ), modifier = - Modifier.align(Alignment.CenterVertically) + Modifier + .align(Alignment.CenterVertically) .height(TileDetailsDefaults.IconHeight) + .width(TileDetailsDefaults.IconWidth) .padding(end = TileDetailsDefaults.IconPadding), ) { Icon( @@ -116,7 +139,8 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode text = subTitle, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleSmall, + style = MaterialTheme.typography.bodySmall, + color = colors.onSurfaceVariant, ) } MapTileDetailsContent(tileDetailedViewModel) @@ -135,6 +159,11 @@ private fun MapTileDetailsContent(tileDetailsViewModel: TileDetailsViewModel) { } private object TileDetailsDefaults { - val IconHeight = 48.dp + val IconHeight = 24.dp + val IconWidth = 24.dp val IconPadding = 4.dp + val TitleRowStart = 14.dp + val TitleRowTop = 22.dp + val TitleRowEnd = 20.dp + val TitleRowBottom = 8.dp } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt index b3b6cfdcc306..699778f3b6f9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt @@ -39,6 +39,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicText +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -173,6 +174,7 @@ fun LargeTileContent( } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun LargeTileLabels( label: String, @@ -188,7 +190,7 @@ fun LargeTileLabels( Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) { TileLabel( text = label, - style = MaterialTheme.typography.labelLarge, + style = MaterialTheme.typography.titleSmallEmphasized, color = { animatedLabelColor }, isVisible = isVisible, ) @@ -196,7 +198,7 @@ fun LargeTileLabels( TileLabel( secondaryLabel ?: "", color = { animatedSecondaryLabelColor }, - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.labelMedium, isVisible = isVisible, modifier = Modifier.thenIf( diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt index ccbd8fdbe00c..46f05d0ac895 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -65,6 +65,7 @@ import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalContentColor @@ -109,11 +110,11 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.text.style.Hyphens import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastMap import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory import com.android.compose.modifiers.height @@ -165,7 +166,7 @@ import kotlinx.coroutines.launch object TileType -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer @@ -177,7 +178,8 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { ), title = { Text( - text = stringResource(id = R.string.qs_edit), + text = stringResource(id = R.string.qs_edit_tiles), + style = MaterialTheme.typography.titleLargeEmphasized, modifier = Modifier.padding(start = 24.dp), ) }, @@ -204,7 +206,10 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { contentColor = MaterialTheme.colorScheme.onPrimary, ), ) { - Text(stringResource(id = com.android.internal.R.string.reset)) + Text( + text = stringResource(id = com.android.internal.R.string.reset), + style = MaterialTheme.typography.labelLarge, + ) } } }, @@ -212,6 +217,7 @@ private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { ) } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun DefaultEditTileGrid( listState: EditTileListState, @@ -283,7 +289,9 @@ fun DefaultEditTileGrid( } } } else { - Text(text = stringResource(id = R.string.drag_to_rearrange_tiles)) + EditGridCenteredText( + text = stringResource(id = R.string.drag_to_rearrange_tiles) + ) } } } @@ -401,6 +409,11 @@ private fun EditGridHeader( } @Composable +private fun EditGridCenteredText(text: String, modifier: Modifier = Modifier) { + Text(text = text, style = MaterialTheme.typography.titleSmall, modifier = modifier) +} + +@Composable private fun RemoveTileTarget(onClick: () -> Unit) { Row( verticalAlignment = Alignment.CenterVertically, @@ -486,6 +499,7 @@ private fun CurrentTilesGrid( } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun AvailableTileGrid( tiles: List<AvailableTileGridCell>, @@ -524,7 +538,7 @@ private fun AvailableTileGrid( ) { Text( text = category.label.load() ?: "", - fontSize = 20.sp, + style = MaterialTheme.typography.titleMediumEmphasized, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.fillMaxWidth().padding(start = 8.dp, bottom = 16.dp), ) @@ -737,6 +751,7 @@ private fun TileGridCell( } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun AvailableTileGridCell( cell: AvailableTileGridCell, @@ -803,6 +818,7 @@ private fun AvailableTileGridCell( color = colors.label, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, + style = MaterialTheme.typography.labelMedium.copy(hyphens = Hyphens.Auto), modifier = Modifier.align(Alignment.TopCenter), ) } 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/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt index 74b3f302cdc8..b94e7b249233 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -36,8 +37,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.Dp import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.Expandable +import com.android.compose.modifiers.thenIf import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.compose.Icon import com.android.systemui.common.ui.compose.load @@ -79,6 +82,17 @@ fun OngoingActivityChip( } is OngoingActivityChipModel.ClickBehavior.None -> null } + val isClickable = onClick != null + + val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding) + val minWidth = + if (isClickable) { + dimensionResource(id = R.dimen.min_clickable_item_size) + } else if (model.icon != null) { + dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size) + chipSidePadding + } else { + dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding + } Expandable( color = Color(model.colors.background(LocalContext.current).defaultColor), @@ -92,6 +106,15 @@ fun OngoingActivityChip( this.contentDescription = contentDescription } } + .thenIf(isClickable) { Modifier.widthIn(min = minWidth) } + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + layout(placeable.width, placeable.height) { + if (constraints.maxWidth >= minWidth.roundToPx()) { + placeable.place(0, 0) + } + } + } .graphicsLayer( alpha = if (model.transitionManager?.hideChipForTransition == true) { @@ -103,9 +126,12 @@ fun OngoingActivityChip( borderStroke = borderStroke, onClick = onClick, useModifierBasedImplementation = StatusBarChipsReturnAnimations.isEnabled, + // Some chips like the 3-2-1 countdown chip should be very small, smaller than a + // reasonable minimum size. + defaultMinSize = false, transitionControllerFactory = model.transitionManager?.controllerFactory, ) { - ChipBody(model, iconViewStore, isClickable = onClick != null) + ChipBody(model, iconViewStore, isClickable = isClickable, minWidth = minWidth) } } @@ -114,36 +140,22 @@ private fun ChipBody( model: OngoingActivityChipModel.Active, iconViewStore: NotificationIconContainerViewBinder.IconViewStore?, isClickable: Boolean, + minWidth: Dp, modifier: Modifier = Modifier, ) { val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView || model.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon - val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding) - val minWidth = - if (isClickable) { - dimensionResource(id = R.dimen.min_clickable_item_size) - } else if (model.icon != null) { - dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size) + chipSidePadding - } else { - dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding - } - Row( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, modifier = modifier .fillMaxHeight() - .layout { measurable, constraints -> - val placeable = measurable.measure(constraints) - layout(placeable.width, placeable.height) { - if (constraints.maxWidth >= minWidth.roundToPx()) { - placeable.place(0, 0) - } - } - } + // Set the minWidth here as well as on the Expandable so that the content within + // this row is still centered correctly horizontally + .thenIf(isClickable) { Modifier.widthIn(min = minWidth) } .padding( horizontal = if (hasEmbeddedIcon) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index 3bb1ff161b6f..fed9417edd88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -81,7 +81,6 @@ public final class NotificationClicker implements View.OnClickListener { mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE); final ExpandableNotificationRow row = (ExpandableNotificationRow) v; - final NotificationEntry entry = row.getEntry(); mLogger.logOnClick(row.getLoggingKey()); // Check if the notification is displaying the menu, if so slide notification back @@ -109,16 +108,16 @@ public final class NotificationClicker implements View.OnClickListener { DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); if (NotificationBundleUi.isEnabled()) { - if (!row.getEntryAdapter().isBubbleCapable() && mBubblesOptional.isPresent()) { + if (!row.getEntryAdapter().isBubble() && mBubblesOptional.isPresent()) { mBubblesOptional.get().collapseStack(); } + row.getEntryAdapter().onEntryClicked(row); } else { if (!row.getEntryLegacy().isBubble() && mBubblesOptional.isPresent()) { mBubblesOptional.get().collapseStack(); } + mNotificationActivityStarter.onNotificationClicked(row.getEntryLegacy(), row); } - - mNotificationActivityStarter.onNotificationClicked(entry, row); } private boolean isMenuVisible(ExpandableNotificationRow row) { @@ -129,9 +128,12 @@ public final class NotificationClicker implements View.OnClickListener { * Attaches the click listener to the row if appropriate. */ public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { + boolean isBubble = NotificationBundleUi.isEnabled() + ? row.getEntryAdapter().isBubble() + : row.getEntryLegacy().isBubble(); Notification notification = sbn.getNotification(); if (notification.contentIntent != null || notification.fullScreenIntent != null - || row.getEntry().isBubble()) { + || isBubble) { if (NotificationBundleUi.isEnabled()) { row.setBubbleClickListener( v -> row.getEntryAdapter().onNotificationBubbleIconClicked()); 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/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt index e743d87a784c..be17ae56c315 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt @@ -98,7 +98,7 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { return false } - override fun isBubbleCapable(): Boolean { + override fun isBubble(): Boolean { return false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java index f39bd0324995..3757ebfb9986 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java @@ -125,7 +125,7 @@ public interface EntryAdapter { boolean canDragAndDrop(); - boolean isBubbleCapable(); + boolean isBubble(); @Nullable String getStyle(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt index 12cfa91d9890..a23c5a3ea9f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt @@ -121,7 +121,7 @@ class NotificationEntryAdapter( return false } - override fun isBubbleCapable(): Boolean { + override fun isBubble(): Boolean { return entry.isBubble } 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/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt index d35c3b617246..7e19ff115a92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt @@ -21,6 +21,7 @@ import android.app.Notification.BigPictureStyle import android.app.Notification.BigTextStyle import android.app.Notification.CallStyle import android.app.Notification.EXTRA_BIG_TEXT +import android.app.Notification.EXTRA_CALL_PERSON import android.app.Notification.EXTRA_CHRONOMETER_COUNT_DOWN import android.app.Notification.EXTRA_PROGRESS import android.app.Notification.EXTRA_PROGRESS_INDETERMINATE @@ -33,6 +34,7 @@ import android.app.Notification.EXTRA_VERIFICATION_ICON import android.app.Notification.EXTRA_VERIFICATION_TEXT import android.app.Notification.InboxStyle import android.app.Notification.ProgressStyle +import android.app.Person import android.content.Context import android.graphics.drawable.Icon import com.android.systemui.Flags @@ -108,12 +110,12 @@ constructor( contentBuilder.shortCriticalText = notification.shortCriticalText() contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs contentBuilder.profileBadgeResId = null // TODO - contentBuilder.title = notification.resolveTitle(recoveredBuilder.style) - contentBuilder.text = notification.resolveText(recoveredBuilder.style) + contentBuilder.title = notification.title(recoveredBuilder.style) + contentBuilder.text = notification.text(recoveredBuilder.style) contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider) contentBuilder.oldProgress = notification.oldProgress() - val colorsFromNotif = recoveredBuilder.getColors(/* header= */ false) + val colorsFromNotif = recoveredBuilder.getColors(/* isHeader= */ false) contentBuilder.colors = PromotedNotificationContentModel.Colors( backgroundColor = colorsFromNotif.backgroundColor, @@ -132,20 +134,16 @@ constructor( private fun Notification.bigTitle(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE_BIG) - private fun Notification.Style.bigTitleOverridesTitle(): Boolean { - return when (this) { + private fun Notification.callPerson(): Person? = + extras?.getParcelable(EXTRA_CALL_PERSON, Person::class.java) + + private fun Notification.title(style: Notification.Style?): CharSequence? { + return when (style) { is BigTextStyle, is BigPictureStyle, - is InboxStyle -> true - else -> false - } - } - - private fun Notification.resolveTitle(style: Notification.Style?): CharSequence? { - return if (style?.bigTitleOverridesTitle() == true) { - bigTitle() - } else { - null + is InboxStyle -> bigTitle() + is CallStyle -> callPerson()?.name + else -> null } ?: title() } @@ -153,13 +151,10 @@ constructor( private fun Notification.bigText(): CharSequence? = extras?.getCharSequence(EXTRA_BIG_TEXT) - private fun Notification.Style.bigTextOverridesText(): Boolean = this is BigTextStyle - - private fun Notification.resolveText(style: Notification.Style?): CharSequence? { - return if (style?.bigTextOverridesText() == true) { - bigText() - } else { - null + private fun Notification.text(style: Notification.Style?): CharSequence? { + return when (style) { + is BigTextStyle -> bigText() + else -> null } ?: text() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt index 468934791525..5f9678a06b59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt @@ -63,7 +63,7 @@ constructor(@PromotedNotificationLog private val buffer: LogBuffer) { INFO, { str1 = entry.logKey - str2 = content.toString() + str2 = content.toRedactedString() }, { "extraction succeeded: $str2 for $str1" }, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt index 0c2859fa1766..57b07204fc6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt @@ -25,6 +25,8 @@ import androidx.annotation.ColorInt import com.android.internal.widget.NotificationProgressModel import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi +import com.android.systemui.statusbar.notification.row.ImageResult +import com.android.systemui.statusbar.notification.row.LazyImage import com.android.systemui.statusbar.notification.row.shared.ImageModel /** @@ -152,6 +154,54 @@ data class PromotedNotificationContentModel( Ineligible, } + fun toRedactedString(): String { + return ("PromotedNotificationContentModel(" + + "identity=$identity, " + + "wasPromotedAutomatically=$wasPromotedAutomatically, " + + "smallIcon=${smallIcon?.toRedactedString()}, " + + "appName=$appName, " + + "subText=${subText?.toRedactedString()}, " + + "shortCriticalText=$shortCriticalText, " + + "time=$time, " + + "lastAudiblyAlertedMs=$lastAudiblyAlertedMs, " + + "profileBadgeResId=$profileBadgeResId, " + + "title=${title?.toRedactedString()}, " + + "text=${text?.toRedactedString()}, " + + "skeletonLargeIcon=${skeletonLargeIcon?.toRedactedString()}, " + + "oldProgress=$oldProgress, " + + "colors=$colors, " + + "style=$style, " + + "personIcon=${personIcon?.toRedactedString()}, " + + "personName=${personName?.toRedactedString()}, " + + "verificationIcon=$verificationIcon, " + + "verificationText=$verificationText, " + + "newProgress=$newProgress)") + } + + private fun CharSequence.toRedactedString(): String = "[$length]" + + private fun ImageModel.toRedactedString(): String { + return when (this) { + is LazyImage -> this.toRedactedString() + else -> this.toString() + } + } + + private fun LazyImage.toRedactedString(): String { + return ("LazyImage(" + + "icon=[${icon.javaClass.simpleName}], " + + "sizeClass=$sizeClass, " + + "transform=$transform, " + + "result=${result?.toRedactedString()})") + } + + private fun ImageResult.toRedactedString(): String { + return when (this) { + is ImageResult.Empty -> this.toString() + is ImageResult.Image -> "Image(drawable=[${drawable.javaClass.simpleName}])" + } + } + companion object { @JvmStatic fun featureFlagEnabled(): Boolean = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 256d549dc880..2a3b266c8d10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -4011,7 +4011,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } else if (isChildInGroup()) { final int childColor = getShowingLayout().getBackgroundColorForExpansionState(); - if (Flags.notificationRowTransparency() && childColor == Color.TRANSPARENT) { + if ((Flags.notificationRowTransparency() || notificationsRedesignTemplates()) + && childColor == Color.TRANSPARENT) { // If child is not customizing its background color, switch from the parent to // the child background when the expansion finishes. mShowNoBackground = !mNotificationParent.mShowNoBackground; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt index fe3a856e711e..a9ca6359b570 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt @@ -62,6 +62,7 @@ class BaseBackgroundDrawable( private val buttonShape = Path() // Color and style + private val outlineStaticColor = context.getColor(R.color.magic_action_button_stroke_color) private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { val bgColor = context.getColor( @@ -70,15 +71,17 @@ class BaseBackgroundDrawable( color = bgColor style = Paint.Style.FILL } - private val outlinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - val outlineColor = - context.getColor( - com.android.internal.R.color.materialColorOutlineVariant - ) - color = outlineColor + private val outlineGradientPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = outlineStaticColor + style = Paint.Style.STROKE + strokeWidth = outlineStrokeWidth + } + private val outlineSolidPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = outlineStaticColor style = Paint.Style.STROKE strokeWidth = outlineStrokeWidth } + private val outlineStartColor = context.getColor( com.android.internal.R.color.materialColorTertiaryContainer @@ -91,21 +94,35 @@ class BaseBackgroundDrawable( context.getColor( com.android.internal.R.color.materialColorPrimary ) + // Animation private var gradientAnimator: ValueAnimator private var rotationAngle = 20f // Start rotation at 20 degrees + private var fadeAnimator: ValueAnimator? = null + private var gradientAlpha = 255 // Fading out gradient + private var solidAlpha = 0 // Fading in solid color init { gradientAnimator = ValueAnimator.ofFloat(0f, 1f).apply { - duration = 5000 // 5 seconds + duration = 1500 interpolator = Interpolators.LINEAR - repeatCount = 1 + repeatCount = 0 addUpdateListener { animator -> val animatedValue = animator.animatedValue as Float rotationAngle = 20f + animatedValue * 360f // Rotate in a spiral invalidateSelf() } - // TODO: Reset the outline color when animation ends. + start() + } + fadeAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + duration = 500 + startDelay = 1000 + addUpdateListener { animator -> + val progress = animator.animatedValue as Float + gradientAlpha = ((1 - progress) * 255).toInt() // Fade out gradient + solidAlpha = (progress * 255).toInt() // Fade in color + invalidateSelf() + } start() } } @@ -120,14 +137,9 @@ class BaseBackgroundDrawable( // Draw background canvas.clipPath(buttonShape) canvas.drawPath(buttonShape, bgPaint) - // Apply gradient to outline - canvas.drawPath(buttonShape, outlinePaint) - updateGradient(boundsF) - canvas.restore() - } - private fun updateGradient(boundsF: RectF) { - val gradient = LinearGradient( + // Set up outline gradient + val gradientShader = LinearGradient( boundsF.left, boundsF.top, boundsF.right, boundsF.bottom, intArrayOf(outlineStartColor, outlineMiddleColor, outlineEndColor), @@ -137,9 +149,17 @@ class BaseBackgroundDrawable( // Create a rotation matrix for the spiral effect val matrix = Matrix() matrix.setRotate(rotationAngle, boundsF.centerX(), boundsF.centerY()) - gradient.setLocalMatrix(matrix) + gradientShader.setLocalMatrix(matrix) + + // Apply gradient to outline + outlineGradientPaint.shader = gradientShader + outlineGradientPaint.alpha = gradientAlpha + canvas.drawPath(buttonShape, outlineGradientPaint) + // Apply solid color to outline + outlineSolidPaint.alpha = solidAlpha + canvas.drawPath(buttonShape, outlineSolidPaint) - outlinePaint.shader = gradient + canvas.restore() } override fun onBoundsChange(bounds: Rect) { @@ -149,13 +169,15 @@ class BaseBackgroundDrawable( override fun setAlpha(alpha: Int) { bgPaint.alpha = alpha - outlinePaint.alpha = alpha + outlineGradientPaint.alpha = alpha + outlineSolidPaint.alpha = alpha invalidateSelf() } override fun setColorFilter(colorFilter: ColorFilter?) { bgPaint.colorFilter = colorFilter - outlinePaint.colorFilter = colorFilter + outlineGradientPaint.colorFilter = colorFilter + outlineSolidPaint.colorFilter = colorFilter invalidateSelf() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index cb1e898ef5b1..488aa44ddd3b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1605,7 +1605,7 @@ public class NotificationContentView extends FrameLayout implements Notification if (shouldShowBubbleButton(entry)) { boolean isBubble = NotificationBundleUi.isEnabled() - ? mContainingNotification.getEntryAdapter().isBubbleCapable() + ? mContainingNotification.getEntryAdapter().isBubble() : entry.isBubble(); // explicitly resolve drawable resource using SystemUI's theme Drawable d = mContext.getDrawable(isBubble diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index a4ee4ad6f6ec..9d9f01b571a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -31,11 +31,11 @@ import static androidx.lifecycle.Lifecycle.State.RESUMED; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import static com.android.systemui.Flags.keyboardShortcutHelperRewrite; -import static com.android.systemui.Flags.lightRevealMigration; import static com.android.systemui.Flags.relockWithPowerButtonImmediately; import static com.android.systemui.Flags.statusBarSignalPolicyRefactor; import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL; import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; +import static com.android.systemui.shared.Flags.ambientAod; import static com.android.systemui.statusbar.StatusBarState.SHADE; import android.annotation.Nullable; @@ -980,7 +980,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onKeyguardGoingAwayChanged() { - if (lightRevealMigration()) { + if (ambientAod()) { // This code path is not used if the KeyguardTransitionRepository is managing // the lightreveal scrim. return; @@ -2446,7 +2446,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return; } - if (lightRevealMigration()) { + if (ambientAod()) { return; } @@ -3103,7 +3103,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onDozeAmountChanged(float linear, float eased) { - if (!lightRevealMigration() + if (!ambientAod() && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { // If wakeAndUnlocking, this is handled in AuthRippleInteractor if (!mBiometricUnlockController.isWakeAndUnlock()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 0d43789e95a8..8890db3cd943 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -18,7 +18,6 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD import com.android.systemui.DejankUtils -import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.KeyguardViewMediator @@ -26,6 +25,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor +import com.android.systemui.shared.Flags.ambientAod import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController @@ -100,7 +100,7 @@ constructor( duration = LIGHT_REVEAL_ANIMATION_DURATION interpolator = Interpolators.LINEAR addUpdateListener { - if (lightRevealMigration()) return@addUpdateListener + if (ambientAod()) return@addUpdateListener if (lightRevealScrim.revealEffect !is CircleReveal) { lightRevealScrim.revealAmount = it.animatedValue as Float } @@ -116,7 +116,7 @@ constructor( addListener( object : AnimatorListenerAdapter() { override fun onAnimationCancel(animation: Animator) { - if (lightRevealMigration()) return + if (ambientAod()) return if (lightRevealScrim.revealEffect !is CircleReveal) { lightRevealScrim.revealAmount = 1f } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index 9a81992a6add..7f778bb029f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -256,6 +256,9 @@ public class Clock extends TextView implements if (mClockFormat != null) { mClockFormat.setTimeZone(mCalendar.getTimeZone()); } + if (mContentDescriptionFormat != null) { + mContentDescriptionFormat.setTimeZone(mCalendar.getTimeZone()); + } }); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { final Locale newLocale = getResources().getConfiguration().locale; diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index 43d1ef478ae1..32f784f17bb7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -16,7 +16,6 @@ package com.android.systemui.volume.dialog.sliders.ui -import android.graphics.drawable.Drawable import android.view.View import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween @@ -29,7 +28,6 @@ import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SliderDefaults import androidx.compose.runtime.Composable @@ -43,7 +41,8 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.PlatformTheme -import com.android.compose.ui.graphics.painter.DrawablePainter +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.ui.compose.Icon import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.res.R @@ -155,7 +154,7 @@ private fun VolumeDialogSlider( }, ) }, - accessibilityParams = AccessibilityParams(label = sliderStateModel.label), + accessibilityParams = AccessibilityParams(contentDescription = sliderStateModel.label), modifier = modifier.pointerInput(Unit) { coroutineScope { @@ -172,7 +171,7 @@ private fun VolumeDialogSlider( @Composable private fun BoxScope.VolumeIcon( - drawable: Drawable, + icon: Icon.Loaded, isVisible: Boolean, modifier: Modifier = Modifier, ) { @@ -182,6 +181,6 @@ private fun BoxScope.VolumeIcon( exit = fadeOut(animationSpec = tween(durationMillis = 50)), modifier = modifier.align(Alignment.Center).size(40.dp).padding(10.dp), ) { - Icon(painter = DrawablePainter(drawable), contentDescription = null) + Icon(icon) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt index ef147c741bec..3712276488ff 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt @@ -18,32 +18,36 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel import android.annotation.SuppressLint import android.content.Context -import android.graphics.drawable.Drawable import android.media.AudioManager import androidx.annotation.DrawableRes import com.android.settingslib.R as SettingsR import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.RingerMode +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.res.R import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.withContext @SuppressLint("UseCompatLoadingForDrawables") class VolumeDialogSliderIconProvider @Inject constructor( private val context: Context, + @UiBackground private val uiBackgroundContext: CoroutineContext, private val zenModeInteractor: ZenModeInteractor, private val audioVolumeInteractor: AudioVolumeInteractor, ) { - fun getAudioSharingIcon(isMuted: Boolean): Flow<Drawable> { + fun getAudioSharingIcon(isMuted: Boolean): Flow<Icon.Loaded> { return flow { val iconRes = if (isMuted) { @@ -51,11 +55,12 @@ constructor( } else { R.drawable.ic_volume_media_bt } - emit(context.getDrawable(iconRes)!!) + val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! } + emit(Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes)) } } - fun getCastIcon(isMuted: Boolean): Flow<Drawable> { + fun getCastIcon(isMuted: Boolean): Flow<Icon.Loaded> { return flow { val iconRes = if (isMuted) { @@ -63,7 +68,8 @@ constructor( } else { SettingsR.drawable.ic_volume_remote } - emit(context.getDrawable(iconRes)!!) + val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! } + emit(Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes)) } } @@ -74,15 +80,18 @@ constructor( levelMax: Int, isMuted: Boolean, isRoutedToBluetooth: Boolean, - ): Flow<Drawable> { + ): Flow<Icon.Loaded> { return combine( zenModeInteractor.activeModesBlockingStream(stream), ringerModeForStream(stream), ) { activeModesBlockingStream, ringerMode -> if (activeModesBlockingStream?.mainMode?.icon != null) { - return@combine activeModesBlockingStream.mainMode.icon.drawable + Icon.Loaded( + drawable = activeModesBlockingStream.mainMode.icon.drawable, + contentDescription = null, + ) } else { - context.getDrawable( + val iconRes = getIconRes( stream, level, @@ -92,7 +101,8 @@ constructor( isRoutedToBluetooth, ringerMode, ) - )!! + val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! } + Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt index 88a061f3813c..ed59598d97d0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt @@ -17,7 +17,7 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel import android.content.Context -import android.graphics.drawable.Drawable +import com.android.systemui.common.shared.model.Icon import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel import com.android.systemui.volume.dialog.shared.model.streamLabel @@ -25,14 +25,14 @@ data class VolumeDialogSliderStateModel( val value: Float, val isDisabled: Boolean, val valueRange: ClosedFloatingPointRange<Float>, - val icon: Drawable, + val icon: Icon.Loaded, val label: String, ) fun VolumeDialogStreamModel.toStateModel( context: Context, isDisabled: Boolean, - icon: Drawable, + icon: Icon.Loaded, ): VolumeDialogSliderStateModel { return VolumeDialogSliderStateModel( value = level.toFloat(), diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index f6aa189eb571..faf0abd4cabd 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -108,11 +108,9 @@ constructor( isMuted = isMuted, isRoutedToBluetooth = routedToBluetooth, ) - is VolumeDialogSliderType.RemoteMediaStream -> { volumeDialogSliderIconProvider.getCastIcon(isMuted) } - is VolumeDialogSliderType.AudioSharingStream -> { volumeDialogSliderIconProvider.getAudioSharingIcon(isMuted) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt index 3d98ebacc7ca..f6452679cbf4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt @@ -16,9 +16,11 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel +import android.content.Context import com.android.internal.logging.UiEventLogger import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.res.R @@ -28,6 +30,7 @@ import com.android.systemui.volume.panel.ui.VolumePanelUiEvent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -39,11 +42,14 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext class AudioSharingStreamSliderViewModel @AssistedInject constructor( + private val context: Context, @Assisted private val coroutineScope: CoroutineScope, + @UiBackground private val uiBackgroundContext: CoroutineContext, private val audioSharingInteractor: AudioSharingInteractor, private val uiEventLogger: UiEventLogger, private val hapticsViewModelFactory: SliderHapticsViewModel.Factory, @@ -51,6 +57,12 @@ constructor( ) : SliderViewModel { private val volumeChanges = MutableStateFlow<Int?>(null) + private val audioSharingIcon = + Icon.Loaded( + drawable = context.getDrawable(R.drawable.ic_volume_media_bt)!!, + contentDescription = null, + res = R.drawable.ic_volume_media_bt, + ) override val slider: StateFlow<SliderState> = combine( audioSharingInteractor.volume.distinctUntilChanged().onEach { @@ -62,16 +74,17 @@ constructor( if (volume == null) { SliderState.Empty } else { - - State( - value = volume.toFloat(), - valueRange = - audioSharingInteractor.volumeMin.toFloat()..audioSharingInteractor - .volumeMax - .toFloat(), - icon = Icon.Resource(R.drawable.ic_volume_media_bt, null), - label = deviceName, - ) + withContext(uiBackgroundContext) { + State( + value = volume.toFloat(), + valueRange = + audioSharingInteractor.volumeMin.toFloat()..audioSharingInteractor + .volumeMax + .toFloat(), + icon = audioSharingIcon, + label = deviceName, + ) + } } } .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty) @@ -107,7 +120,7 @@ constructor( private data class State( override val value: Float, override val valueRange: ClosedFloatingPointRange<Float>, - override val icon: Icon, + override val icon: Icon.Loaded?, override val label: String, ) : SliderState { override val hapticFilter: SliderHapticFeedbackFilter @@ -116,7 +129,7 @@ constructor( override val isEnabled: Boolean get() = true - override val a11yStep: Float + override val step: Float get() = 1f override val disabledMessage: String? @@ -125,6 +138,9 @@ constructor( override val isMutable: Boolean get() = false + override val a11yContentDescription: String + get() = label + override val a11yClickDescription: String? get() = null diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index 9d32285fecb3..9fe0ad42cdba 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel import android.content.Context import android.media.AudioManager import android.util.Log +import androidx.annotation.DrawableRes import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.CachedBluetoothDevice @@ -28,6 +29,7 @@ import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.modes.shared.ModesUiIcons @@ -40,18 +42,21 @@ import com.android.systemui.volume.panel.ui.VolumePanelUiEvent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext /** Models a particular slider state. */ class AudioStreamSliderViewModel @@ -59,10 +64,11 @@ class AudioStreamSliderViewModel constructor( @Assisted private val audioStreamWrapper: FactoryAudioStreamWrapper, @Assisted private val coroutineScope: CoroutineScope, + @UiBackground private val uiBackgroundContext: CoroutineContext, private val context: Context, private val audioVolumeInteractor: AudioVolumeInteractor, private val zenModeInteractor: ZenModeInteractor, - private val audioSharingInteractor: AudioSharingInteractor, + audioSharingInteractor: AudioSharingInteractor, private val uiEventLogger: UiEventLogger, private val volumePanelLogger: VolumePanelLogger, private val hapticsViewModelFactory: SliderHapticsViewModel.Factory, @@ -148,57 +154,69 @@ constructor( null } - private fun AudioStreamModel.toState( + private suspend fun AudioStreamModel.toState( isEnabled: Boolean, ringerMode: RingerMode, disabledMessage: String?, inAudioSharing: Boolean, primaryDevice: CachedBluetoothDevice?, - ): State { - val label = getLabel(inAudioSharing, primaryDevice) - val icon = getIcon(ringerMode, inAudioSharing) - return State( - value = volume.toFloat(), - valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), - hapticFilter = createHapticFilter(ringerMode), - icon = icon, - label = label, - disabledMessage = disabledMessage, - isEnabled = isEnabled, - a11yStep = volumeRange.step.toFloat(), - a11yClickDescription = - if (isAffectedByMute) { - context.getString( - if (isMuted) { - R.string.volume_panel_hint_unmute - } else { - R.string.volume_panel_hint_mute - }, - label, - ) - } else { - null - }, - a11yStateDescription = - if (isMuted) { - context.getString( - if (isAffectedByRingerMode) { - if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { - R.string.volume_panel_hint_vibrate + ): State = + withContext(uiBackgroundContext) { + val label = getLabel(inAudioSharing, primaryDevice) + State( + value = volume.toFloat(), + valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), + hapticFilter = createHapticFilter(ringerMode), + icon = getIcon(ringerMode, inAudioSharing), + label = label, + disabledMessage = disabledMessage, + isEnabled = isEnabled, + step = volumeRange.step.toFloat(), + a11yContentDescription = + if (isEnabled) { + label + } else { + disabledMessage?.let { + context.getString( + R.string.volume_slider_disabled_message_template, + label, + disabledMessage, + ) + } ?: label + }, + a11yClickDescription = + if (isAffectedByMute) { + context.getString( + if (isMuted) { + R.string.volume_panel_hint_unmute + } else { + R.string.volume_panel_hint_mute + }, + label, + ) + } else { + null + }, + a11yStateDescription = + if (isMuted) { + context.getString( + if (isAffectedByRingerMode) { + if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { + R.string.volume_panel_hint_vibrate + } else { + R.string.volume_panel_hint_muted + } } else { R.string.volume_panel_hint_muted } - } else { - R.string.volume_panel_hint_muted - } - ) - } else { - null - }, - audioStreamModel = this, - isMutable = isAffectedByMute, - ) - } + ) + } else { + null + }, + audioStreamModel = this@toState, + isMutable = isAffectedByMute, + ) + } private fun AudioStreamModel.createHapticFilter( ringerMode: RingerMode @@ -220,12 +238,14 @@ constructor( flowOf(context.getString(R.string.stream_notification_unavailable)) } else { if (zenModeInteractor.canBeBlockedByZenMode(audioStream)) { - zenModeInteractor.activeModesBlockingStream(audioStream).map { blockingZenModes - -> - blockingZenModes.mainMode?.name?.let { - context.getString(R.string.stream_unavailable_by_modes, it) - } ?: context.getString(R.string.stream_unavailable_by_unknown) - } + zenModeInteractor + .activeModesBlockingStream(audioStream) + .map { blockingZenModes -> + blockingZenModes.mainMode?.name?.let { + context.getString(R.string.stream_unavailable_by_modes, it) + } ?: context.getString(R.string.stream_unavailable_by_unknown) + } + .distinctUntilChanged() } else { flowOf(context.getString(R.string.stream_unavailable_by_unknown)) } @@ -256,8 +276,11 @@ constructor( ?: error("No label for the stream: $audioStream") } - private fun AudioStreamModel.getIcon(ringerMode: RingerMode, inAudioSharing: Boolean): Icon { - val iconRes = + private fun AudioStreamModel.getIcon( + ringerMode: RingerMode, + inAudioSharing: Boolean, + ): Icon.Loaded { + val iconResource: Int = if (isMuted) { if (isAffectedByRingerMode) { if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { @@ -272,14 +295,21 @@ constructor( inAudioSharing ) { R.drawable.ic_volume_media_bt_mute - } else R.drawable.ic_volume_off + } else { + R.drawable.ic_volume_off + } } } else { getIconByStream(audioStream, inAudioSharing) } - return Icon.Resource(iconRes, null) + return Icon.Loaded( + drawable = context.getDrawable(iconResource)!!, + contentDescription = null, + res = iconResource, + ) } + @DrawableRes private fun getIconByStream(audioStream: AudioStream, inAudioSharing: Boolean): Int = when (audioStream.value) { AudioManager.STREAM_MUSIC -> @@ -302,14 +332,15 @@ constructor( private data class State( override val value: Float, override val valueRange: ClosedFloatingPointRange<Float>, + override val step: Float, override val hapticFilter: SliderHapticFeedbackFilter, - override val icon: Icon, + override val icon: Icon.Loaded?, override val label: String, override val disabledMessage: String?, override val isEnabled: Boolean, - override val a11yStep: Float, override val a11yClickDescription: String?, override val a11yStateDescription: String?, + override val a11yContentDescription: String, override val isMutable: Boolean, val audioStreamModel: AudioStreamModel, ) : SliderState diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt index a6c809186ca5..01810f9aafc3 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt @@ -21,6 +21,7 @@ import android.media.session.MediaController.PlaybackInfo import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.res.R @@ -30,30 +31,40 @@ import com.android.systemui.volume.panel.shared.VolumePanelLogger import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext class CastVolumeSliderViewModel @AssistedInject constructor( @Assisted private val session: MediaDeviceSession, @Assisted private val coroutineScope: CoroutineScope, + @UiBackground private val uiBackgroundContext: CoroutineContext, private val context: Context, private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, private val hapticsViewModelFactory: SliderHapticsViewModel.Factory, private val volumePanelLogger: VolumePanelLogger, ) : SliderViewModel { + private val castLabel = context.getString(R.string.media_device_cast) + private val castIcon = + Icon.Loaded( + drawable = context.getDrawable(R.drawable.ic_cast)!!, + contentDescription = null, + res = R.drawable.ic_cast, + ) override val slider: StateFlow<SliderState> = mediaDeviceSessionInteractor .playbackInfo(session) .mapNotNull { volumePanelLogger.onVolumeUpdateReceived(session.sessionToken, it.currentVolume) - it.getCurrentState() + withContext(uiBackgroundContext) { it.getCurrentState() } } .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty) @@ -83,20 +94,20 @@ constructor( return State( value = currentVolume.toFloat(), valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), - icon = Icon.Resource(R.drawable.ic_cast, null), - label = context.getString(R.string.media_device_cast), + icon = castIcon, + label = castLabel, isEnabled = true, - a11yStep = 1f, + step = 1f, ) } private data class State( override val value: Float, override val valueRange: ClosedFloatingPointRange<Float>, - override val icon: Icon, + override val icon: Icon.Loaded?, override val label: String, override val isEnabled: Boolean, - override val a11yStep: Float, + override val step: Float, ) : SliderState { override val hapticFilter: SliderHapticFeedbackFilter get() = SliderHapticFeedbackFilter() @@ -107,6 +118,9 @@ constructor( override val isMutable: Boolean get() = false + override val a11yContentDescription: String + get() = label + override val a11yClickDescription: String? get() = null diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt index 4bc237bd36f5..b1d183404a9f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt @@ -27,18 +27,17 @@ import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter sealed interface SliderState { val value: Float val valueRange: ClosedFloatingPointRange<Float> + val step: Float val hapticFilter: SliderHapticFeedbackFilter - val icon: Icon? + // Force preloaded icon + val icon: Icon.Loaded? val isEnabled: Boolean val label: String - /** - * A11y slider controls works by adjusting one step up or down. The default slider step isn't - * enough to trigger rounding to the correct value. - */ - val a11yStep: Float + val a11yClickDescription: String? val a11yStateDescription: String? + val a11yContentDescription: String val disabledMessage: String? val isMutable: Boolean @@ -46,12 +45,13 @@ sealed interface SliderState { override val value: Float = 0f override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f override val hapticFilter = SliderHapticFeedbackFilter() - override val icon: Icon? = null + override val icon: Icon.Loaded? = null override val label: String = "" override val disabledMessage: String? = null - override val a11yStep: Float = 0f + override val step: Float = 0f override val a11yClickDescription: String? = null override val a11yStateDescription: String? = null + override val a11yContentDescription: String = label override val isEnabled: Boolean = true override val isMutable: Boolean = false } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt index f6582a005035..502b311f7b40 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt @@ -40,7 +40,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.SemanticsPropertyReceiver import androidx.compose.ui.semantics.clearAndSetSemantics @@ -52,7 +51,6 @@ import androidx.compose.ui.semantics.stateDescription import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.lifecycle.rememberViewModel -import com.android.systemui.res.R import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider import kotlin.math.round import kotlinx.coroutines.Job @@ -108,7 +106,8 @@ fun Slider( } } val semantics = - accessibilityParams.createSemantics( + createSemantics( + accessibilityParams, animatable.targetValue, valueRange, valueChange, @@ -167,24 +166,18 @@ private fun snapValue( return Math.round(coercedValue / stepDistance) * stepDistance } -@Composable -private fun AccessibilityParams.createSemantics( +private fun createSemantics( + params: AccessibilityParams, value: Float, valueRange: ClosedFloatingPointRange<Float>, onValueChanged: (Float) -> Unit, isEnabled: Boolean, stepDistance: Float, ): SemanticsPropertyReceiver.() -> Unit { - val semanticsContentDescription = - disabledMessage - ?.takeIf { !isEnabled } - ?.let { message -> - stringResource(R.string.volume_slider_disabled_message_template, label, message) - } ?: label return { - contentDescription = semanticsContentDescription + contentDescription = params.contentDescription if (isEnabled) { - currentStateDescription?.let { stateDescription = it } + params.stateDescription?.let { stateDescription = it } progressBarRangeInfo = ProgressBarRangeInfo(value, valueRange) } else { disabled() @@ -253,9 +246,8 @@ private fun Haptics.createViewModel( } data class AccessibilityParams( - val label: String, - val currentStateDescription: String? = null, - val disabledMessage: String? = null, + val contentDescription: String, + val stateDescription: String? = null, ) sealed interface Haptics { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java index 146488b523ad..108f3ae86f9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java @@ -69,4 +69,25 @@ public class MenuViewAppearanceTest extends SysuiTestCase { assertThat(end_y).isEqualTo(end_y); } + + @Test + public void avoidVerticalDisplayCutout_doesNotExceedTopBounds() { + final int y = DRAGGABLE_BOUNDS.top - 100; + + final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout( + y, MENU_HEIGHT, DRAGGABLE_BOUNDS, new Rect(0, 5, 0, 6)); + + assertThat(end_y).isGreaterThan(DRAGGABLE_BOUNDS.top); + } + + + @Test + public void avoidVerticalDisplayCutout_doesNotExceedBottomBounds() { + final int y = DRAGGABLE_BOUNDS.bottom + 100; + + final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout( + y, MENU_HEIGHT, DRAGGABLE_BOUNDS, new Rect(0, 5, 0, 6)); + + assertThat(end_y).isLessThan(DRAGGABLE_BOUNDS.bottom); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 8fb2a245921a..6a9a485151de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -734,6 +734,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); NotificationEntry entry = mock(NotificationEntry.class); when(row.getEntry()).thenReturn(entry); + when(row.getEntryLegacy()).thenReturn(entry); when(entry.isAmbient()).thenReturn(false); EntryAdapter entryAdapter = mock(EntryAdapter.class); when(entryAdapter.isAmbient()).thenReturn(false); @@ -753,6 +754,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); NotificationEntry entry = mock(NotificationEntry.class); when(row.getEntry()).thenReturn(entry); + when(row.getEntryLegacy()).thenReturn(entry); when(entry.isAmbient()).thenReturn(true); EntryAdapter entryAdapter = mock(EntryAdapter.class); when(entryAdapter.isAmbient()).thenReturn(true); @@ -772,6 +774,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); NotificationEntry entry = mock(NotificationEntry.class); when(row.getEntry()).thenReturn(entry); + when(row.getEntryLegacy()).thenReturn(entry); when(entry.isAmbient()).thenReturn(false); EntryAdapter entryAdapter = mock(EntryAdapter.class); when(entryAdapter.isAmbient()).thenReturn(false); @@ -1384,6 +1387,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { NotificationEntry entry = mock(NotificationEntry.class); when(entry.isSeenInShade()).thenReturn(true); ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + when(row.getEntryLegacy()).thenReturn(entry); when(row.getEntry()).thenReturn(entry); // WHEN we generate an add event @@ -1440,6 +1444,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { NotificationEntry entry = mock(NotificationEntry.class); when(row.canViewBeCleared()).thenReturn(true); when(row.getEntry()).thenReturn(entry); + when(row.getEntryLegacy()).thenReturn(entry); when(entry.isClearable()).thenReturn(true); EntryAdapter entryAdapter = mock(EntryAdapter.class); when(entryAdapter.isClearable()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index a3616d20e11f..a7f3fdcb517e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -28,8 +28,8 @@ import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED; import static android.provider.Settings.Global.HEADS_UP_ON; import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE; -import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION; import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; +import static com.android.systemui.shared.Flags.FLAG_AMBIENT_AOD; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.phone.CentralSurfaces.MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU; @@ -242,7 +242,7 @@ import javax.inject.Provider; @SmallTest @RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) -@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION) +@EnableFlags(FLAG_AMBIENT_AOD) public class CentralSurfacesImplTest extends SysuiTestCase { private static final DeviceState FOLD_STATE_FOLDED = new DeviceState( 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/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt index 09f9f1c6362e..44d7a22b6258 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel import android.content.applicationContext import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.volume.domain.interactor.audioVolumeInteractor @@ -25,6 +26,7 @@ val Kosmos.volumeDialogSliderIconProvider by Kosmos.Fixture { VolumeDialogSliderIconProvider( context = applicationContext, + uiBackgroundContext = backgroundCoroutineContext, audioVolumeInteractor = audioVolumeInteractor, zenModeInteractor = zenModeInteractor, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt index 8c8d0240f572..6e43d79295ff 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt @@ -16,9 +16,11 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel +import android.content.applicationContext import com.android.internal.logging.uiEventLogger import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.volume.domain.interactor.audioSharingInteractor import com.android.systemui.volume.shared.volumePanelLogger import kotlinx.coroutines.CoroutineScope @@ -28,7 +30,9 @@ val Kosmos.audioSharingStreamSliderViewModelFactory by object : AudioSharingStreamSliderViewModel.Factory { override fun create(coroutineScope: CoroutineScope): AudioSharingStreamSliderViewModel { return AudioSharingStreamSliderViewModel( + applicationContext, coroutineScope, + backgroundCoroutineContext, audioSharingInteractor, uiEventLogger, sliderHapticsViewModelFactory, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt index 88c716e0ab10..47016e535ea4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt @@ -20,6 +20,7 @@ import android.content.applicationContext import com.android.internal.logging.uiEventLogger import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.volume.domain.interactor.audioSharingInteractor import com.android.systemui.volume.domain.interactor.audioVolumeInteractor @@ -37,6 +38,7 @@ val Kosmos.audioStreamSliderViewModelFactory by return AudioStreamSliderViewModel( audioStream, coroutineScope, + backgroundCoroutineContext, applicationContext, audioVolumeInteractor, zenModeInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt index 6875619d45fc..ed51e054e50c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel import android.content.applicationContext import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.volume.mediaDeviceSessionInteractor import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession import com.android.systemui.volume.shared.volumePanelLogger @@ -34,6 +35,7 @@ val Kosmos.castVolumeSliderViewModelFactory by return CastVolumeSliderViewModel( session, coroutineScope, + backgroundCoroutineContext, applicationContext, mediaDeviceSessionInteractor, sliderHapticsViewModelFactory, diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt index b956e44e0618..90a92712bf81 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt @@ -281,7 +281,6 @@ internal class DepthTracker { }, ) } - downstreamSet.clear() } } reset() diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt index c11eb122597d..81f37022a868 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt @@ -145,7 +145,14 @@ internal class MuxDeferredNode<W, K, V>( val conn = branchNode.upstream severed.add(conn) conn.removeDownstream(downstream = branchNode.schedulable) - depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + if (conn.depthTracker.snapshotIsDirect) { + depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + } else { + depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth) + depthTracker.updateIndirectRoots( + removals = conn.depthTracker.snapshotIndirectRoots + ) + } } } @@ -156,7 +163,14 @@ internal class MuxDeferredNode<W, K, V>( val conn = branchNode.upstream severed.add(conn) conn.removeDownstream(downstream = branchNode.schedulable) - depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + if (conn.depthTracker.snapshotIsDirect) { + depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + } else { + depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth) + depthTracker.updateIndirectRoots( + removals = conn.depthTracker.snapshotIndirectRoots + ) + } } // add new @@ -343,13 +357,8 @@ private class MuxDeferredActivator<W, K, V>( val (patchesConn, needsEval) = getPatches(evalScope).activate(evalScope, downstream = muxNode.schedulable) ?: run { - // Turns out we can't connect to patches, so update our depth and - // propagate - if (muxNode.depthTracker.setIsIndirectRoot(false)) { - // TODO: schedules might not be necessary now that we're not - // parallel? - muxNode.depthTracker.schedule(evalScope.scheduler, muxNode) - } + // Turns out we can't connect to patches, so update our depth + muxNode.depthTracker.setIsIndirectRoot(false) return } muxNode.patches = patchesConn diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt index cb2c6e51df66..faef6a21d4f3 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt @@ -109,7 +109,14 @@ internal class MuxPromptNode<W, K, V>( val conn: NodeConnection<V> = branchNode.upstream severed.add(conn) conn.removeDownstream(downstream = branchNode.schedulable) - depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + if (conn.depthTracker.snapshotIsDirect) { + depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + } else { + depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth) + depthTracker.updateIndirectRoots( + removals = conn.depthTracker.snapshotIndirectRoots + ) + } } } @@ -123,7 +130,14 @@ internal class MuxPromptNode<W, K, V>( val conn: NodeConnection<V> = oldBranch.upstream severed.add(conn) conn.removeDownstream(downstream = oldBranch.schedulable) - depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + if (conn.depthTracker.snapshotIsDirect) { + depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth) + } else { + depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth) + depthTracker.updateIndirectRoots( + removals = conn.depthTracker.snapshotIndirectRoots + ) + } } // add new diff --git a/ravenwood/tools/hoststubgen/Android.bp b/ravenwood/tools/hoststubgen/Android.bp index 004834eed983..e605318f4a10 100644 --- a/ravenwood/tools/hoststubgen/Android.bp +++ b/ravenwood/tools/hoststubgen/Android.bp @@ -99,6 +99,7 @@ java_library_host { "ow2-asm-commons", "ow2-asm-tree", "ow2-asm-util", + "apache-commons-compress", ], } diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt index b2af7827f8c5..a62f66dd18e5 100644 --- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt @@ -16,6 +16,15 @@ package com.android.hoststubgen import java.io.PrintWriter +import java.util.zip.CRC32 +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream +import org.apache.commons.compress.archivers.zip.ZipFile + +/** + * Whether to skip compression when adding processed entries back to a zip file. + */ +private const val SKIP_COMPRESSION = false /** * Name of this executable. Set it in the main method. @@ -118,3 +127,29 @@ inline fun runMainWithBoilerplate(realMain: () -> Unit) { System.exit(if (success) 0 else 1 ) } + +/** + * Copy a single ZIP entry to the output. + */ +fun copyZipEntry( + inZip: ZipFile, + entry: ZipArchiveEntry, + out: ZipArchiveOutputStream, +) { + inZip.getRawInputStream(entry).use { out.addRawArchiveEntry(entry, it) } +} + +/** + * Add a single ZIP entry with data. + */ +fun ZipArchiveOutputStream.addBytesEntry(name: String, data: ByteArray) { + val newEntry = ZipArchiveEntry(name) + if (SKIP_COMPRESSION) { + newEntry.method = 0 + newEntry.size = data.size.toLong() + newEntry.crc = CRC32().apply { update(data) }.value + } + putArchiveEntry(newEntry) + write(data) + closeArchiveEntry() +} diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt index e2647eb13ed3..40d343ab9658 100644 --- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt @@ -18,21 +18,20 @@ package com.android.hoststubgen.asm import com.android.hoststubgen.ClassParseException import com.android.hoststubgen.InvalidJarFileException import com.android.hoststubgen.log -import org.objectweb.asm.ClassReader -import org.objectweb.asm.tree.AnnotationNode -import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.FieldNode -import org.objectweb.asm.tree.MethodNode -import org.objectweb.asm.tree.TypeAnnotationNode -import java.io.BufferedInputStream import java.io.PrintWriter import java.util.Arrays import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference import java.util.function.Consumer -import java.util.zip.ZipEntry -import java.util.zip.ZipFile +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipFile +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.AnnotationNode +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.FieldNode +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.tree.TypeAnnotationNode /** * Stores all classes loaded from a jar file, in a form of [ClassNode] @@ -189,7 +188,8 @@ class ClassNodes { * Load all the classes, without code. */ fun loadClassStructures( - inJar: String, + inJar: ZipFile, + jarName: String, timeCollector: Consumer<Double>? = null, ): ClassNodes { val allClasses = ClassNodes() @@ -201,20 +201,19 @@ class ClassNodes { val exception = AtomicReference<Throwable>() // Called on a BG thread. Read a single jar entry and add it to [allClasses]. - fun parseClass(inZip: ZipFile, entry: ZipEntry) { + fun parseClass(inZip: ZipFile, entry: ZipArchiveEntry) { try { - inZip.getInputStream(entry).use { ins -> - val cr = ClassReader(BufferedInputStream(ins)) - val cn = ClassNode() - cr.accept( - cn, ClassReader.SKIP_CODE - or ClassReader.SKIP_DEBUG - or ClassReader.SKIP_FRAMES - ) - synchronized(allClasses) { - if (!allClasses.addClass(cn)) { - log.w("Duplicate class found: ${cn.name}") - } + val classBytes = inZip.getInputStream(entry).use { it.readAllBytes() } + val cr = ClassReader(classBytes) + val cn = ClassNode() + cr.accept( + cn, ClassReader.SKIP_CODE + or ClassReader.SKIP_DEBUG + or ClassReader.SKIP_FRAMES + ) + synchronized(allClasses) { + if (!allClasses.addClass(cn)) { + log.w("Duplicate class found: ${cn.name}") } } } catch (e: Throwable) { @@ -224,35 +223,30 @@ class ClassNodes { } // Actually open the jar and read it on worker threads. - val time = log.iTime("Reading class structure from $inJar") { + val time = log.iTime("Reading class structure from $jarName") { log.withIndent { - ZipFile(inJar).use { inZip -> - val inEntries = inZip.entries() - - while (inEntries.hasMoreElements()) { - val entry = inEntries.nextElement() - - if (entry.name.endsWith(".class")) { - executor.submit { - parseClass(inZip, entry) - } - } else if (entry.name.endsWith(".dex")) { - // Seems like it's an ART jar file. We can't process it. - // It's a fatal error. - throw InvalidJarFileException( - "$inJar is not a desktop jar file." - + " It contains a *.dex file." - ) - } else { - // Unknown file type. Skip. + inJar.entries.asSequence().forEach { entry -> + if (entry.name.endsWith(".class")) { + executor.submit { + parseClass(inJar, entry) } + } else if (entry.name.endsWith(".dex")) { + // Seems like it's an ART jar file. We can't process it. + // It's a fatal error. + throw InvalidJarFileException( + "$jarName is not a desktop jar file." + + " It contains a *.dex file." + ) + } else { + // Unknown file type. Skip. } - // Wait for all the work to complete. (must do it before closing the zip) - log.i("Waiting for all loaders to finish...") - executor.shutdown() - executor.awaitTermination(5, TimeUnit.MINUTES) - log.i("All loaders to finished.") } + + // Wait for all the work to complete. (must do it before closing the zip) + log.i("Waiting for all loaders to finish...") + executor.shutdown() + executor.awaitTermination(5, TimeUnit.MINUTES) + log.i("All loaders to finished.") } // If any exception is detected, throw it. @@ -261,13 +255,13 @@ class ClassNodes { } if (allClasses.size == 0) { - log.w("$inJar contains no *.class files.") + log.w("$jarName contains no *.class files.") } else { - log.i("Loaded ${allClasses.size} classes from $inJar.") + log.i("Loaded ${allClasses.size} classes from $jarName.") } } timeCollector?.accept(time) return allClasses } } -}
\ No newline at end of file +} diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 333540573364..2edcb2a6c199 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -19,13 +19,11 @@ import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.dumper.ApiDumper import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.filters.printAsTextPolicy -import java.io.BufferedInputStream -import java.io.BufferedOutputStream import java.io.FileOutputStream import java.io.PrintWriter -import java.util.zip.ZipEntry -import java.util.zip.ZipFile -import java.util.zip.ZipOutputStream +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream +import org.apache.commons.compress.archivers.zip.ZipFile /** * Actual main class. @@ -34,9 +32,10 @@ class HostStubGen(val options: HostStubGenOptions) { fun run() { val errors = HostStubGenErrors() val stats = HostStubGenStats() + val inJar = ZipFile(options.inJar.get) // Load all classes. - val allClasses = ClassNodes.loadClassStructures(options.inJar.get) + val allClasses = ClassNodes.loadClassStructures(inJar, options.inJar.get) // Dump the classes, if specified. options.inputJarDumpFile.ifSet { @@ -59,7 +58,7 @@ class HostStubGen(val options: HostStubGenOptions) { val processor = HostStubGenClassProcessor(options, allClasses, errors, stats) // Transform the jar. - convert( + inJar.convert( options.inJar.get, options.outJar.get, processor, @@ -88,7 +87,7 @@ class HostStubGen(val options: HostStubGenOptions) { /** * Convert a JAR file into "stub" and "impl" JAR files. */ - private fun convert( + private fun ZipFile.convert( inJar: String, outJar: String?, processor: HostStubGenClassProcessor, @@ -100,45 +99,39 @@ class HostStubGen(val options: HostStubGenOptions) { log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled") log.iTime("Transforming jar") { - var itemIndex = 0 var numItemsProcessed = 0 var numItems = -1 // == Unknown log.withIndent { - // Open the input jar file and process each entry. - ZipFile(inJar).use { inZip -> - - numItems = inZip.size() - val shardStart = numItems * shard / numShards - val shardNextStart = numItems * (shard + 1) / numShards - - maybeWithZipOutputStream(outJar) { outStream -> - val inEntries = inZip.entries() - while (inEntries.hasMoreElements()) { - val entry = inEntries.nextElement() - val inShard = (shardStart <= itemIndex) - && (itemIndex < shardNextStart) - itemIndex++ - if (!inShard) { - continue - } - convertSingleEntry(inZip, entry, outStream, processor) - numItemsProcessed++ + val entries = entries.toList() + + numItems = entries.size + val shardStart = numItems * shard / numShards + val shardNextStart = numItems * (shard + 1) / numShards + + maybeWithZipOutputStream(outJar) { outStream -> + entries.forEachIndexed { itemIndex, entry -> + val inShard = (shardStart <= itemIndex) + && (itemIndex < shardNextStart) + if (!inShard) { + return@forEachIndexed } - log.i("Converted all entries.") + convertSingleEntry(this, entry, outStream, processor) + numItemsProcessed++ } - outJar?.let { log.i("Created: $it") } + log.i("Converted all entries.") } + outJar?.let { log.i("Created: $it") } } log.i("%d / %d item(s) processed.", numItemsProcessed, numItems) } } - private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T { + private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipArchiveOutputStream?) -> T): T { if (filename == null) { return block(null) } - return ZipOutputStream(BufferedOutputStream(FileOutputStream(filename))).use(block) + return ZipArchiveOutputStream(FileOutputStream(filename).buffered()).use(block) } /** @@ -146,8 +139,8 @@ class HostStubGen(val options: HostStubGenOptions) { */ private fun convertSingleEntry( inZip: ZipFile, - entry: ZipEntry, - outStream: ZipOutputStream?, + entry: ZipArchiveEntry, + outStream: ZipArchiveOutputStream?, processor: HostStubGenClassProcessor ) { log.d("Entry: %s", entry.name) @@ -181,32 +174,12 @@ class HostStubGen(val options: HostStubGenOptions) { } /** - * Copy a single ZIP entry to the output. - */ - private fun copyZipEntry( - inZip: ZipFile, - entry: ZipEntry, - out: ZipOutputStream, - ) { - // TODO: It seems like copying entries this way is _very_ slow, - // even with out.setLevel(0). Look for other ways to do it. - - inZip.getInputStream(entry).use { ins -> - // Copy unknown entries as is to the impl out. (but not to the stub out.) - val outEntry = ZipEntry(entry.name) - out.putNextEntry(outEntry) - ins.transferTo(out) - out.closeEntry() - } - } - - /** * Convert a single class. */ private fun processSingleClass( inZip: ZipFile, - entry: ZipEntry, - outStream: ZipOutputStream?, + entry: ZipArchiveEntry, + outStream: ZipArchiveOutputStream?, processor: HostStubGenClassProcessor ) { val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "") @@ -227,12 +200,10 @@ class HostStubGen(val options: HostStubGenOptions) { if (outStream != null) { log.v("Creating class: %s Policy: %s", classInternalName, classPolicy) log.withIndent { - BufferedInputStream(inZip.getInputStream(entry)).use { bis -> - val newEntry = ZipEntry(newName) - outStream.putNextEntry(newEntry) - val classBytecode = bis.readAllBytes() - outStream.write(processor.processClassBytecode(classBytecode)) - outStream.closeEntry() + inZip.getInputStream(entry).use { zis -> + var classBytecode = zis.readAllBytes() + classBytecode = processor.processClassBytecode(classBytecode) + outStream.addBytesEntry(newName, classBytecode) } } } diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt index 04e3bda2ba27..8e36323fd495 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt @@ -17,17 +17,17 @@ package com.android.platform.test.ravenwood.ravenizer import com.android.hoststubgen.GeneralUserErrorException import com.android.hoststubgen.HostStubGenClassProcessor +import com.android.hoststubgen.addBytesEntry import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.zipEntryNameToClassName +import com.android.hoststubgen.copyZipEntry import com.android.hoststubgen.executableName import com.android.hoststubgen.log import com.android.platform.test.ravenwood.ravenizer.adapter.RunnerRewritingAdapter -import java.io.BufferedInputStream -import java.io.BufferedOutputStream import java.io.FileOutputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipFile -import java.util.zip.ZipOutputStream +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream +import org.apache.commons.compress.archivers.zip.ZipFile import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter @@ -93,13 +93,14 @@ class Ravenizer { val stats = RavenizerStats() stats.totalTime = log.nTime { - val allClasses = ClassNodes.loadClassStructures(options.inJar.get) { + val inJar = ZipFile(options.inJar.get) + val allClasses = ClassNodes.loadClassStructures(inJar, options.inJar.get) { stats.loadStructureTime = it } val processor = HostStubGenClassProcessor(options, allClasses) - process( - options.inJar.get, + inJar.process( + options.outJar.get, options.outJar.get, options.enableValidation.get, options.fatalValidation.get, @@ -111,7 +112,7 @@ class Ravenizer { log.i(stats.toString()) } - private fun process( + private fun ZipFile.process( inJar: String, outJar: String, enableValidation: Boolean, @@ -138,40 +139,34 @@ class Ravenizer { } stats.totalProcessTime = log.vTime("$executableName processing $inJar") { - ZipFile(inJar).use { inZip -> - val inEntries = inZip.entries() - - stats.totalEntries = inZip.size() - - ZipOutputStream(BufferedOutputStream(FileOutputStream(outJar))).use { outZip -> - while (inEntries.hasMoreElements()) { - val entry = inEntries.nextElement() - - if (entry.name.endsWith(".dex")) { - // Seems like it's an ART jar file. We can't process it. - // It's a fatal error. - throw GeneralUserErrorException( - "$inJar is not a desktop jar file. It contains a *.dex file." - ) - } + ZipArchiveOutputStream(FileOutputStream(outJar).buffered()).use { outZip -> + entries.asSequence().forEach { entry -> + stats.totalEntries++ + if (entry.name.endsWith(".dex")) { + // Seems like it's an ART jar file. We can't process it. + // It's a fatal error. + throw GeneralUserErrorException( + "$inJar is not a desktop jar file. It contains a *.dex file." + ) + } - if (stripMockito && entry.name.isMockitoFile()) { - // Skip this entry - continue - } + if (stripMockito && entry.name.isMockitoFile()) { + // Skip this entry + return@forEach + } - val className = zipEntryNameToClassName(entry.name) + val className = zipEntryNameToClassName(entry.name) - if (className != null) { - stats.totalClasses += 1 - } + if (className != null) { + stats.totalClasses += 1 + } - if (className != null && - shouldProcessClass(processor.allClasses, className)) { - processSingleClass(inZip, entry, outZip, processor, stats) - } else { - // Too slow, let's use merge_zips to bring back the original classes. - copyZipEntry(inZip, entry, outZip, stats) + if (className != null && + shouldProcessClass(processor.allClasses, className)) { + processSingleClass(this, entry, outZip, processor, stats) + } else { + stats.totalCopyTime += log.nTime { + copyZipEntry(this, entry, outZip) } } } @@ -179,53 +174,25 @@ class Ravenizer { } } - /** - * Copy a single ZIP entry to the output. - */ - private fun copyZipEntry( - inZip: ZipFile, - entry: ZipEntry, - out: ZipOutputStream, - stats: RavenizerStats, - ) { - stats.totalCopyTime += log.nTime { - inZip.getInputStream(entry).use { ins -> - // Copy unknown entries as is to the impl out. (but not to the stub out.) - val outEntry = ZipEntry(entry.name) - outEntry.method = 0 - outEntry.size = entry.size - outEntry.crc = entry.crc - out.putNextEntry(outEntry) - - ins.transferTo(out) - - out.closeEntry() - } - } - } - private fun processSingleClass( inZip: ZipFile, - entry: ZipEntry, - outZip: ZipOutputStream, + entry: ZipArchiveEntry, + outZip: ZipArchiveOutputStream, processor: HostStubGenClassProcessor, stats: RavenizerStats, ) { stats.processedClasses += 1 - val newEntry = ZipEntry(entry.name) - outZip.putNextEntry(newEntry) - - BufferedInputStream(inZip.getInputStream(entry)).use { bis -> - var classBytes = bis.readBytes() + inZip.getInputStream(entry).use { zis -> + var classBytes = zis.readAllBytes() stats.totalRavenizeTime += log.vTime("Ravenize ${entry.name}") { classBytes = ravenizeSingleClass(entry, classBytes, processor.allClasses) } stats.totalHostStubGenTime += log.vTime("HostStubGen ${entry.name}") { classBytes = processor.processClassBytecode(classBytes) } - outZip.write(classBytes) + // TODO: if the class does not change, use copyZipEntry + outZip.addBytesEntry(entry.name, classBytes) } - outZip.closeEntry() } /** @@ -237,7 +204,7 @@ class Ravenizer { } private fun ravenizeSingleClass( - entry: ZipEntry, + entry: ZipArchiveEntry, input: ByteArray, allClasses: ClassNodes, ): ByteArray { 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 658ea4c27e4c..193d82743a34 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -168,7 +168,6 @@ public class AdbDebuggingManager { private AdbConnectionInfo mAdbConnectionInfo = new AdbConnectionInfo(); // Polls for a tls port property when adb wifi is enabled private AdbConnectionPortPoller mConnectionPortPoller; - private final PortListenerImpl mPortListener = new PortListenerImpl(); private final Ticker mTicker; public AdbDebuggingManager(Context context) { @@ -323,10 +322,6 @@ public class AdbDebuggingManager { } } - interface AdbConnectionPortListener { - void onPortReceived(int port); - } - /** * This class will poll for a period of time for adbd to write the port * it connected to. @@ -336,16 +331,11 @@ public class AdbDebuggingManager { * port through different means. A better fix would be to always start AdbDebuggingManager, but * it needs to adjust accordingly on whether ro.adb.secure is set. */ - static class AdbConnectionPortPoller extends Thread { + private class AdbConnectionPortPoller extends Thread { private final String mAdbPortProp = "service.adb.tls.port"; - private AdbConnectionPortListener mListener; private final int mDurationSecs = 10; private AtomicBoolean mCanceled = new AtomicBoolean(false); - AdbConnectionPortPoller(AdbConnectionPortListener listener) { - mListener = listener; - } - @Override public void run() { Slog.d(TAG, "Starting adb port property poller"); @@ -362,13 +352,22 @@ public class AdbDebuggingManager { // to start the server. Otherwise we should have a valid port. int port = SystemProperties.getInt(mAdbPortProp, Integer.MAX_VALUE); if (port == -1 || (port > 0 && port <= 65535)) { - mListener.onPortReceived(port); + onPortReceived(port); return; } SystemClock.sleep(1000); } Slog.w(TAG, "Failed to receive adb connection port"); - mListener.onPortReceived(-1); + onPortReceived(-1); + } + + private void onPortReceived(int port) { + Slog.d(TAG, "Received tls port=" + port); + Message msg = mHandler.obtainMessage(port > 0 + ? AdbDebuggingHandler.MSG_SERVER_CONNECTED + : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED); + msg.obj = port; + mHandler.sendMessage(msg); } public void cancelAndWait() { @@ -382,17 +381,6 @@ public class AdbDebuggingManager { } } - class PortListenerImpl implements AdbConnectionPortListener { - public void onPortReceived(int port) { - Slog.d(TAG, "Received tls port=" + port); - Message msg = mHandler.obtainMessage(port > 0 - ? AdbDebuggingHandler.MSG_SERVER_CONNECTED - : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED); - msg.obj = port; - mHandler.sendMessage(msg); - } - } - @VisibleForTesting static class AdbDebuggingThread extends Thread { private boolean mStopped; @@ -800,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; @@ -1088,8 +1075,7 @@ public class AdbDebuggingManager { mContext.registerReceiver(mBroadcastReceiver, intentFilter); SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); - mConnectionPortPoller = - new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); + mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller(); mConnectionPortPoller.start(); startAdbDebuggingThread(); @@ -1106,9 +1092,6 @@ public class AdbDebuggingManager { setAdbConnectionInfo(null); mContext.unregisterReceiver(mBroadcastReceiver); - if (mThread != null) { - mThread.sendResponse(MSG_DISABLE_ADBDWIFI); - } onAdbdWifiServerDisconnected(-1); stopAdbDebuggingThread(); break; @@ -1138,8 +1121,7 @@ public class AdbDebuggingManager { mContext.registerReceiver(mBroadcastReceiver, intentFilter); SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); - mConnectionPortPoller = - new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); + mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller(); mConnectionPortPoller.start(); startAdbDebuggingThread(); @@ -1257,7 +1239,7 @@ public class AdbDebuggingManager { if (mAdbWifiEnabled) { // In scenarios where adbd is restarted, the tls port may change. mConnectionPortPoller = - new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); + new AdbDebuggingManager.AdbConnectionPortPoller(); mConnectionPortPoller.start(); } break; diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index 40f7c873eae8..d12a0a2a1e00 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -21,10 +21,8 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; import android.database.ContentObserver; -import android.debug.AdbManager; import android.debug.AdbManagerInternal; import android.debug.AdbTransportType; import android.debug.FingerprintAndPairDevice; @@ -40,10 +38,8 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemProperties; -import android.os.UserHandle; import android.provider.Settings; import android.service.adb.AdbServiceDumpProto; -import android.sysprop.AdbProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -63,7 +59,6 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; /** * The Android Debug Bridge (ADB) service. This controls the availability of ADB and authorization @@ -85,12 +80,6 @@ public class AdbService extends IAdbManager.Stub { */ static final String CTL_STOP = "ctl.stop"; - // The tcp port adb is currently using - AtomicInteger mConnectionPort = new AtomicInteger(-1); - - private final AdbConnectionPortListener mPortListener = new AdbConnectionPortListener(); - private AdbDebuggingManager.AdbConnectionPortPoller mConnectionPortPoller; - private final RemoteCallbackList<IAdbCallback> mCallbacks = new RemoteCallbackList<>(); /** * Manages the service lifecycle for {@code AdbService} in {@code SystemServer}. @@ -404,39 +393,6 @@ public class AdbService extends IAdbManager.Stub { Slog.d(TAG, "Unregistering callback " + callback); mCallbacks.unregister(callback); } - /** - * This listener is only used when ro.adb.secure=0. Otherwise, AdbDebuggingManager will - * do this. - */ - class AdbConnectionPortListener implements AdbDebuggingManager.AdbConnectionPortListener { - public void onPortReceived(int port) { - if (port > 0 && port <= 65535) { - mConnectionPort.set(port); - } else { - mConnectionPort.set(-1); - // Turn off wifi debugging, since the server did not start. - try { - Settings.Global.putInt(mContentResolver, - Settings.Global.ADB_WIFI_ENABLED, 0); - } catch (SecurityException e) { - // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't - // be changed. - Slog.d(TAG, "ADB_ENABLED is restricted."); - } - } - broadcastPortInfo(mConnectionPort.get()); - } - } - - private void broadcastPortInfo(int port) { - Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION); - intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, (port >= 0) - ? AdbManager.WIRELESS_STATUS_CONNECTED - : AdbManager.WIRELESS_STATUS_DISCONNECTED); - intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port); - AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL); - Slog.i(TAG, "sent port broadcast port=" + port); - } private void startAdbd() { SystemProperties.set(CTL_START, ADBD); @@ -470,20 +426,11 @@ public class AdbService extends IAdbManager.Stub { } else if (transportType == AdbTransportType.WIFI && enable != mIsAdbWifiEnabled) { mIsAdbWifiEnabled = enable; if (mIsAdbWifiEnabled) { - if (!AdbProperties.secure().orElse(false)) { - // Start adbd. If this is secure adb, then we defer enabling adb over WiFi. - SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); - mConnectionPortPoller = - new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); - mConnectionPortPoller.start(); - } + // Start adb over WiFi. + SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); } else { // Stop adb over WiFi. SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "0"); - if (mConnectionPortPoller != null) { - mConnectionPortPoller.cancelAndWait(); - mConnectionPortPoller = null; - } } } else { // No change 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/DeviceSelectActionFromTv.java b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java index 9118c46e3b22..574e484edd16 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java @@ -167,7 +167,11 @@ final class DeviceSelectActionFromTv extends HdmiCecFeatureAction { private boolean handleReportPowerStatus(int powerStatus) { switch (powerStatus) { case HdmiControlManager.POWER_STATUS_ON: - selectDevice(); + if (tv().getActiveSource().physicalAddress == mTarget.getPhysicalAddress()) { + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + } else { + selectDevice(); + } return true; case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY: if (mPowerStatusCounter < 4) { 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/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java index 6db62c8397f3..ccb9e3ea5cbe 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java @@ -301,7 +301,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub throw new IllegalArgumentException( "Unknown session ID in closeSession: id=" + sessionId); } - halCloseEndpointSession(sessionId, ContextHubServiceUtil.toHalReason(reason)); + mEndpointManager.halCloseEndpointSession( + sessionId, ContextHubServiceUtil.toHalReason(reason)); } @Override @@ -312,7 +313,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub // Iterate in reverse since cleanupSessionResources will remove the entry for (int i = mSessionMap.size() - 1; i >= 0; i--) { int id = mSessionMap.keyAt(i); - halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE); + mEndpointManager.halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE); cleanupSessionResources(id); } } @@ -444,7 +445,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub int id = mSessionMap.keyAt(i); HubEndpointInfo target = mSessionMap.get(id).getRemoteEndpointInfo(); if (!hasEndpointPermissions(target)) { - halCloseEndpointSessionNoThrow(id, Reason.PERMISSION_DENIED); + mEndpointManager.halCloseEndpointSessionNoThrow( + id, Reason.PERMISSION_DENIED); onCloseEndpointSession(id, Reason.PERMISSION_DENIED); // Resource cleanup is done in onCloseEndpointSession } @@ -503,17 +505,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */); } - /* package */ void onEndpointSessionOpenRequest( - int sessionId, HubEndpointInfo initiator, String serviceDescriptor) { - Optional<Byte> error = - onEndpointSessionOpenRequestInternal(sessionId, initiator, serviceDescriptor); - if (error.isPresent()) { - halCloseEndpointSessionNoThrow(sessionId, error.get()); - onCloseEndpointSession(sessionId, error.get()); - // Resource cleanup is done in onCloseEndpointSession - } - } - + /** Handle close endpoint callback to the client side */ /* package */ void onCloseEndpointSession(int sessionId, byte reason) { if (!cleanupSessionResources(sessionId)) { Log.w(TAG, "Unknown session ID in onCloseEndpointSession: id=" + sessionId); @@ -585,7 +577,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } } - private Optional<Byte> onEndpointSessionOpenRequestInternal( + /* package */ Optional<Byte> onEndpointSessionOpenRequest( int sessionId, HubEndpointInfo initiator, String serviceDescriptor) { if (!hasEndpointPermissions(initiator)) { Log.e( @@ -594,15 +586,41 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub + initiator + " doesn't have permission for " + mEndpointInfo); - return Optional.of(Reason.PERMISSION_DENIED); + byte reason = Reason.PERMISSION_DENIED; + onCloseEndpointSession(sessionId, reason); + return Optional.of(reason); } + // Check & handle error cases for duplicated session id. + final boolean existingSession; + final boolean existingSessionActive; synchronized (mOpenSessionLock) { if (hasSessionId(sessionId)) { - Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId); - return Optional.of(Reason.UNSPECIFIED); + existingSession = true; + existingSessionActive = mSessionMap.get(sessionId).isActive(); + Log.w( + TAG, + "onEndpointSessionOpenRequest: " + + "Existing session ID: " + + sessionId + + ", isActive: " + + existingSessionActive); + } else { + existingSession = false; + existingSessionActive = false; + mSessionMap.put(sessionId, new Session(initiator, true)); + } + } + + if (existingSession) { + if (existingSessionActive) { + // Existing session is already active, call onSessionOpenComplete. + openSessionRequestComplete(sessionId); + return Optional.empty(); } - mSessionMap.put(sessionId, new Session(initiator, true)); + // Reject the session open request for now. Consider invalidating previous pending + // session open request based on timeout. + return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED); } boolean success = @@ -610,7 +628,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub (consumer) -> consumer.onSessionOpenRequest( sessionId, initiator, serviceDescriptor)); - return success ? Optional.empty() : Optional.of(Reason.UNSPECIFIED); + byte reason = Reason.UNSPECIFIED; + if (!success) { + onCloseEndpointSession(sessionId, reason); + } + return success ? Optional.empty() : Optional.of(reason); } private byte onMessageReceivedInternal(int sessionId, HubMessage message) { @@ -657,29 +679,6 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } /** - * Calls the HAL closeEndpointSession API. - * - * @param sessionId The session ID to close - * @param halReason The HAL reason - */ - private void halCloseEndpointSession(int sessionId, byte halReason) throws RemoteException { - try { - mHubInterface.closeEndpointSession(sessionId, halReason); - } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { - throw e; - } - } - - /** Same as halCloseEndpointSession but does not throw the exception */ - private void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) { - try { - halCloseEndpointSession(sessionId, halReason); - } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { - Log.e(TAG, "Exception while calling HAL closeEndpointSession", e); - } - } - - /** * Cleans up resources related to a session with the provided ID. * * @param sessionId The session ID to clean up resources for diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java index 8ab581e1fb7a..e1561599517d 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java @@ -29,6 +29,7 @@ import android.hardware.contexthub.IContextHubEndpoint; import android.hardware.contexthub.IContextHubEndpointCallback; import android.hardware.contexthub.IEndpointCommunication; import android.hardware.contexthub.MessageDeliveryStatus; +import android.hardware.contexthub.Reason; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; @@ -42,6 +43,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; @@ -316,6 +318,11 @@ import java.util.function.Consumer; } } + /** Returns if a sessionId can be allocated for the service hub. */ + private boolean isSessionIdAllocatedForService(int sessionId) { + return sessionId > mMaxSessionId || sessionId < mMinSessionId; + } + /** * Unregisters an endpoint given its ID. * @@ -337,8 +344,7 @@ import java.util.function.Consumer; } } - @Override - public void onEndpointSessionOpenRequest( + private Optional<Byte> onEndpointSessionOpenRequestInternal( int sessionId, HubEndpointInfo.HubEndpointIdentifier destination, HubEndpointInfo.HubEndpointIdentifier initiator, @@ -348,7 +354,7 @@ import java.util.function.Consumer; TAG, "onEndpointSessionOpenRequest: invalid destination hub ID: " + destination.getHub()); - return; + return Optional.of(Reason.ENDPOINT_INVALID); } ContextHubEndpointBroker broker = mEndpointMap.get(destination.getEndpoint()); if (broker == null) { @@ -356,7 +362,7 @@ import java.util.function.Consumer; TAG, "onEndpointSessionOpenRequest: unknown destination endpoint ID: " + destination.getEndpoint()); - return; + return Optional.of(Reason.ENDPOINT_INVALID); } HubEndpointInfo initiatorInfo = mHubInfoRegistry.getEndpointInfo(initiator); if (initiatorInfo == null) { @@ -364,9 +370,29 @@ import java.util.function.Consumer; TAG, "onEndpointSessionOpenRequest: unknown initiator endpoint ID: " + initiator.getEndpoint()); - return; + return Optional.of(Reason.ENDPOINT_INVALID); + } + if (!isSessionIdAllocatedForService(sessionId)) { + Log.e( + TAG, + "onEndpointSessionOpenRequest: invalid session ID, rejected:" + + " sessionId=" + + sessionId); + return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED); } - broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor); + return broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor); + } + + @Override + public void onEndpointSessionOpenRequest( + int sessionId, + HubEndpointInfo.HubEndpointIdentifier destination, + HubEndpointInfo.HubEndpointIdentifier initiator, + String serviceDescriptor) { + Optional<Byte> errorOptional = + onEndpointSessionOpenRequestInternal( + sessionId, destination, initiator, serviceDescriptor); + errorOptional.ifPresent((error) -> halCloseEndpointSessionNoThrow(sessionId, error)); } @Override @@ -418,6 +444,30 @@ import java.util.function.Consumer; } } + /** + * Calls the HAL closeEndpointSession API. + * + * @param sessionId The session ID to close + * @param halReason The HAL reason + */ + /* package */ void halCloseEndpointSession(int sessionId, byte halReason) + throws RemoteException { + try { + mHubInterface.closeEndpointSession(sessionId, halReason); + } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { + throw e; + } + } + + /** Same as halCloseEndpointSession but does not throw the exception */ + /* package */ void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) { + try { + halCloseEndpointSession(sessionId, halReason); + } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { + Log.e(TAG, "Exception while calling HAL closeEndpointSession", e); + } + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); 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/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/AppCompatSafeRegionPolicy.java b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java index 959609309da1..cd6a01478eef 100644 --- a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java @@ -156,6 +156,8 @@ class AppCompatSafeRegionPolicy { void dump(@NonNull PrintWriter pw, @NonNull String prefix) { if (mNeedsSafeRegionBounds) { pw.println(prefix + " mNeedsSafeRegionBounds=true"); + pw.println( + prefix + " latestSafeRegionBoundsOnActivity=" + getLatestSafeRegionBounds()); } if (isLetterboxedForSafeRegionOnlyAllowed()) { pw.println(prefix + " isLetterboxForSafeRegionOnlyAllowed=true"); diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index 2798e843d6dd..ab87459da01a 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -218,6 +218,11 @@ class Dimmer { */ protected void adjustAppearance(@NonNull WindowState dimmingContainer, float alpha, int blurRadius) { + if (!mHost.isVisibleRequested()) { + // If the host is already going away, there is no point in keeping dimming + return; + } + if (mDimState != null || (alpha != 0 || blurRadius != 0)) { final DimState d = obtainDimState(dimmingContainer); d.prepareLookChange(alpha, blurRadius); 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/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java index f6b107b60d62..d60807c7b001 100644 --- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java @@ -62,6 +62,8 @@ public class PrepareGetRequestSession extends GetRequestSession { Collectors.toSet())).size(); // Dedupe type strings mRequestSessionMetric.collectGetFlowInitialMetricInfo(request); mPrepareGetCredentialCallback = prepareGetCredentialCallback; + + Slog.i(TAG, "PrepareGetRequestSession constructed."); } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 543e32fae55f..9ff6eb66064f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -34,6 +34,7 @@ import android.app.admin.PackagePolicyKey; import android.app.admin.PolicyKey; import android.app.admin.PolicyValue; import android.app.admin.UserRestrictionPolicyKey; +import android.app.admin.flags.Flags; import android.content.ComponentName; import android.content.Context; import android.content.IntentFilter; @@ -282,7 +283,9 @@ final class PolicyDefinition<V> { static PolicyDefinition<Set<String>> PERMITTED_INPUT_METHODS = new PolicyDefinition<>( new NoArgsPolicyKey(DevicePolicyIdentifiers.PERMITTED_INPUT_METHODS_POLICY), - new MostRecent<>(), + (Flags.usePolicyIntersectionForPermittedInputMethods() + ? new StringSetIntersection() + : new MostRecent<>()), POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE, PolicyEnforcerCallbacks::noOp, new PackageSetPolicySerializer()); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java new file mode 100644 index 000000000000..bc075b02b141 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.devicepolicy; + +import android.annotation.NonNull; +import android.app.admin.PolicyValue; +import android.app.admin.PackageSetPolicyValue; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.Set; + +final class StringSetIntersection extends ResolutionMechanism<Set<String>> { + + @Override + PolicyValue<Set<String>> resolve( + @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<Set<String>>> adminPolicies) { + Objects.requireNonNull(adminPolicies); + Set<String> intersectionOfPolicies = null; + for (PolicyValue<Set<String>> policy : adminPolicies.values()) { + if (intersectionOfPolicies == null) { + intersectionOfPolicies = new HashSet<>(policy.getValue()); + } else { + intersectionOfPolicies.retainAll(policy.getValue()); + } + } + if (intersectionOfPolicies == null) { + return null; + } + // Note that the resulting set below may be empty, but that's fine: + // particular policy should decide what is the meaning of an empty set. + return new PackageSetPolicyValue(intersectionOfPolicies); + } + + @Override + android.app.admin.StringSetIntersection getParcelableResolutionMechanism() { + return new android.app.admin.StringSetIntersection(); + } + + @Override + public String toString() { + return "StringSetIntersection {}"; + } +} 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/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java index a4c71bd6094e..2227eebdb128 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java @@ -251,6 +251,35 @@ public class DeviceSelectActionFromTvTest { } @Test + public void testDeviceSelect_DeviceAssertsActiveSource_singleSetStreamPathMessage() { + // TV was watching playback2 device connected at port 2, and wants to select + // playback1. + TestActionTimer actionTimer = new TestActionTimer(); + TestCallback callback = new TestCallback(); + DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback, + /*isCec20=*/false); + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, + "testDeviceSelect"); + action.start(); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + mNativeWrapper.clearResultMessages(); + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_1, + "testDeviceSelect"); + mTestLooper.dispatchAll(); + + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE); + action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_ON); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test public void testDeviceSelect_DeviceInStandbyStatus_Cec14b() { mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, "testDeviceSelect"); 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/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java index 4d2dcf65bfeb..43b1ec393bf6 100644 --- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -67,12 +68,15 @@ public class ContextHubEndpointTest { private static final int SESSION_ID_RANGE = ContextHubEndpointManager.SERVICE_SESSION_RANGE; private static final int MIN_SESSION_ID = 0; private static final int MAX_SESSION_ID = MIN_SESSION_ID + SESSION_ID_RANGE - 1; + private static final int SESSION_ID_FOR_OPEN_REQUEST = MAX_SESSION_ID + 1; + private static final int INVALID_SESSION_ID_FOR_OPEN_REQUEST = MIN_SESSION_ID + 1; private static final String ENDPOINT_NAME = "Example test endpoint"; private static final int ENDPOINT_ID = 1; private static final String ENDPOINT_PACKAGE_NAME = "com.android.server.location.contexthub"; private static final String TARGET_ENDPOINT_NAME = "Example target endpoint"; + private static final String ENDPOINT_SERVICE_DESCRIPTOR = "serviceDescriptor"; private static final int TARGET_ENDPOINT_ID = 1; private static final int SAMPLE_MESSAGE_TYPE = 1234; @@ -225,6 +229,105 @@ public class ContextHubEndpointTest { } @Test + public void testEndpointSessionOpenRequest() throws RemoteException { + assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE); + IContextHubEndpoint endpoint = registerExampleEndpoint(); + + HubEndpointInfo targetInfo = + new HubEndpointInfo( + TARGET_ENDPOINT_NAME, + TARGET_ENDPOINT_ID, + ENDPOINT_PACKAGE_NAME, + Collections.emptyList()); + mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo}); + mEndpointManager.onEndpointSessionOpenRequest( + SESSION_ID_FOR_OPEN_REQUEST, + endpoint.getAssignedHubEndpointInfo().getIdentifier(), + targetInfo.getIdentifier(), + ENDPOINT_SERVICE_DESCRIPTOR); + + verify(mMockCallback) + .onSessionOpenRequest( + SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR); + + // Accept + endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST); + verify(mMockEndpointCommunications) + .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST); + + unregisterExampleEndpoint(endpoint); + } + + @Test + public void testEndpointSessionOpenRequestWithInvalidSessionId() throws RemoteException { + assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE); + IContextHubEndpoint endpoint = registerExampleEndpoint(); + + HubEndpointInfo targetInfo = + new HubEndpointInfo( + TARGET_ENDPOINT_NAME, + TARGET_ENDPOINT_ID, + ENDPOINT_PACKAGE_NAME, + Collections.emptyList()); + mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo}); + mEndpointManager.onEndpointSessionOpenRequest( + INVALID_SESSION_ID_FOR_OPEN_REQUEST, + endpoint.getAssignedHubEndpointInfo().getIdentifier(), + targetInfo.getIdentifier(), + ENDPOINT_SERVICE_DESCRIPTOR); + verify(mMockEndpointCommunications) + .closeEndpointSession( + INVALID_SESSION_ID_FOR_OPEN_REQUEST, + Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED); + verify(mMockCallback, never()) + .onSessionOpenRequest( + INVALID_SESSION_ID_FOR_OPEN_REQUEST, + targetInfo, + ENDPOINT_SERVICE_DESCRIPTOR); + + unregisterExampleEndpoint(endpoint); + } + + @Test + public void testEndpointSessionOpenRequest_duplicatedSessionId_noopWhenSessionIsActive() + throws RemoteException { + assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE); + IContextHubEndpoint endpoint = registerExampleEndpoint(); + + HubEndpointInfo targetInfo = + new HubEndpointInfo( + TARGET_ENDPOINT_NAME, + TARGET_ENDPOINT_ID, + ENDPOINT_PACKAGE_NAME, + Collections.emptyList()); + mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo}); + mEndpointManager.onEndpointSessionOpenRequest( + SESSION_ID_FOR_OPEN_REQUEST, + endpoint.getAssignedHubEndpointInfo().getIdentifier(), + targetInfo.getIdentifier(), + ENDPOINT_SERVICE_DESCRIPTOR); + endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST); + // Now session with id SESSION_ID_FOR_OPEN_REQUEST is active + + // Duplicated session open request + mEndpointManager.onEndpointSessionOpenRequest( + SESSION_ID_FOR_OPEN_REQUEST, + endpoint.getAssignedHubEndpointInfo().getIdentifier(), + targetInfo.getIdentifier(), + ENDPOINT_SERVICE_DESCRIPTOR); + + // Client API is only invoked once + verify(mMockCallback, times(1)) + .onSessionOpenRequest( + SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR); + // HAL still receives two open complete notifications + verify(mMockEndpointCommunications, times(2)) + .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST); + + unregisterExampleEndpoint(endpoint); + } + + @Test public void testMessageTransaction() throws RemoteException { IContextHubEndpoint endpoint = registerExampleEndpoint(); testMessageTransactionInternal(endpoint, /* deliverMessageStatus= */ true); 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()); } diff --git a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java index d9bb7db17685..5419d9444abd 100644 --- a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java +++ b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java @@ -110,7 +110,7 @@ public class TextToSpeechTests extends InstrumentationTestCase { blockingCallSpeak("foo bar", delegate); ArgumentCaptor<SynthesisRequest> req = ArgumentCaptor.forClass(SynthesisRequest.class); Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req.capture(), - Mockito.<SynthesisCallback>anyObject()); + Mockito.<SynthesisCallback>any()); assertEquals("eng", req.getValue().getLanguage()); assertEquals("USA", req.getValue().getCountry()); @@ -133,7 +133,7 @@ public class TextToSpeechTests extends InstrumentationTestCase { blockingCallSpeak("le fou barre", delegate); ArgumentCaptor<SynthesisRequest> req2 = ArgumentCaptor.forClass(SynthesisRequest.class); Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req2.capture(), - Mockito.<SynthesisCallback>anyObject()); + Mockito.<SynthesisCallback>any()); // The params are basically unchanged. assertEquals("eng", req2.getValue().getLanguage()); @@ -177,7 +177,7 @@ public class TextToSpeechTests extends InstrumentationTestCase { blockingCallSpeak("foo bar", delegate); ArgumentCaptor<SynthesisRequest> req = ArgumentCaptor.forClass(SynthesisRequest.class); Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req.capture(), - Mockito.<SynthesisCallback>anyObject()); + Mockito.<SynthesisCallback>any()); assertEquals(defaultLocale.getISO3Language(), req.getValue().getLanguage()); assertEquals(defaultLocale.getISO3Country(), req.getValue().getCountry()); @@ -189,8 +189,8 @@ public class TextToSpeechTests extends InstrumentationTestCase { private void blockingCallSpeak(String speech, IDelegate mock) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); - doCountDown(latch).when(mock).onSynthesizeText(Mockito.<SynthesisRequest>anyObject(), - Mockito.<SynthesisCallback>anyObject()); + doCountDown(latch).when(mock).onSynthesizeText(Mockito.<SynthesisRequest>any(), + Mockito.<SynthesisCallback>any()); mTts.speak(speech, TextToSpeech.QUEUE_ADD, null); awaitCountDown(latch, 5, TimeUnit.SECONDS); |