diff options
333 files changed, 5527 insertions, 2208 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 4cd6d6f03ee8..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>); @@ -55423,6 +55425,7 @@ package android.view { method public void dispatchOnDraw(); method public void dispatchOnGlobalLayout(); method public boolean dispatchOnPreDraw(); + method @FlaggedApi("android.view.flags.enable_dispatch_on_scroll_changed") public void dispatchOnScrollChanged(); method public boolean isAlive(); method public void registerFrameCommitCallback(@NonNull Runnable); method @Deprecated public void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener); 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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 00fa1c1a4f80..bdecbaeb455d 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -302,13 +302,23 @@ public class ApplicationPackageManager extends PackageManager { @Override public Intent getLaunchIntentForPackage(String packageName) { + return getLaunchIntentForPackage(packageName, false); + } + + @Override + @Nullable + public Intent getLaunchIntentForPackage(@NonNull String packageName, + boolean includeDirectBootUnaware) { + ResolveInfoFlags queryFlags = ResolveInfoFlags.of( + includeDirectBootUnaware ? MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE : 0); + // First see if the package has an INFO activity; the existence of // such an activity is implied to be the desired front-door for the // overall package (such as if it has multiple launcher entries). Intent intentToResolve = new Intent(Intent.ACTION_MAIN); intentToResolve.addCategory(Intent.CATEGORY_INFO); intentToResolve.setPackage(packageName); - List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0); + List<ResolveInfo> ris = queryIntentActivities(intentToResolve, queryFlags); // Otherwise, try to find a main launcher activity. if (ris == null || ris.size() <= 0) { @@ -316,7 +326,7 @@ public class ApplicationPackageManager extends PackageManager { intentToResolve.removeCategory(Intent.CATEGORY_INFO); intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); intentToResolve.setPackage(packageName); - ris = queryIntentActivities(intentToResolve, 0); + ris = queryIntentActivities(intentToResolve, queryFlags); } if (ris == null || ris.size() <= 0) { return null; diff --git a/core/java/android/app/AutomaticZenRule.aidl b/core/java/android/app/AutomaticZenRule.aidl index feb21d657c6a..92f7d5235cf7 100644 --- a/core/java/android/app/AutomaticZenRule.aidl +++ b/core/java/android/app/AutomaticZenRule.aidl @@ -16,4 +16,6 @@ package android.app; -parcelable AutomaticZenRule;
\ No newline at end of file +parcelable AutomaticZenRule; + +parcelable AutomaticZenRule.AzrWithId;
\ No newline at end of file diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index fa977c93113a..1ce38ace75d8 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -228,7 +228,7 @@ public final class AutomaticZenRule implements Parcelable { public AutomaticZenRule(Parcel source) { enabled = source.readInt() == ENABLED; if (source.readInt() == ENABLED) { - name = getTrimmedString(source.readString()); + name = getTrimmedString(source.readString8()); } interruptionFilter = source.readInt(); conditionId = getTrimmedUri(source.readParcelable(null, android.net.Uri.class)); @@ -238,11 +238,11 @@ public final class AutomaticZenRule implements Parcelable { source.readParcelable(null, android.content.ComponentName.class)); creationTime = source.readLong(); mZenPolicy = source.readParcelable(null, ZenPolicy.class); - mPkg = source.readString(); + mPkg = source.readString8(); mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class); mAllowManualInvocation = source.readBoolean(); mIconResId = source.readInt(); - mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH); + mTriggerDescription = getTrimmedString(source.readString8(), MAX_DESC_LENGTH); mType = source.readInt(); } @@ -514,7 +514,7 @@ public final class AutomaticZenRule implements Parcelable { dest.writeInt(enabled ? ENABLED : DISABLED); if (name != null) { dest.writeInt(1); - dest.writeString(name); + dest.writeString8(name); } else { dest.writeInt(0); } @@ -524,11 +524,11 @@ public final class AutomaticZenRule implements Parcelable { dest.writeParcelable(configurationActivity, 0); dest.writeLong(creationTime); dest.writeParcelable(mZenPolicy, 0); - dest.writeString(mPkg); + dest.writeString8(mPkg); dest.writeParcelable(mDeviceEffects, 0); dest.writeBoolean(mAllowManualInvocation); dest.writeInt(mIconResId); - dest.writeString(mTriggerDescription); + dest.writeString8(mTriggerDescription); dest.writeInt(mType); } @@ -843,4 +843,41 @@ public final class AutomaticZenRule implements Parcelable { return rule; } } + + /** @hide */ + public static final class AzrWithId implements Parcelable { + public final String mId; + public final AutomaticZenRule mRule; + + public AzrWithId(String id, AutomaticZenRule rule) { + mId = id; + mRule = rule; + } + + public static final Creator<AzrWithId> CREATOR = new Creator<>() { + @Override + public AzrWithId createFromParcel(Parcel in) { + return new AzrWithId( + in.readString8(), + in.readParcelable(AutomaticZenRule.class.getClassLoader(), + AutomaticZenRule.class)); + } + + @Override + public AzrWithId[] newArray(int size) { + return new AzrWithId[size]; + } + }; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mId); + dest.writeParcelable(mRule, flags); + } + + @Override + public int describeContents() { + return 0; + } + } } diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index 6efc4ef55180..3003b79435e2 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -49,11 +49,14 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.compat.CompatChanges; import android.app.role.RoleManager; +import android.companion.virtual.VirtualDevice; +import android.companion.virtual.VirtualDeviceManager; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; import android.compat.annotation.EnabledAfter; import android.compat.annotation.Overridable; +import android.content.AttributionSource; import android.content.Context; import android.content.PermissionChecker; import android.content.pm.PackageManager; @@ -67,6 +70,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.permission.PermissionCheckerManager; +import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.text.TextUtils; import android.util.ArrayMap; @@ -1174,17 +1178,48 @@ public abstract class ForegroundServiceTypePolicy { @PackageManager.PermissionResult public int checkPermission(@NonNull Context context, int callerUid, int callerPid, String packageName, boolean allowWhileInUse) { - return checkPermission(context, mName, callerUid, callerPid, packageName, - allowWhileInUse); + int permissionResult = checkPermission(context, mName, callerUid, callerPid, + packageName, allowWhileInUse, Context.DEVICE_ID_DEFAULT); + + if (permissionResult == PERMISSION_GRANTED + || !PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(mName)) { + return permissionResult; + } + + // For device aware permissions, check if the permission is granted on any other + // active virtual device + VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class); + if (vdm == null) { + return permissionResult; + } + + final List<VirtualDevice> virtualDevices = vdm.getVirtualDevices(); + for (int i = 0, size = virtualDevices.size(); i < size; i++) { + final VirtualDevice virtualDevice = virtualDevices.get(i); + int resolvedDeviceId = PermissionManager.resolveDeviceIdForPermissionCheck( + context, virtualDevice.getDeviceId(), mName); + // we already checked on the default device context + if (resolvedDeviceId == Context.DEVICE_ID_DEFAULT) { + continue; + } + permissionResult = checkPermission(context, mName, callerUid, callerPid, + packageName, allowWhileInUse, resolvedDeviceId); + if (permissionResult == PERMISSION_GRANTED) { + break; + } + } + + return permissionResult; } @SuppressLint("AndroidFrameworkRequiresPermission") @PackageManager.PermissionResult int checkPermission(@NonNull Context context, @NonNull String name, int callerUid, - int callerPid, String packageName, boolean allowWhileInUse) { + int callerPid, String packageName, boolean allowWhileInUse, int deviceId) { + final AttributionSource attributionSource = new AttributionSource(callerUid, + packageName, null /*attributionTag*/, deviceId); @PermissionCheckerManager.PermissionResult final int result = - PermissionChecker.checkPermissionForPreflight(context, name, - callerPid, callerUid, packageName); + PermissionChecker.checkPermissionForPreflight(context, name, attributionSource); if (result == PERMISSION_HARD_DENIED) { // If the user didn't grant this permission at all. return PERMISSION_DENIED; @@ -1196,7 +1231,7 @@ public abstract class ForegroundServiceTypePolicy { ? PERMISSION_GRANTED : PERMISSION_DENIED; } final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - final int mode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid, packageName); + final int mode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, attributionSource); switch (mode) { case MODE_ALLOWED: // The appop is just allowed, plain and simple. diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 00df7246a300..1f0cd39a823c 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -224,7 +224,7 @@ interface INotificationManager void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted); ZenPolicy getDefaultZenPolicy(); AutomaticZenRule getAutomaticZenRule(String id); - Map<String, AutomaticZenRule> getAutomaticZenRules(); + ParceledListSlice getAutomaticZenRules(); String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg, boolean fromUser); boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule, boolean fromUser); boolean removeAutomaticZenRule(String id, boolean fromUser); 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/NotificationManager.java b/core/java/android/app/NotificationManager.java index 050ef23b89e3..69e3ef9086d5 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1747,7 +1747,15 @@ public class NotificationManager { public Map<String, AutomaticZenRule> getAutomaticZenRules() { INotificationManager service = service(); try { - return service.getAutomaticZenRules(); + Map<String, AutomaticZenRule> result = new HashMap<>(); + ParceledListSlice<AutomaticZenRule.AzrWithId> parceledRules = + service.getAutomaticZenRules(); + if (parceledRules != null) { + for (AutomaticZenRule.AzrWithId rule : parceledRules.getList()) { + result.put(rule.mId, rule.mRule); + } + } + return result; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } 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/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java index a13af7f1ddcd..d7d6262599da 100644 --- a/core/java/android/app/wallpaper/WallpaperDescription.java +++ b/core/java/android/app/wallpaper/WallpaperDescription.java @@ -168,6 +168,12 @@ public final class WallpaperDescription implements Parcelable { return mSampleSize; } + @Override + public String toString() { + String component = (mComponent != null) ? mComponent.toString() : "{null}"; + return component + ":" + mId; + } + ////// Comparison overrides @Override diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 49fd6344270e..53966b8af533 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5964,7 +5964,39 @@ public abstract class PackageManager { * * @see #getLaunchIntentSenderForPackage(String) */ - public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName); + public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName); + + /** + * Returns a "good" intent to launch a front-door activity in a package. + * This is used, for example, to implement an "open" button when browsing + * through packages. The current implementation looks first for a main + * activity in the category {@link Intent#CATEGORY_INFO}, and next for a + * main activity in the category {@link Intent#CATEGORY_LAUNCHER}. Returns + * <code>null</code> if neither are found. + * + * <p>Consider using {@link #getLaunchIntentSenderForPackage(String)} if + * the caller is not allowed to query for the <code>packageName</code>. + * + * @param packageName The name of the package to inspect. + * @param includeDirectBootUnaware When {@code true}, activities that are direct-boot-unaware + * will be considered even if the device hasn't been unlocked (i.e. querying will be done + * with {@code MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE}). + * + * @return A fully-qualified {@link Intent} that can be used to launch the + * main activity in the package. Returns <code>null</code> if the package + * does not contain such an activity, or if <em>packageName</em> is not + * recognized. + * + * @see #getLaunchIntentSenderForPackage(String) + * + * @hide + */ + public @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName, + boolean includeDirectBootUnaware) { + throw new UnsupportedOperationException( + "getLaunchIntentForPackage(packageName, includeDirectBootUnaware) not implemented" + + " in subclass"); + } /** * Return a "good" intent to launch a front-door Leanback activity in a 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/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index 4ed0fc056e7d..c3c8c3d63bec 100644 --- a/core/java/android/hardware/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -600,8 +600,7 @@ public final class DisplayTopology implements Parcelable { private void addDisplay(int displayId, float width, float height, boolean shouldLog) { if (findDisplay(displayId, mRoot) != null) { - throw new IllegalArgumentException( - "DisplayTopology: attempting to add a display that already exists"); + return; } if (mRoot == null) { mRoot = new TreeNode(displayId, width, height, POSITION_LEFT, /* offset= */ 0); 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/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 4cbd5beb3a8c..fce2df185461 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -62,6 +62,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.net.Uri; import android.os.Build; @@ -460,14 +461,21 @@ public class ZenModeConfig implements Parcelable { } private static void readRulesFromParcel(ArrayMap<String, ZenRule> ruleMap, Parcel source) { - final int len = source.readInt(); + int len = source.readInt(); if (len > 0) { final String[] ids = new String[len]; - final ZenRule[] rules = new ZenRule[len]; - source.readStringArray(ids); - source.readTypedArray(rules, ZenRule.CREATOR); + source.readString8Array(ids); + ParceledListSlice<?> parceledRules = source.readParcelable( + ZenRule.class.getClassLoader(), ParceledListSlice.class); + List<?> rules = parceledRules != null ? parceledRules.getList() : new ArrayList<>(); + if (rules.size() != len) { + Slog.wtf(TAG, String.format( + "Unexpected parceled rules count (%s != %s), throwing them out", + rules.size(), len)); + len = 0; + } for (int i = 0; i < len; i++) { - ruleMap.put(ids[i], rules[i]); + ruleMap.put(ids[i], (ZenRule) rules.get(i)); } } } @@ -485,8 +493,8 @@ public class ZenModeConfig implements Parcelable { } dest.writeInt(user); dest.writeParcelable(manualRule, 0); - writeRulesToParcel(automaticRules, dest); - writeRulesToParcel(deletedRules, dest); + writeRulesToParcel(automaticRules, dest, flags); + writeRulesToParcel(deletedRules, dest, flags); if (!Flags.modesUi()) { dest.writeInt(allowAlarms ? 1 : 0); dest.writeInt(allowMedia ? 1 : 0); @@ -501,18 +509,19 @@ public class ZenModeConfig implements Parcelable { } } - private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest) { + private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest, + int flags) { if (!ruleMap.isEmpty()) { final int len = ruleMap.size(); final String[] ids = new String[len]; - final ZenRule[] rules = new ZenRule[len]; + final ArrayList<ZenRule> rules = new ArrayList<>(); for (int i = 0; i < len; i++) { ids[i] = ruleMap.keyAt(i); - rules[i] = ruleMap.valueAt(i); + rules.add(ruleMap.valueAt(i)); } dest.writeInt(len); - dest.writeStringArray(ids); - dest.writeTypedArray(rules, 0); + dest.writeString8Array(ids); + dest.writeParcelable(new ParceledListSlice<>(rules), flags); } else { dest.writeInt(0); } @@ -2636,7 +2645,7 @@ public class ZenModeConfig implements Parcelable { enabled = source.readInt() == 1; snoozing = source.readInt() == 1; if (source.readInt() == 1) { - name = source.readString(); + name = source.readString8(); } zenMode = source.readInt(); conditionId = source.readParcelable(null, android.net.Uri.class); @@ -2644,18 +2653,18 @@ public class ZenModeConfig implements Parcelable { component = source.readParcelable(null, android.content.ComponentName.class); configurationActivity = source.readParcelable(null, android.content.ComponentName.class); if (source.readInt() == 1) { - id = source.readString(); + id = source.readString8(); } creationTime = source.readLong(); if (source.readInt() == 1) { - enabler = source.readString(); + enabler = source.readString8(); } zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); zenDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class); - pkg = source.readString(); + pkg = source.readString8(); allowManualInvocation = source.readBoolean(); - iconResName = source.readString(); - triggerDescription = source.readString(); + iconResName = source.readString8(); + triggerDescription = source.readString8(); type = source.readInt(); userModifiedFields = source.readInt(); zenPolicyUserModifiedFields = source.readInt(); @@ -2703,7 +2712,7 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(snoozing ? 1 : 0); if (name != null) { dest.writeInt(1); - dest.writeString(name); + dest.writeString8(name); } else { dest.writeInt(0); } @@ -2714,23 +2723,23 @@ public class ZenModeConfig implements Parcelable { dest.writeParcelable(configurationActivity, 0); if (id != null) { dest.writeInt(1); - dest.writeString(id); + dest.writeString8(id); } else { dest.writeInt(0); } dest.writeLong(creationTime); if (enabler != null) { dest.writeInt(1); - dest.writeString(enabler); + dest.writeString8(enabler); } else { dest.writeInt(0); } dest.writeParcelable(zenPolicy, 0); dest.writeParcelable(zenDeviceEffects, 0); - dest.writeString(pkg); + dest.writeString8(pkg); dest.writeBoolean(allowManualInvocation); - dest.writeString(iconResName); - dest.writeString(triggerDescription); + dest.writeString8(iconResName); + dest.writeString8(triggerDescription); dest.writeInt(type); dest.writeInt(userModifiedFields); dest.writeInt(zenPolicyUserModifiedFields); diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 3b444c44c368..fc66e49ff6b0 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -16,6 +16,9 @@ package android.view; +import static android.view.flags.Flags.FLAG_ENABLE_DISPATCH_ON_SCROLL_CHANGED; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -1258,8 +1261,9 @@ public final class ViewTreeObserver { /** * Notifies registered listeners that something has scrolled. */ + @FlaggedApi(FLAG_ENABLE_DISPATCH_ON_SCROLL_CHANGED) @UnsupportedAppUsage - final void dispatchOnScrollChanged() { + public final void dispatchOnScrollChanged() { // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents diff --git a/core/java/android/view/flags/view_tree_observer_flags.aconfig b/core/java/android/view/flags/view_tree_observer_flags.aconfig new file mode 100644 index 000000000000..82f3300a87cb --- /dev/null +++ b/core/java/android/view/flags/view_tree_observer_flags.aconfig @@ -0,0 +1,9 @@ +package: "android.view.flags" +container: "system" + +flag { + name: "enable_dispatch_on_scroll_changed" + namespace: "toolkit" + description: "Feature flag for enabling the dispatchOnScrollChanged method in ViewTreeObserver." + bug: "238109286" +}
\ No newline at end of file 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/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 816270235446..8dd0457248a4 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -287,6 +287,17 @@ flag { } flag { + name: "use_visible_requested_for_process_tracker" + namespace: "windowing_frontend" + description: "Do not count closing activity as visible process" + bug: "396653764" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "ensure_wallpaper_in_transitions" namespace: "windowing_frontend" description: "Ensure that wallpaper window tokens are always present/available for collection in transitions" diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index 41e2ca9cdfad..e125e258c596 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -313,8 +313,17 @@ public class Cuj { */ public static final int CUJ_DEFAULT_TASK_TO_TASK_ANIMATION = 128; + /** + * Track moving a window to another display in Desktop Windowing mode. + * + * <p>Tracking starts when the DesktopModeMoveToDisplayTransitionHandler starts animating the + * task to move it to another display. This is triggered when the user presses a keyboard + * shortcut or clicks the menu in the overview. Tracking ends when the animation completes.</p> + */ + public static final int CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY = 129; + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. - @VisibleForTesting static final int LAST_CUJ = CUJ_DEFAULT_TASK_TO_TASK_ANIMATION; + @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY; /** @hide */ @IntDef({ @@ -434,7 +443,8 @@ public class Cuj { CUJ_DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH, CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND, CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK, - CUJ_DEFAULT_TASK_TO_TASK_ANIMATION + CUJ_DEFAULT_TASK_TO_TASK_ANIMATION, + CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} @@ -565,6 +575,7 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WORK_UTILITY_VIEW_EXPAND; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WORK_UTILITY_VIEW_SHRINK; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DEFAULT_TASK_TO_TASK_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DEFAULT_TASK_TO_TASK_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY; } private Cuj() { @@ -817,6 +828,8 @@ public class Cuj { return "LAUNCHER_WORK_UTILITY_VIEW_SHRINK"; case CUJ_DEFAULT_TASK_TO_TASK_ANIMATION: return "DEFAULT_TASK_TO_TASK_ANIMATION"; + case CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY: + return "DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY"; } return "UNKNOWN"; } 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/layout/notification_2025_messaging_group.xml b/core/res/res/layout/notification_2025_messaging_group.xml index ecaf0b9a785f..ba0ce7b21b80 100644 --- a/core/res/res/layout/notification_2025_messaging_group.xml +++ b/core/res/res/layout/notification_2025_messaging_group.xml @@ -53,7 +53,6 @@ android:id="@+id/group_message_container" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/notification_text_margin_top" android:spacing="2dp" /> </com.android.internal.widget.RemeasuringLinearLayout> <FrameLayout diff --git a/core/res/res/layout/notification_2025_template_expanded_base.xml b/core/res/res/layout/notification_2025_template_expanded_base.xml index 287110766dc7..58b26c905162 100644 --- a/core/res/res/layout/notification_2025_template_expanded_base.xml +++ b/core/res/res/layout/notification_2025_template_expanded_base.xml @@ -49,7 +49,7 @@ android:orientation="vertical" > - <include layout="@layout/notification_template_part_line1" /> + <include layout="@layout/notification_2025_title" /> <include layout="@layout/notification_template_text_multiline" /> diff --git a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml index ead6d4cbc034..f6a17ef6d46f 100644 --- a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml +++ b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml @@ -46,7 +46,7 @@ android:orientation="vertical" > - <include layout="@layout/notification_template_part_line1" /> + <include layout="@layout/notification_2025_title" /> <include layout="@layout/notification_template_progress" diff --git a/core/res/res/layout/notification_2025_template_expanded_big_text.xml b/core/res/res/layout/notification_2025_template_expanded_big_text.xml index de5e71d2015f..540444ea4da4 100644 --- a/core/res/res/layout/notification_2025_template_expanded_big_text.xml +++ b/core/res/res/layout/notification_2025_template_expanded_big_text.xml @@ -47,7 +47,7 @@ android:layout_weight="1" > - <include layout="@layout/notification_template_part_line1" /> + <include layout="@layout/notification_2025_title" /> <include layout="@layout/notification_template_progress" diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml index c096bc8a4d15..60b32002d484 100644 --- a/core/res/res/layout/notification_2025_template_expanded_call.xml +++ b/core/res/res/layout/notification_2025_template_expanded_call.xml @@ -50,7 +50,7 @@ android:clipChildren="false" > - <include layout="@layout/notification_template_part_line1"/> + <include layout="@layout/notification_2025_title"/> <include layout="@layout/notification_template_text_multiline" /> diff --git a/core/res/res/layout/notification_2025_template_expanded_conversation.xml b/core/res/res/layout/notification_2025_template_expanded_conversation.xml index 6eea8cc93aeb..e085d47d4579 100644 --- a/core/res/res/layout/notification_2025_template_expanded_conversation.xml +++ b/core/res/res/layout/notification_2025_template_expanded_conversation.xml @@ -48,14 +48,15 @@ android:clipChildren="false" > - <include layout="@layout/notification_template_part_line1"/> + <include layout="@layout/notification_2025_title"/> <com.android.internal.widget.MessagingLinearLayout android:id="@+id/notification_messaging" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_2025_margin" android:clipChildren="false" - android:spacing="@dimen/notification_messaging_spacing" /> + android:spacing="@dimen/notification_2025_messaging_spacing" /> </com.android.internal.widget.RemeasuringLinearLayout> <include layout="@layout/notification_template_smart_reply_container" diff --git a/core/res/res/layout/notification_2025_template_expanded_inbox.xml b/core/res/res/layout/notification_2025_template_expanded_inbox.xml index 327cd7ac71bb..55253d0ce5f8 100644 --- a/core/res/res/layout/notification_2025_template_expanded_inbox.xml +++ b/core/res/res/layout/notification_2025_template_expanded_inbox.xml @@ -43,7 +43,7 @@ android:clipToPadding="false" android:orientation="vertical" > - <include layout="@layout/notification_template_part_line1" + <include layout="@layout/notification_2025_title" android:layout_width="match_parent" android:layout_height="wrap_content" /> <include layout="@layout/notification_template_progress" diff --git a/core/res/res/layout/notification_2025_template_expanded_media.xml b/core/res/res/layout/notification_2025_template_expanded_media.xml index 565a558a5115..5e97607dc65b 100644 --- a/core/res/res/layout/notification_2025_template_expanded_media.xml +++ b/core/res/res/layout/notification_2025_template_expanded_media.xml @@ -44,7 +44,7 @@ android:layout_marginEnd="@dimen/notification_2025_margin" android:orientation="vertical" > - <include layout="@layout/notification_template_part_line1"/> + <include layout="@layout/notification_2025_title"/> <include layout="@layout/notification_template_text"/> </LinearLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml index df48479e5ec3..14ed536a9a5c 100644 --- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml +++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml @@ -48,14 +48,15 @@ android:clipChildren="false" > - <include layout="@layout/notification_template_part_line1"/> + <include layout="@layout/notification_2025_title"/> <com.android.internal.widget.MessagingLinearLayout android:id="@+id/notification_messaging" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_2025_margin" android:clipChildren="false" - android:spacing="@dimen/notification_messaging_spacing" /> + android:spacing="@dimen/notification_2025_messaging_spacing" /> </com.android.internal.widget.RemeasuringLinearLayout> <include layout="@layout/notification_template_smart_reply_container" diff --git a/core/res/res/layout/notification_2025_template_expanded_progress.xml b/core/res/res/layout/notification_2025_template_expanded_progress.xml index b929b9ed20b7..1315481d86c5 100644 --- a/core/res/res/layout/notification_2025_template_expanded_progress.xml +++ b/core/res/res/layout/notification_2025_template_expanded_progress.xml @@ -50,7 +50,7 @@ android:orientation="vertical" > - <include layout="@layout/notification_template_part_line1" /> + <include layout="@layout/notification_2025_title" /> <include layout="@layout/notification_template_text_multiline" /> diff --git a/core/res/res/layout/notification_2025_title.xml b/core/res/res/layout/notification_2025_title.xml new file mode 100644 index 000000000000..7cea5ea08449 --- /dev/null +++ b/core/res/res/layout/notification_2025_title.xml @@ -0,0 +1,26 @@ +<?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 + --> +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/title" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:maxLines="2" + android:ellipsize="end" + android:textAlignment="viewStart" + /> diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml index 60aec5342b4f..4738d20cdede 100644 --- a/core/res/res/values-watch/themes_device_defaults.xml +++ b/core/res/res/values-watch/themes_device_defaults.xml @@ -48,6 +48,9 @@ a similar way. <item name="disabledAlpha">@dimen/disabled_alpha_device_default</item> <item name="primaryContentAlpha">@dimen/primary_content_alpha_device_default</item> <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item> + + <!-- Material3 default delay before scroll bar fading animation. --> + <item name="scrollbarDefaultDelayBeforeFade">1000</item> </style> <!-- Variant of {@link #Theme_DeviceDefault} with no action bar --> 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/dimens.xml b/core/res/res/values/dimens.xml index 534a3a01285a..c0d2779f3a31 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -338,9 +338,9 @@ <!-- The spacing between the content and the header text above it, scaling with text size. This value is chosen so that, taking into account the text spacing for both the text in the - top line and the text in the content, the distance between them is 4dp with the default + top line and the text in the content, the distance between them is ~2dp with the default screen configuration (and will grow accordingly for larger font sizes) --> - <dimen name="notification_2025_content_margin_top">10sp</dimen> + <dimen name="notification_2025_content_margin_top">8sp</dimen> <!-- height of the content margin that is applied at the end of the notification content --> <dimen name="notification_content_margin">20dp</dimen> @@ -523,6 +523,9 @@ <!-- The spacing between messages in Notification.MessagingStyle --> <dimen name="notification_messaging_spacing">6dp</dimen> + <!-- The spacing between messages in Notification.MessagingStyle (2025 redesign version) --> + <dimen name="notification_2025_messaging_spacing">14dp</dimen> + <!-- The spacing between messages in Notification.MessagingStyle --> <dimen name="notification_messaging_spacing_conversation_group">24dp</dimen> 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/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java index 62d89f6dc846..146b386e5a4b 100644 --- a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java +++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java @@ -16,19 +16,37 @@ package android.app; +import static android.content.Intent.ACTION_MAIN; +import static android.content.Intent.CATEGORY_INFO; +import static android.content.Intent.CATEGORY_LAUNCHER; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.os.storage.VolumeInfo.STATE_MOUNTED; import static android.os.storage.VolumeInfo.STATE_UNMOUNTED; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager.ResolveInfoFlags; +import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -44,6 +62,7 @@ import com.android.internal.annotations.VisibleForTesting; import junit.framework.TestCase; +import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import org.xmlpull.v1.XmlPullParser; @@ -102,14 +121,14 @@ public class ApplicationPackageManagerTest extends TestCase { sVolumes.add(sPrivateUnmountedVol); } - private static final class MockedApplicationPackageManager extends ApplicationPackageManager { + public static class MockedApplicationPackageManager extends ApplicationPackageManager { private boolean mForceAllowOnExternal = false; private boolean mAllow3rdPartyOnInternal = true; private HashMap<ApplicationInfo, Resources> mResourcesMap; public MockedApplicationPackageManager() { super(null, null); - mResourcesMap = new HashMap<ApplicationInfo, Resources>(); + mResourcesMap = new HashMap<>(); } public void setForceAllowOnExternal(boolean forceAllowOnExternal) { @@ -153,7 +172,7 @@ public class ApplicationPackageManagerTest extends TestCase { } private StorageManager getMockedStorageManager() { - StorageManager storageManager = Mockito.mock(StorageManager.class); + StorageManager storageManager = mock(StorageManager.class); Mockito.when(storageManager.getVolumes()).thenReturn(sVolumes); Mockito.when(storageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL)) .thenReturn(sInternalVol); @@ -190,7 +209,7 @@ public class ApplicationPackageManagerTest extends TestCase { sysAppInfo.flags = ApplicationInfo.FLAG_SYSTEM; StorageManager storageManager = getMockedStorageManager(); - IPackageManager pm = Mockito.mock(IPackageManager.class); + IPackageManager pm = mock(IPackageManager.class); MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); @@ -220,7 +239,7 @@ public class ApplicationPackageManagerTest extends TestCase { ApplicationInfo appInfo = new ApplicationInfo(); StorageManager storageManager = getMockedStorageManager(); - IPackageManager pm = Mockito.mock(IPackageManager.class); + IPackageManager pm = mock(IPackageManager.class); Mockito.when(pm.isPackageDeviceAdminOnAnyUser(Mockito.anyString())).thenReturn(false); MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); @@ -249,7 +268,7 @@ public class ApplicationPackageManagerTest extends TestCase { ApplicationInfo appInfo = new ApplicationInfo(); StorageManager storageManager = getMockedStorageManager(); - IPackageManager pm = Mockito.mock(IPackageManager.class); + IPackageManager pm = mock(IPackageManager.class); MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); appPkgMgr.setForceAllowOnExternal(true); @@ -291,15 +310,15 @@ public class ApplicationPackageManagerTest extends TestCase { public void testExtractPackageItemInfoAttributes_noMetaData() { final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); - final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class); + final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class); assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null, new int[]{})).isNull(); } public void testExtractPackageItemInfoAttributes_noParser() { final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); - final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class); - final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); + final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class); + final ApplicationInfo applicationInfo = mock(ApplicationInfo.class); when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo); assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null, new int[]{})).isNull(); @@ -307,8 +326,8 @@ public class ApplicationPackageManagerTest extends TestCase { public void testExtractPackageItemInfoAttributes_noMetaDataXml() { final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); - final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class); - final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); + final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class); + final ApplicationInfo applicationInfo = mock(ApplicationInfo.class); when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo); when(packageItemInfo.loadXmlMetaData(any(), any())).thenReturn(null); assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null, @@ -318,9 +337,9 @@ public class ApplicationPackageManagerTest extends TestCase { public void testExtractPackageItemInfoAttributes_nonMatchingRootTag() throws Exception { final String rootTag = "rootTag"; final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); - final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class); - final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); - final XmlResourceParser parser = Mockito.mock(XmlResourceParser.class); + final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class); + final ApplicationInfo applicationInfo = mock(ApplicationInfo.class); + final XmlResourceParser parser = mock(XmlResourceParser.class); when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo); packageItemInfo.metaData = new Bundle(); @@ -334,11 +353,11 @@ public class ApplicationPackageManagerTest extends TestCase { public void testExtractPackageItemInfoAttributes_successfulExtraction() throws Exception { final String rootTag = "rootTag"; final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager(); - final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class); - final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); - final XmlResourceParser parser = Mockito.mock(XmlResourceParser.class); - final Resources resources = Mockito.mock(Resources.class); - final TypedArray attributes = Mockito.mock(TypedArray.class); + final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class); + final ApplicationInfo applicationInfo = mock(ApplicationInfo.class); + final XmlResourceParser parser = mock(XmlResourceParser.class); + final Resources resources = mock(Resources.class); + final TypedArray attributes = mock(TypedArray.class); when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo); packageItemInfo.metaData = new Bundle(); @@ -351,4 +370,123 @@ public class ApplicationPackageManagerTest extends TestCase { assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, rootTag, new int[]{})).isEqualTo(attributes); } + + public void testGetLaunchIntentForPackage_categoryInfoActivity_returnsIt() throws Exception { + String pkg = "com.some.package"; + int userId = 42; + ResolveInfo categoryInfoResolveInfo = new ResolveInfo(); + categoryInfoResolveInfo.activityInfo = new ActivityInfo(); + categoryInfoResolveInfo.activityInfo.packageName = pkg; + categoryInfoResolveInfo.activityInfo.name = "activity"; + Intent baseIntent = new Intent(ACTION_MAIN).setPackage(pkg); + + final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager()); + doReturn(userId).when(pm).getUserId(); + doReturn(List.of(categoryInfoResolveInfo)) + .when(pm).queryIntentActivitiesAsUser( + eqIntent(new Intent(baseIntent).addCategory(CATEGORY_INFO)), + any(ResolveInfoFlags.class), + anyInt()); + doReturn( + List.of()) + .when(pm).queryIntentActivitiesAsUser( + eqIntent(new Intent(baseIntent).addCategory(CATEGORY_LAUNCHER)), + any(ResolveInfoFlags.class), + anyInt()); + + Intent intent = pm.getLaunchIntentForPackage(pkg, true); + + assertThat(intent).isNotNull(); + assertThat(intent.getComponent()).isEqualTo(new ComponentName(pkg, "activity")); + assertThat(intent.getCategories()).containsExactly(CATEGORY_INFO); + assertThat(intent.getFlags()).isEqualTo(FLAG_ACTIVITY_NEW_TASK); + verify(pm).queryIntentActivitiesAsUser( + eqIntent(new Intent(ACTION_MAIN).addCategory(CATEGORY_INFO).setPackage(pkg)), + eqResolveInfoFlags(MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE), + eq(userId)); + } + + public void testGetLaunchIntentForPackage_categoryLauncherActivity_returnsIt() { + String pkg = "com.some.package"; + int userId = 42; + ResolveInfo categoryLauncherResolveInfo1 = new ResolveInfo(); + categoryLauncherResolveInfo1.activityInfo = new ActivityInfo(); + categoryLauncherResolveInfo1.activityInfo.packageName = pkg; + categoryLauncherResolveInfo1.activityInfo.name = "activity1"; + ResolveInfo categoryLauncherResolveInfo2 = new ResolveInfo(); + categoryLauncherResolveInfo2.activityInfo = new ActivityInfo(); + categoryLauncherResolveInfo2.activityInfo.packageName = pkg; + categoryLauncherResolveInfo2.activityInfo.name = "activity2"; + Intent baseIntent = new Intent(ACTION_MAIN).setPackage(pkg); + + final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager()); + doReturn(userId).when(pm).getUserId(); + doReturn(List.of()) + .when(pm).queryIntentActivitiesAsUser( + eqIntent(new Intent(baseIntent).addCategory(CATEGORY_INFO)), + any(ResolveInfoFlags.class), + anyInt()); + doReturn( + List.of(categoryLauncherResolveInfo1, categoryLauncherResolveInfo2)) + .when(pm).queryIntentActivitiesAsUser( + eqIntent(new Intent(baseIntent).addCategory(CATEGORY_LAUNCHER)), + any(ResolveInfoFlags.class), + anyInt()); + + Intent intent = pm.getLaunchIntentForPackage(pkg, true); + + assertThat(intent).isNotNull(); + assertThat(intent.getComponent()).isEqualTo(new ComponentName(pkg, "activity1")); + assertThat(intent.getCategories()).containsExactly(CATEGORY_LAUNCHER); + assertThat(intent.getFlags()).isEqualTo(FLAG_ACTIVITY_NEW_TASK); + } + + public void testGetLaunchIntentForPackage_noSuitableActivity_returnsNull() throws Exception { + String pkg = "com.some.package"; + int userId = 42; + + final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager()); + doReturn(userId).when(pm).getUserId(); + doReturn(List.of()) + .when(pm).queryIntentActivitiesAsUser( + any(), + any(ResolveInfoFlags.class), + anyInt()); + + Intent intent = pm.getLaunchIntentForPackage(pkg, true); + + assertThat(intent).isNull(); + } + + /** Equality check for intents -- ignoring extras */ + private static Intent eqIntent(Intent wanted) { + return argThat( + new ArgumentMatcher<>() { + @Override + public boolean matches(Intent argument) { + return wanted.filterEquals(argument) + && wanted.getFlags() == argument.getFlags(); + } + + @Override + public String toString() { + return wanted.toString(); + } + }); + } + + private static ResolveInfoFlags eqResolveInfoFlags(long flagsWanted) { + return argThat( + new ArgumentMatcher<>() { + @Override + public boolean matches(ResolveInfoFlags argument) { + return argument.getValue() == flagsWanted; + } + + @Override + public String toString() { + return String.valueOf(flagsWanted); + } + }); + } } 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/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 67a4d6cf89bf..2cf671b0f446 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -969,8 +969,15 @@ public abstract class WMShellModule { @WMSingleton @Provides - static DesktopModeMoveToDisplayTransitionHandler provideMoveToDisplayTransitionHandler() { - return new DesktopModeMoveToDisplayTransitionHandler(new SurfaceControl.Transaction()); + static DesktopModeMoveToDisplayTransitionHandler provideMoveToDisplayTransitionHandler( + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler shellMainHandler, + DisplayController displayController) { + return new DesktopModeMoveToDisplayTransitionHandler( + new SurfaceControl.Transaction(), + interactionJankMonitor, + shellMainHandler, + displayController); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt index 489e4f0aed01..ea2fdc0ee8ed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt @@ -28,6 +28,7 @@ import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS import android.view.Display.DEFAULT_DISPLAY import android.view.IWindowManager +import android.view.InputDevice import android.view.WindowManager.TRANSIT_CHANGE import android.window.DesktopExperienceFlags import android.window.WindowContainerTransaction @@ -62,12 +63,28 @@ class DesktopDisplayModeController( } } + private val inputDeviceListener = + object : InputManager.InputDeviceListener { + override fun onInputDeviceAdded(deviceId: Int) { + refreshDisplayWindowingMode() + } + + override fun onInputDeviceChanged(deviceId: Int) { + refreshDisplayWindowingMode() + } + + override fun onInputDeviceRemoved(deviceId: Int) { + refreshDisplayWindowingMode() + } + } + init { if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { inputManager.registerOnTabletModeChangedListener( onTabletModeChangedListener, mainHandler, ) + inputManager.registerInputDeviceListener(inputDeviceListener, mainHandler) } } @@ -122,7 +139,7 @@ class DesktopDisplayModeController( return true } if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { - if (isInClamshellMode()) { + if (isInClamshellMode() || hasAnyMouseDevice()) { return true } } @@ -169,6 +186,11 @@ class DesktopDisplayModeController( private fun hasExternalDisplay() = rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY } + private fun hasAnyMouseDevice() = + inputManager.inputDeviceIds.any { + inputManager.getInputDevice(it)?.supportsSource(InputDevice.SOURCE_MOUSE) == true + } + private fun isInClamshellMode() = inputManager.isInTabletMode() == InputManager.SWITCH_STATE_OFF private fun isDefaultDisplayDesktopEligible(): Boolean { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt index 91bd3c9b6c22..844a1f896fd2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt @@ -19,19 +19,26 @@ package com.android.wm.shell.desktopmode import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator +import android.os.Handler import android.os.IBinder import android.view.Choreographer import android.view.SurfaceControl import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY +import com.android.internal.jank.InteractionJankMonitor +import com.android.wm.shell.common.DisplayController import com.android.wm.shell.shared.animation.Interpolators import com.android.wm.shell.transition.Transitions import kotlin.time.Duration.Companion.milliseconds /** Transition handler for moving a window to a different display. */ class DesktopModeMoveToDisplayTransitionHandler( - private val animationTransaction: SurfaceControl.Transaction + private val animationTransaction: SurfaceControl.Transaction, + private val interactionJankMonitor: InteractionJankMonitor, + private val shellMainHandler: Handler, + private val displayController: DisplayController, ) : Transitions.TransitionHandler { override fun handleRequest( @@ -74,18 +81,31 @@ class DesktopModeMoveToDisplayTransitionHandler( } } ) + animator.addListener( object : Animator.AnimatorListener { - override fun onAnimationStart(animation: Animator) = Unit + override fun onAnimationStart(animation: Animator) { + val displayContext = + displayController.getDisplayContext(changes[0].endDisplayId) + if (displayContext == null) return + interactionJankMonitor.begin( + changes[0].leash, + displayContext, + shellMainHandler, + CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY, + ) + } override fun onAnimationEnd(animation: Animator) { finishTransaction.apply() finishCallback.onTransitionFinished(null) + interactionJankMonitor.end(CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY) } override fun onAnimationCancel(animation: Animator) { finishTransaction.apply() finishCallback.onTransitionFinished(null) + interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY) } override fun onAnimationRepeat(animation: Animator) = Unit diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index 6cb26b54e802..e77acfb805a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -68,6 +68,8 @@ class DesktopRepository( * @property topTransparentFullscreenTaskId the task id of any current top transparent * fullscreen task launched on top of the desk. Cleared when the transparent task is closed or * sent to back. (top is at index 0). + * @property leftTiledTaskId task id of the task tiled on the left. + * @property rightTiledTaskId task id of the task tiled on the right. */ private data class Desk( val deskId: Int, @@ -80,6 +82,8 @@ class DesktopRepository( val freeformTasksInZOrder: ArrayList<Int> = ArrayList(), var fullImmersiveTaskId: Int? = null, var topTransparentFullscreenTaskId: Int? = null, + var leftTiledTaskId: Int? = null, + var rightTiledTaskId: Int? = null, ) { fun deepCopy(): Desk = Desk( @@ -92,6 +96,8 @@ class DesktopRepository( freeformTasksInZOrder = ArrayList(freeformTasksInZOrder), fullImmersiveTaskId = fullImmersiveTaskId, topTransparentFullscreenTaskId = topTransparentFullscreenTaskId, + leftTiledTaskId = leftTiledTaskId, + rightTiledTaskId = rightTiledTaskId, ) // TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't @@ -104,6 +110,8 @@ class DesktopRepository( freeformTasksInZOrder.clear() fullImmersiveTaskId = null topTransparentFullscreenTaskId = null + leftTiledTaskId = null + rightTiledTaskId = null } } @@ -268,6 +276,106 @@ class DesktopRepository( } } + /** Register a left tiled task to desktop state. */ + fun addLeftTiledTask(displayId: Int, taskId: Int) { + logD("addLeftTiledTask for displayId=%d, taskId=%d", displayId, taskId) + val activeDesk = + checkNotNull(desktopData.getDefaultDesk(displayId)) { + "Expected desk in display: $displayId" + } + addLeftTiledTaskToDesk(displayId, taskId, activeDesk.deskId) + } + + private fun addLeftTiledTaskToDesk(displayId: Int, taskId: Int, deskId: Int) { + logD("addLeftTiledTaskToDesk for displayId=%d, taskId=%d", displayId, taskId) + val desk = checkNotNull(desktopData.getDesk(deskId)) { "Did not find desk: $deskId" } + desk.leftTiledTaskId = taskId + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { + updatePersistentRepository(displayId) + } + } + + /** Register a right tiled task to desktop state. */ + fun addRightTiledTask(displayId: Int, taskId: Int) { + logD("addRightTiledTask for displayId=%d, taskId=%d", displayId, taskId) + val activeDesk = + checkNotNull(desktopData.getDefaultDesk(displayId)) { + "Expected desk in display: $displayId" + } + addRightTiledTaskToDesk(displayId, taskId, activeDesk.deskId) + } + + private fun addRightTiledTaskToDesk(displayId: Int, taskId: Int, deskId: Int) { + logD("addRightTiledTaskToDesk for displayId=%d, taskId=%d", displayId, taskId) + val desk = checkNotNull(desktopData.getDesk(deskId)) { "Did not find desk: $deskId" } + desk.rightTiledTaskId = taskId + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { + updatePersistentRepository(displayId) + } + } + + /** Gets a registered left tiled task to desktop state or returns null. */ + fun getLeftTiledTask(displayId: Int): Int? { + logD("getLeftTiledTask for displayId=%d", displayId) + val activeDesk = + checkNotNull(desktopData.getDefaultDesk(displayId)) { + "Expected desk in display: $displayId" + } + val deskId = activeDesk.deskId + val desk = checkNotNull(desktopData.getDesk(deskId)) { "Did not find desk: $deskId" } + return desk.leftTiledTaskId + } + + /** gets a registered right tiled task to desktop state or returns null. */ + fun getRightTiledTask(displayId: Int): Int? { + logD("getRightTiledTask for displayId=%d", displayId) + val activeDesk = + checkNotNull(desktopData.getDefaultDesk(displayId)) { + "Expected desk in display: $displayId" + } + val deskId = activeDesk.deskId + val desk = checkNotNull(desktopData.getDesk(deskId)) { "Did not find desk: $deskId" } + return desk.rightTiledTaskId + } + + /* Unregisters a left tiled task from desktop state. */ + fun removeLeftTiledTask(displayId: Int) { + logD("removeLeftTiledTask for displayId=%d", displayId) + val activeDesk = + checkNotNull(desktopData.getDefaultDesk(displayId)) { + "Expected desk in display: $displayId" + } + removeLeftTiledTaskFromDesk(displayId, activeDesk.deskId) + } + + private fun removeLeftTiledTaskFromDesk(displayId: Int, deskId: Int) { + logD("removeLeftTiledTaskToDesk for displayId=%d", displayId) + val desk = checkNotNull(desktopData.getDesk(deskId)) { "Did not find desk: $deskId" } + desk.leftTiledTaskId = null + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { + updatePersistentRepository(displayId) + } + } + + /* Unregisters a right tiled task from desktop state. */ + fun removeRightTiledTask(displayId: Int) { + logD("removeRightTiledTask for displayId=%d", displayId) + val activeDesk = + checkNotNull(desktopData.getDefaultDesk(displayId)) { + "Expected desk in display: $displayId" + } + removeRightTiledTaskFromDesk(displayId, activeDesk.deskId) + } + + private fun removeRightTiledTaskFromDesk(displayId: Int, deskId: Int) { + logD("removeRightTiledTaskFromDesk for displayId=%d", displayId) + val desk = checkNotNull(desktopData.getDesk(deskId)) { "Did not find desk: $deskId" } + desk.rightTiledTaskId = null + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { + updatePersistentRepository(displayId) + } + } + /** Returns the id of the active desk in the given display, if any. */ fun getActiveDeskId(displayId: Int): Int? = desktopData.getActiveDesk(displayId)?.deskId @@ -972,6 +1080,8 @@ class DesktopRepository( visibleTasks = desk.visibleTasks, minimizedTasks = desk.minimizedTasks, freeformTasksInZOrder = desk.freeformTasksInZOrder, + leftTiledTask = desk.leftTiledTaskId, + rightTiledTask = desk.rightTiledTaskId, ) } catch (exception: Exception) { logE( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 487c7d15fa38..0920f274f51d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -306,6 +306,8 @@ class DesktopTasksController( this, ) shellController.addUserChangeListener(this) + // Update the current user id again because it might be updated between init and onInit(). + updateCurrentUser(ActivityManager.getCurrentUser()) transitions.addHandler(this) dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener recentsTransitionHandler.addTransitionStateListener( @@ -3547,9 +3549,15 @@ class DesktopTasksController( // TODO(b/366397912): Support full multi-user mode in Windowing. override fun onUserChanged(newUserId: Int, userContext: Context) { logV("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId) + updateCurrentUser(newUserId) + } + + private fun updateCurrentUser(newUserId: Int) { userId = newUserId taskRepository = userRepositories.getProfile(userId) - snapEventHandler.onUserChange() + if (this::snapEventHandler.isInitialized) { + snapEventHandler.onUserChange() + } } /** Called when a task's info changes. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt index f71eacab518d..e04a5cdb8a38 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt @@ -113,6 +113,8 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis visibleTasks: ArraySet<Int> = ArraySet(), minimizedTasks: ArraySet<Int> = ArraySet(), freeformTasksInZOrder: ArrayList<Int> = ArrayList(), + leftTiledTask: Int? = null, + rightTiledTask: Int? = null, ) { // TODO: b/367609270 - Improve the API to support multi-user try { @@ -125,7 +127,13 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis val desktop = getDesktop(currentRepository, desktopId) .toBuilder() - .updateTaskStates(visibleTasks, minimizedTasks, freeformTasksInZOrder) + .updateTaskStates( + visibleTasks, + minimizedTasks, + freeformTasksInZOrder, + leftTiledTask, + rightTiledTask, + ) .updateZOrder(freeformTasksInZOrder) persistentRepositories @@ -222,6 +230,8 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis visibleTasks: ArraySet<Int>, minimizedTasks: ArraySet<Int>, freeformTasksInZOrder: ArrayList<Int>, + leftTiledTask: Int?, + rightTiledTask: Int?, ): Desktop.Builder { clearTasksByTaskId() @@ -238,7 +248,11 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis } putAllTasksByTaskId( visibleTasks.associateWith { - createDesktopTask(it, state = DesktopTaskState.VISIBLE) + createDesktopTask( + it, + state = DesktopTaskState.VISIBLE, + getTilingStateForTask(it, leftTiledTask, rightTiledTask), + ) } ) putAllTasksByTaskId( @@ -249,6 +263,17 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis return this } + private fun getTilingStateForTask( + taskId: Int, + leftTiledTask: Int?, + rightTiledTask: Int?, + ): DesktopTaskTilingState = + when (taskId) { + leftTiledTask -> DesktopTaskTilingState.LEFT + rightTiledTask -> DesktopTaskTilingState.RIGHT + else -> DesktopTaskTilingState.NONE + } + private fun Desktop.Builder.updateZOrder( freeformTasksInZOrder: ArrayList<Int> ): Desktop.Builder { @@ -260,7 +285,12 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis private fun createDesktopTask( taskId: Int, state: DesktopTaskState = DesktopTaskState.VISIBLE, + tiling_state: DesktopTaskTilingState = DesktopTaskTilingState.NONE, ): DesktopTask = - DesktopTask.newBuilder().setTaskId(taskId).setDesktopTaskState(state).build() + DesktopTask.newBuilder() + .setTaskId(taskId) + .setDesktopTaskState(state) + .setDesktopTaskTilingState(tiling_state) + .build() } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt index 49cb7391fe97..5ed0b1d1616f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt @@ -126,6 +126,20 @@ class DesktopRepositoryInitializerImpl( taskId = task.taskId, ) } + + if (task.desktopTaskTilingState == DesktopTaskTilingState.LEFT) { + repository.addLeftTiledTask( + persistentDesktop.displayId, + task.taskId, + ) + } else if ( + task.desktopTaskTilingState == DesktopTaskTilingState.RIGHT + ) { + repository.addRightTiledTask( + persistentDesktop.displayId, + task.taskId, + ) + } } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/persistent_desktop_repositories.proto b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/persistent_desktop_repositories.proto index 010523162cec..86dcee8f8515 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/persistent_desktop_repositories.proto +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/persistent_desktop_repositories.proto @@ -9,9 +9,16 @@ enum DesktopTaskState { MINIMIZED = 1; } +enum DesktopTaskTilingState { + NONE = 1; + LEFT = 2; + RIGHT = 3; +} + message DesktopTask { optional int32 task_id = 1; optional DesktopTaskState desktop_task_state= 2; + optional DesktopTaskTilingState desktop_task_tiling_state = 3; } message Desktop { 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/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt index 7c5f34f979cd..854d9e1deef1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt @@ -137,6 +137,7 @@ class DesktopTilingWindowDecoration( // Observe drag resizing to break tiling if a task is drag resized. desktopModeWindowDecoration.addDragResizeListener(this) val callback = { initTilingForDisplayIfNeeded(taskInfo.configuration, isFirstTiledApp) } + updateDesktopRepository(taskInfo.taskId, snapPosition = position) if (isTiled) { val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentBounds, callback) @@ -159,6 +160,14 @@ class DesktopTilingWindowDecoration( return isTiled } + private fun updateDesktopRepository(taskId: Int, snapPosition: SnapPosition) { + when (snapPosition) { + SnapPosition.LEFT -> desktopUserRepositories.current.addLeftTiledTask(displayId, taskId) + SnapPosition.RIGHT -> + desktopUserRepositories.current.addRightTiledTask(displayId, taskId) + } + } + // If a task is already tiled on the same position, release this task, otherwise if the same // task is tiled on the opposite side, remove it from the opposite side so it's tiled correctly. private fun initTilingApps( @@ -580,6 +589,7 @@ class DesktopTilingWindowDecoration( ) { val taskRepository = desktopUserRepositories.current if (taskId == leftTaskResizingHelper?.taskInfo?.taskId) { + desktopUserRepositories.current.removeLeftTiledTask(displayId) removeTask(leftTaskResizingHelper, taskVanished, shouldDelayUpdate) leftTaskResizingHelper = null val taskId = rightTaskResizingHelper?.taskInfo?.taskId @@ -593,6 +603,7 @@ class DesktopTilingWindowDecoration( } if (taskId == rightTaskResizingHelper?.taskInfo?.taskId) { + desktopUserRepositories.current.removeRightTiledTask(displayId) removeTask(rightTaskResizingHelper, taskVanished, shouldDelayUpdate) rightTaskResizingHelper = null val taskId = leftTaskResizingHelper?.taskInfo?.taskId 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/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt index da6a67c679ff..96b826f93aae 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt @@ -33,6 +33,7 @@ import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERN import android.view.Display import android.view.Display.DEFAULT_DISPLAY import android.view.IWindowManager +import android.view.InputDevice import android.view.WindowManager.TRANSIT_CHANGE import android.window.DisplayAreaInfo import android.window.WindowContainerTransaction @@ -103,6 +104,7 @@ class DesktopDisplayModeControllerTest( private val wallpaperToken = MockToken().token() private val defaultDisplay = mock<Display>() private val externalDisplay = mock<Display>() + private val mouseDevice = mock<InputDevice>() private lateinit var extendedDisplaySettingsRestoreSession: ExtendedDisplaySettingsRestoreSession @@ -150,6 +152,9 @@ class DesktopDisplayModeControllerTest( defaultDisplay ) ).thenReturn(true) + whenever(mouseDevice.supportsSource(InputDevice.SOURCE_MOUSE)).thenReturn(true) + whenever(inputManager.getInputDevice(EXTERNAL_DEVICE_ID)).thenReturn(mouseDevice) + setMouseConnected(false) } @After @@ -207,6 +212,7 @@ class DesktopDisplayModeControllerTest( fun testTargetWindowingMode_formfactorDisabled( @TestParameter param: ExternalDisplayBasedTargetModeTestCase, @TestParameter tabletModeStatus: SwitchState, + @TestParameter hasAnyMouseDevice: Boolean, ) { whenever(mockWindowManager.getWindowingMode(anyInt())) .thenReturn(param.defaultWindowingMode) @@ -216,6 +222,7 @@ class DesktopDisplayModeControllerTest( disconnectExternalDisplay() } setTabletModeStatus(tabletModeStatus) + setMouseConnected(hasAnyMouseDevice) setExtendedMode(param.extendedDisplayEnabled) whenever( DesktopModeStatus.isDesktopModeSupportedOnDisplay( @@ -247,6 +254,7 @@ class DesktopDisplayModeControllerTest( defaultDisplay ) ).thenReturn(param.isDefaultDisplayDesktopEligible) + setMouseConnected(param.hasAnyMouseDevice) assertThat(controller.getTargetWindowingModeForDefaultDisplay()) .isEqualTo(param.expectedWindowingMode) @@ -321,6 +329,11 @@ class DesktopDisplayModeControllerTest( } } + private fun setMouseConnected(connected: Boolean) { + whenever(inputManager.inputDeviceIds) + .thenReturn(if (connected) intArrayOf(EXTERNAL_DEVICE_ID) else intArrayOf()) + } + private class ExtendedDisplaySettingsRestoreSession( private val contentResolver: ContentResolver ) { @@ -345,6 +358,7 @@ class DesktopDisplayModeControllerTest( companion object { const val EXTERNAL_DISPLAY_ID = 100 + const val EXTERNAL_DEVICE_ID = 10 enum class SwitchState(val value: Int) { UNKNOWN(InputManager.SWITCH_STATE_UNKNOWN), @@ -478,174 +492,391 @@ class DesktopDisplayModeControllerTest( val extendedDisplayEnabled: Boolean, val tabletModeStatus: SwitchState, val isDefaultDisplayDesktopEligible: Boolean, + val hasAnyMouseDevice: Boolean, val expectedWindowingMode: Int, ) { - EXTERNAL_EXTENDED_TABLET_NO_PROJECTED( + EXTERNAL_EXTENDED_TABLET_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.ON, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_EXTENDED_TABLET_NO_PROJECTED( + NO_EXTERNAL_EXTENDED_TABLET_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.ON, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_MIRROR_TABLET_NO_PROJECTED( + EXTERNAL_MIRROR_TABLET_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_MIRROR_TABLET_NO_PROJECTED( + NO_EXTERNAL_MIRROR_TABLET_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED( + EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED( + NO_EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED( + EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED( + NO_EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED( + EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - NO_EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED( + NO_EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED( + EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.UNKNOWN, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED( + NO_EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED_NO_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.UNKNOWN, isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_EXTENDED_TABLET_PROJECTED_NO_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_EXTENDED_TABLET_PROJECTED_NO_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_TABLET_PROJECTED_NO_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_TABLET_PROJECTED_NO_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED_NO_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED_NO_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_CLAMSHELL_PROJECTED_NO_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_CLAMSHELL_PROJECTED_NO_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_EXTENDED_UNKNOWN_PROJECTED_NO_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_EXTENDED_UNKNOWN_PROJECTED_NO_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_UNKNOWN_PROJECTED_NO_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_UNKNOWN_PROJECTED_NO_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_EXTENDED_TABLET_PROJECTED( + EXTERNAL_EXTENDED_TABLET_NO_PROJECTED_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + NO_EXTERNAL_EXTENDED_TABLET_NO_PROJECTED_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + EXTERNAL_MIRROR_TABLET_NO_PROJECTED_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + NO_EXTERNAL_MIRROR_TABLET_NO_PROJECTED_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.ON, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + NO_EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + NO_EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.OFF, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + NO_EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED_MOUSE( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + NO_EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED_MOUSE( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.UNKNOWN, + isDefaultDisplayDesktopEligible = true, + hasAnyMouseDevice = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + EXTERNAL_EXTENDED_TABLET_PROJECTED_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.ON, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_EXTENDED_TABLET_PROJECTED( + NO_EXTERNAL_EXTENDED_TABLET_PROJECTED_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.ON, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_MIRROR_TABLET_PROJECTED( + EXTERNAL_MIRROR_TABLET_PROJECTED_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_MIRROR_TABLET_PROJECTED( + NO_EXTERNAL_MIRROR_TABLET_PROJECTED_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED( + EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED( + NO_EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_MIRROR_CLAMSHELL_PROJECTED( + EXTERNAL_MIRROR_CLAMSHELL_PROJECTED_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_MIRROR_CLAMSHELL_PROJECTED( + NO_EXTERNAL_MIRROR_CLAMSHELL_PROJECTED_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_EXTENDED_UNKNOWN_PROJECTED( + EXTERNAL_EXTENDED_UNKNOWN_PROJECTED_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_EXTENDED_UNKNOWN_PROJECTED( + NO_EXTERNAL_EXTENDED_UNKNOWN_PROJECTED_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - EXTERNAL_MIRROR_UNKNOWN_PROJECTED( + EXTERNAL_MIRROR_UNKNOWN_PROJECTED_MOUSE( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.UNKNOWN, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - NO_EXTERNAL_MIRROR_UNKNOWN_PROJECTED( + NO_EXTERNAL_MIRROR_UNKNOWN_PROJECTED_MOUSE( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.UNKNOWN, isDefaultDisplayDesktopEligible = false, + hasAnyMouseDevice = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt index 6a99d4770728..a7ea66e17363 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt @@ -43,7 +43,8 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() { @Before fun setUp() { - handler = DesktopModeMoveToDisplayTransitionHandler(StubTransaction()) + handler = + DesktopModeMoveToDisplayTransitionHandler(StubTransaction(), mock(), mock(), mock()) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index a10aeca95bce..3fb008377d6e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -58,6 +58,7 @@ import org.mockito.Mockito.spy import org.mockito.kotlin.any import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.eq +import org.mockito.kotlin.isNull import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -331,6 +332,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(arrayOf(1)), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(), + leftTiledTask = null, + rightTiledTask = null, ) verify(persistentRepository) .addOrUpdateDesktop( @@ -339,6 +342,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(arrayOf(1, 2)), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(), + leftTiledTask = null, + rightTiledTask = null, ) } } @@ -358,6 +363,52 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) + fun leftTiledTask_updatedInRepoAndPersisted() { + runTest(StandardTestDispatcher()) { + repo.addLeftTiledTask(displayId = DEFAULT_DISPLAY, taskId = 1) + + assertThat(repo.getLeftTiledTask(displayId = DEFAULT_DISPLAY)).isEqualTo(1) + verify(persistentRepository) + .addOrUpdateDesktop( + DEFAULT_USER_ID, + DEFAULT_DESKTOP_ID, + visibleTasks = ArraySet(), + minimizedTasks = ArraySet(), + freeformTasksInZOrder = arrayListOf(), + leftTiledTask = 1, + rightTiledTask = null, + ) + + repo.removeRightTiledTask(displayId = DEFAULT_DISPLAY) + assertThat(repo.getRightTiledTask(displayId = DEFAULT_DISPLAY)).isNull() + } + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) + fun rightTiledTask_updatedInRepoAndPersisted() { + runTest(StandardTestDispatcher()) { + repo.addRightTiledTask(displayId = DEFAULT_DISPLAY, taskId = 1) + + assertThat(repo.getRightTiledTask(displayId = DEFAULT_DISPLAY)).isEqualTo(1) + verify(persistentRepository) + .addOrUpdateDesktop( + DEFAULT_USER_ID, + DEFAULT_DESKTOP_ID, + visibleTasks = ArraySet(), + minimizedTasks = ArraySet(), + freeformTasksInZOrder = arrayListOf(), + leftTiledTask = null, + rightTiledTask = 1, + ) + + repo.removeLeftTiledTask(displayId = DEFAULT_DISPLAY) + assertThat(repo.getLeftTiledTask(displayId = DEFAULT_DISPLAY)).isNull() + } + } + + @Test fun isOnlyVisibleNonClosingTask_singleVisibleMinimizedTask() { val taskId = 1 repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true) @@ -663,6 +714,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(5), + leftTiledTask = null, + rightTiledTask = null, ) verify(persistentRepository) .addOrUpdateDesktop( @@ -671,6 +724,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(arrayOf(5)), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(6, 5), + leftTiledTask = null, + rightTiledTask = null, ) verify(persistentRepository) .addOrUpdateDesktop( @@ -679,6 +734,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(arrayOf(5, 6)), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(7, 6, 5), + leftTiledTask = null, + rightTiledTask = null, ) } } @@ -729,6 +786,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(5), + leftTiledTask = null, + rightTiledTask = null, ) verify(persistentRepository) .addOrUpdateDesktop( @@ -737,6 +796,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(arrayOf(5)), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(6, 5), + leftTiledTask = null, + rightTiledTask = null, ) verify(persistentRepository) .addOrUpdateDesktop( @@ -745,6 +806,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(arrayOf(5, 6)), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(7, 6, 5), + leftTiledTask = null, + rightTiledTask = null, ) verify(persistentRepository, times(2)) .addOrUpdateDesktop( @@ -753,6 +816,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(arrayOf(5, 7)), minimizedTasks = ArraySet(arrayOf(6)), freeformTasksInZOrder = arrayListOf(7, 6, 5), + leftTiledTask = null, + rightTiledTask = null, ) } } @@ -793,6 +858,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(1), + leftTiledTask = null, + rightTiledTask = null, ) verify(persistentRepository) .addOrUpdateDesktop( @@ -801,6 +868,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(), minimizedTasks = ArraySet(), freeformTasksInZOrder = ArrayList(), + leftTiledTask = null, + rightTiledTask = null, ) } } @@ -830,6 +899,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(1), + leftTiledTask = null, + rightTiledTask = null, ) verify(persistentRepository) .addOrUpdateDesktop( @@ -838,6 +909,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(), minimizedTasks = ArraySet(), freeformTasksInZOrder = ArrayList(), + leftTiledTask = null, + rightTiledTask = null, ) } } @@ -869,6 +942,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(), minimizedTasks = ArraySet(), freeformTasksInZOrder = arrayListOf(1), + leftTiledTask = null, + rightTiledTask = null, ) verify(persistentRepository, never()) .addOrUpdateDesktop( @@ -877,6 +952,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = ArraySet(), minimizedTasks = ArraySet(), freeformTasksInZOrder = ArrayList(), + leftTiledTask = null, + rightTiledTask = null, ) } } @@ -1391,6 +1468,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { visibleTasks = any(), minimizedTasks = any(), freeformTasksInZOrder = any(), + leftTiledTask = isNull(), + rightTiledTask = isNull(), ) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt index 5f92326b5e5e..6039b8f0edc5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt @@ -115,6 +115,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { minimizedTasks = minimizedTasks, freeformTasksInZOrder = freeformTasksInZOrder, userId = DEFAULT_USER_ID, + leftTiledTask = null, + rightTiledTask = null, ) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) @@ -124,6 +126,50 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { } @Test + fun addTiledTasks_addsTiledTasksToDesktop() { + runTest(StandardTestDispatcher()) { + // Create a basic repository state + val task = createDesktopTask(1) + val desktopPersistentRepositories = createRepositoryWithOneDesk(task) + testDatastore.updateData { desktopPersistentRepositories } + // Create a new state to be initialized + val visibleTasks = ArraySet(listOf(1, 2)) + + // Update with new state + datastoreRepository.addOrUpdateDesktop( + visibleTasks = visibleTasks, + minimizedTasks = ArraySet(), + freeformTasksInZOrder = ArrayList(), + userId = DEFAULT_USER_ID, + leftTiledTask = 1, + rightTiledTask = null, + ) + + var actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) + assertThat(actualDesktop?.tasksByTaskIdMap?.get(1)?.desktopTaskTilingState) + .isEqualTo(DesktopTaskTilingState.LEFT) + assertThat(actualDesktop?.tasksByTaskIdMap?.get(2)?.desktopTaskTilingState) + .isEqualTo(DesktopTaskTilingState.NONE) + + // Update with new state + datastoreRepository.addOrUpdateDesktop( + visibleTasks = visibleTasks, + minimizedTasks = ArraySet(), + freeformTasksInZOrder = ArrayList(), + userId = DEFAULT_USER_ID, + leftTiledTask = null, + rightTiledTask = 2, + ) + + actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) + assertThat(actualDesktop?.tasksByTaskIdMap?.get(1)?.desktopTaskTilingState) + .isEqualTo(DesktopTaskTilingState.NONE) + assertThat(actualDesktop?.tasksByTaskIdMap?.get(2)?.desktopTaskTilingState) + .isEqualTo(DesktopTaskTilingState.RIGHT) + } + } + + @Test fun removeUsers_removesUsersData() { runTest(StandardTestDispatcher()) { val task = createDesktopTask(1) @@ -138,12 +184,16 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { minimizedTasks = minimizedTasks, freeformTasksInZOrder = freeformTasksInZOrder, userId = DEFAULT_USER_ID, + leftTiledTask = null, + rightTiledTask = null, ) datastoreRepository.addOrUpdateDesktop( visibleTasks = visibleTasks, minimizedTasks = minimizedTasks, freeformTasksInZOrder = freeformTasksInZOrder, userId = USER_ID_2, + leftTiledTask = null, + rightTiledTask = null, ) datastoreRepository.removeUsers(mutableListOf(USER_ID_2)) @@ -175,6 +225,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { minimizedTasks = minimizedTasks, freeformTasksInZOrder = freeformTasksInZOrder, userId = DEFAULT_USER_ID, + leftTiledTask = null, + rightTiledTask = null, ) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) @@ -200,6 +252,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { minimizedTasks = minimizedTasks, freeformTasksInZOrder = freeformTasksInZOrder, userId = DEFAULT_USER_ID, + leftTiledTask = null, + rightTiledTask = null, ) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) 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/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt index 646ec21cab9b..9e148e57260b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt @@ -28,6 +28,7 @@ import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger +import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.DesktopUserRepositories @@ -63,6 +64,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { private val transitionsMock: Transitions = mock() private val shellTaskOrganizerMock: ShellTaskOrganizer = mock() private val userRepositories: DesktopUserRepositories = mock() + private val desktopRepository: DesktopRepository = mock() private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val toggleResizeDesktopTaskTransitionHandlerMock: ToggleResizeDesktopTaskTransitionHandler = @@ -105,6 +107,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { whenever(contextMock.createContextAsUser(any(), any())).thenReturn(context) whenever(contextMock.resources).thenReturn(resourcesMock) whenever(resourcesMock.getDimensionPixelSize(any())).thenReturn(10) + whenever(userRepositories.current).thenReturn(desktopRepository) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt index e4424f3c57f2..e5f8d7d34a47 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt @@ -653,6 +653,64 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { verify(context, never()).getApplicationContext() } + @Test + fun addLeftTiledTask_updatesTaskRepository_whenLeftTileInitializedOrBroken() { + val task1 = createVisibleTask() + val stableBounds = STABLE_BOUNDS_MOCK + whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout) + whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(stableBounds) + } + whenever(context.resources).thenReturn(resources) + whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) + whenever(tiledTaskHelper.taskInfo).thenReturn(task1) + whenever(tiledTaskHelper.desktopModeWindowDecoration).thenReturn(desktopWindowDecoration) + + tilingDecoration.onAppTiled( + task1, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.LEFT, + BOUNDS, + ) + + verify(desktopRepository, times(1)).addLeftTiledTask(displayId, task1.taskId) + verify(desktopRepository, never()).addRightTiledTask(displayId, task1.taskId) + + tilingDecoration.removeTaskIfTiled(task1.taskId) + + verify(desktopRepository, times(1)).removeLeftTiledTask(displayId) + verify(desktopRepository, never()).removeRightTiledTask(displayId) + } + + @Test + fun addRightTiledTask_updatesTaskRepository_whenRightTileInitializedOrBroken() { + val task1 = createVisibleTask() + val stableBounds = STABLE_BOUNDS_MOCK + whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout) + whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(stableBounds) + } + whenever(context.resources).thenReturn(resources) + whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) + whenever(tiledTaskHelper.taskInfo).thenReturn(task1) + whenever(tiledTaskHelper.desktopModeWindowDecoration).thenReturn(desktopWindowDecoration) + + tilingDecoration.onAppTiled( + task1, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.RIGHT, + BOUNDS, + ) + + verify(desktopRepository, times(1)).addRightTiledTask(displayId, task1.taskId) + verify(desktopRepository, never()).addLeftTiledTask(displayId, task1.taskId) + + tilingDecoration.removeTaskIfTiled(task1.taskId) + + verify(desktopRepository, times(1)).removeRightTiledTask(displayId) + verify(desktopRepository, never()).removeLeftTiledTask(displayId) + } + private fun initTiledTaskHelperMock(taskInfo: ActivityManager.RunningTaskInfo) { whenever(tiledTaskHelper.bounds).thenReturn(BOUNDS) whenever(tiledTaskHelper.taskInfo).thenReturn(taskInfo) 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/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index dc669a5eca73..aa8cbd1f0703 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -175,6 +175,8 @@ bool SkiaPipeline::setupMultiFrameCapture() { if (stream->isValid()) { mOpenMultiPicStream = std::move(stream); mSerialContext.reset(new SkSharingSerialContext()); + // passing the GrDirectContext to the SerialContext allows us to raster/serialize GPU images + mSerialContext->setDirectContext(mRenderThread.getGrContext()); SkSerialProcs procs; procs.fImageProc = SkSharingSerialContext::serializeImage; procs.fImageCtx = mSerialContext.get(); 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/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 3625c002e9d8..7cdc13cb9189 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -36,6 +36,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.collection.ArraySet; import com.android.settingslib.R; import com.android.settingslib.flags.Flags; @@ -274,29 +275,37 @@ public class BluetoothEventManager { @VisibleForTesting void dispatchActiveDeviceChanged( @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { - CachedBluetoothDevice targetDevice = activeDevice; + CachedBluetoothDevice mainActiveDevice = activeDevice; for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) { - // should report isActive from main device or it will cause trouble to other callers. CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); - CachedBluetoothDevice finalTargetDevice = targetDevice; - if (targetDevice != null - && ((subDevice != null && subDevice.equals(targetDevice)) - || cachedDevice.getMemberDevice().stream().anyMatch( - memberDevice -> memberDevice.equals(finalTargetDevice)))) { - Log.d(TAG, - "The active device is the sub/member device " - + targetDevice.getDevice().getAnonymizedAddress() - + ". change targetDevice as main device " - + cachedDevice.getDevice().getAnonymizedAddress()); - targetDevice = cachedDevice; + Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); + final Set<CachedBluetoothDevice> cachedDevices = new ArraySet<>(); + cachedDevices.add(cachedDevice); + if (!memberDevices.isEmpty()) { + cachedDevices.addAll(memberDevices); + } else if (subDevice != null) { + cachedDevices.add(subDevice); + } + + // should report isActive from main device or it will cause trouble to other callers. + if (activeDevice != null + && (cachedDevices.stream().anyMatch( + device -> device.equals(activeDevice)))) { + Log.d(TAG, "The active device is in the set, report main device as active device:" + + cachedDevice.getDevice() + ", active device:" + activeDevice.getDevice()); + mainActiveDevice = cachedDevice; } - boolean isActiveDevice = cachedDevice.equals(targetDevice); - cachedDevice.onActiveDeviceChanged(isActiveDevice, bluetoothProfile); + boolean isActiveDevice = cachedDevice.equals(mainActiveDevice); + cachedDevices.forEach( + device -> device.onActiveDeviceChanged(isActiveDevice, bluetoothProfile)); + //TODO: b/400440223 - Check if we can call DeviceManager.onActiveDeviceChanged & + // Callback.onActiveDeviceChanged for cachedDevices Set also, so we don't need to report + // isActive from main device. mDeviceManager.onActiveDeviceChanged(cachedDevice); } for (BluetoothCallback callback : mCallbacks) { - callback.onActiveDeviceChanged(targetDevice, bluetoothProfile); + callback.onActiveDeviceChanged(mainActiveDevice, bluetoothProfile); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java index eac6923473b1..2620174e5d39 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java @@ -70,6 +70,9 @@ public class BluetoothEventManagerTest { public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String DEVICE_NAME = "test_device_name"; + private static final String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11"; + private static final String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22"; + private static final String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33"; @Mock private LocalBluetoothAdapter mLocalAdapter; @@ -132,6 +135,9 @@ public class BluetoothEventManagerTest { when(mA2dpProfile.isProfileReady()).thenReturn(true); when(mHearingAidProfile.isProfileReady()).thenReturn(true); when(mLeAudioProfile.isProfileReady()).thenReturn(true); + when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1); + when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2); + when(mDevice3.getAddress()).thenReturn(DEVICE_ADDRESS_3); mCachedDevice1 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1); mCachedDevice2 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2); mCachedDevice3 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3); @@ -515,7 +521,6 @@ public class BluetoothEventManagerTest { cachedDevices.add(mCachedDevice2); int group1 = 1; - when(mDevice3.getAddress()).thenReturn("testAddress3"); mCachedDevice1.setGroupId(group1); mCachedDevice3.setGroupId(group1); mCachedDevice1.addMemberDevice(mCachedDevice3); @@ -620,18 +625,32 @@ public class BluetoothEventManagerTest { } @Test - public void dispatchActiveDeviceChanged_activeFromSubDevice_mainCachedDeviceActive() { + public void dispatchActiveDeviceChanged_activeFromSubDevice_bothCachedDevicesActive() { CachedBluetoothDevice subDevice = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3); mCachedDevice1.setSubDevice(subDevice); when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( Collections.singletonList(mCachedDevice1)); - mCachedDevice1.onProfileStateChanged(mHearingAidProfile, - BluetoothProfile.STATE_CONNECTED); + mCachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); - assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse(); mBluetoothEventManager.dispatchActiveDeviceChanged(subDevice, BluetoothProfile.HEARING_AID); + assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isTrue(); + assertThat(subDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).isTrue(); + } + + @Test + public void dispatchActiveDeviceChanged_activeFromMemberDevice_allCachedDevicesActive() { + mCachedDevice1.addMemberDevice(mCachedDevice2); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( + Collections.singletonList(mCachedDevice1)); + mCachedDevice1.onProfileStateChanged(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED); + + mBluetoothEventManager.dispatchActiveDeviceChanged(mCachedDevice2, + BluetoothProfile.LE_AUDIO); + + assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).isTrue(); + assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.LE_AUDIO)).isTrue(); } @Test 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/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java index ca94482b9c5a..21ec89646f0f 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java @@ -245,6 +245,17 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner runner.onAnimationCancelled(); finishRunnable.run(); } + + @Override + public void onTransitionConsumed(IBinder transition, boolean aborted) + throws RemoteException { + // Notify the remote runner that the transition has been canceled if the transition + // was merged into another transition or aborted + synchronized (mFinishRunnables) { + mFinishRunnables.remove(transition); + } + runner.onAnimationCancelled(); + } }; } 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/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt index 2ea9c487c27c..362748ec71b0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt @@ -112,6 +112,16 @@ interface NestedDraggable { interface Controller { /** + * Whether this controller is ready to drag. [onDrag] will be called only if this returns + * `true`, and any drag event will be ignored until then. + * + * This can for instance be used to wait for the content we are dragging to to be composed + * before actually dragging, reducing perceivable jank at the beginning of a drag. + */ + val isReadyToDrag: Boolean + get() = true + + /** * Whether drags that were started from nested scrolls should be automatically * [stopped][onDragStopped] as soon as they don't consume the entire `delta` passed to * [onDrag]. @@ -274,6 +284,9 @@ private class NestedDraggableNode( /** The pointers currently down, in order of which they were done and mapping to their type. */ private val pointersDown = linkedMapOf<PointerId, PointerType>() + /** Whether the next drag event should be ignored. */ + private var ignoreNextDrag = false + init { delegate(nestedScrollModifierNode(this, nestedScrollDispatcher)) } @@ -426,6 +439,7 @@ private class NestedDraggableNode( velocityTracker: VelocityTracker, ) { velocityTracker.addPointerInputChange(change) + if (shouldIgnoreDrag(controller)) return scrollWithOverscroll(delta.toOffset()) { deltaFromOverscroll -> scrollWithNestedScroll(deltaFromOverscroll) { deltaFromNestedScroll -> @@ -434,6 +448,23 @@ private class NestedDraggableNode( } } + private fun shouldIgnoreDrag(controller: NestedDraggable.Controller): Boolean { + return when { + !controller.isReadyToDrag -> { + // The controller is not ready yet, so we are waiting for an expensive frame to be + // composed. We should ignore this drag and the next one, given that the first delta + // after an expensive frame will be large. + ignoreNextDrag = true + true + } + ignoreNextDrag -> { + ignoreNextDrag = false + true + } + else -> false + } + } + private fun onDragStopped(controller: NestedDraggable.Controller, velocity: Velocity) { // We launch in the scope of the dispatcher so that the fling is not cancelled if this node // is removed right after onDragStopped() is called. @@ -617,6 +648,8 @@ private class NestedDraggableNode( } private fun scrollWithOverscroll(controller: NestedScrollController, offset: Offset): Offset { + if (shouldIgnoreDrag(controller.controller)) return offset + return scrollWithOverscroll(offset) { delta -> val available = delta.toFloat() val consumed = controller.controller.onDrag(available) diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt index b247993de4e4..b31617369cdb 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt @@ -972,6 +972,36 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw } @Test + fun isReadyToDrag() { + var isReadyToDrag by mutableStateOf(false) + val draggable = TestDraggable(isReadyToDrag = { isReadyToDrag }) + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + rule.onRoot().performTouchInput { + down(center) + moveBy((touchSlop + 10f).toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragDelta).isEqualTo(0f) + + rule.onRoot().performTouchInput { moveBy(20f.toOffset()) } + assertThat(draggable.onDragDelta).isEqualTo(0f) + + // Flag as ready to drag. We still ignore the next drag after that. + isReadyToDrag = true + rule.onRoot().performTouchInput { moveBy(30f.toOffset()) } + assertThat(draggable.onDragDelta).isEqualTo(0f) + + // Now we drag. + rule.onRoot().performTouchInput { moveBy(40f.toOffset()) } + assertThat(draggable.onDragDelta).isEqualTo(40f) + } + + @Test fun consumeNestedPreScroll() { var consumeNestedPreScroll by mutableStateOf(false) val draggable = TestDraggable(shouldConsumeNestedPreScroll = { consumeNestedPreScroll }) @@ -1060,6 +1090,7 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw }, private val shouldConsumeNestedPostScroll: (Float) -> Boolean = { true }, private val shouldConsumeNestedPreScroll: (Float) -> Boolean = { false }, + private val isReadyToDrag: () -> Boolean = { true }, private val autoStopNestedDrags: Boolean = false, ) : NestedDraggable { var shouldStartDrag = true @@ -1092,6 +1123,9 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw return object : NestedDraggable.Controller { override val autoStopNestedDrags: Boolean = this@TestDraggable.autoStopNestedDrags + override val isReadyToDrag: Boolean + get() = isReadyToDrag() + override fun onDrag(delta: Float): Float { onDragCalled = true onDragDelta += delta diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 3150e94908cd..2b8fe39c4870 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -47,6 +47,7 @@ import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.thenIf +import com.android.systemui.Flags import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys @@ -104,7 +105,9 @@ val sceneTransitionsV2 = transitions { fade(Communal.Elements.Grid) fade(Communal.Elements.IndicationArea) fade(Communal.Elements.LockIcon) - fade(Communal.Elements.StatusBar) + if (!Flags.glanceableHubV2()) { + fade(Communal.Elements.StatusBar) + } } timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) } } @@ -131,7 +134,9 @@ val sceneTransitions = transitions { fade(Communal.Elements.Grid) fade(Communal.Elements.IndicationArea) fade(Communal.Elements.LockIcon) - fade(Communal.Elements.StatusBar) + if (!Flags.glanceableHubV2()) { + fade(Communal.Elements.StatusBar) + } } timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 2d03e2bcdd19..0181928317e1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.android.compose.animation.scene.ContentScope +import com.android.systemui.Flags import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection @@ -70,8 +71,10 @@ constructor( content = { Box(modifier = Modifier.fillMaxSize()) { with(communalPopupSection) { Popup() } - with(ambientStatusBarSection) { - AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f)) + if (!Flags.glanceableHubV2()) { + with(ambientStatusBarSection) { + AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f)) + } } CommunalHub( viewModel = viewModel, 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/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt index b04d89d8160f..0b0df06c2015 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt @@ -20,6 +20,7 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.SpringSpec import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.runtime.withFrameNanos import com.android.compose.animation.scene.content.state.TransitionState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -47,6 +48,11 @@ internal fun CoroutineScope.animateContent( oneOffAnimation.animatable = it } + if (layoutState.deferTransitionProgress) { + // Defer the animation by one frame so that the transition progress is changed only when + // the expensive first composition frame is done. + withFrameNanos {} + } animatable.animateTo(targetProgress, animationSpec, initialVelocity) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 024ca22069ae..36eafa400090 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -290,6 +290,15 @@ private class DragControllerImpl( val isDrivingTransition: Boolean get() = layoutState.transitionState == swipeAnimation.contentTransition + override val isReadyToDrag: Boolean + get() { + return !layoutState.deferTransitionProgress || + with(draggableHandler.layoutImpl.elementStateScope) { + swipeAnimation.fromContent.targetSize() != null && + swipeAnimation.toContent.targetSize() != null + } + } + init { check(!isDrivingTransition) { "Multiple controllers with the same SwipeTransition" } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 4da83c3a6fc9..a8b676d4ee45 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -462,7 +462,9 @@ internal class SceneTransitionLayoutImpl( // swipes. .swipeToScene(horizontalDraggableHandler) .swipeToScene(verticalDraggableHandler) - .then(LayoutElement(layoutImpl = this)) + .then( + LayoutElement(layoutImpl = this, transitionState = this.state.transitionState) + ) ) { LookaheadScope { if (_lookaheadScope == null) { @@ -623,23 +625,28 @@ internal class SceneTransitionLayoutImpl( @VisibleForTesting internal fun overlaysOrNullForTest(): Map<OverlayKey, Overlay>? = _overlays } -private data class LayoutElement(private val layoutImpl: SceneTransitionLayoutImpl) : - ModifierNodeElement<LayoutNode>() { - override fun create(): LayoutNode = LayoutNode(layoutImpl) +private data class LayoutElement( + private val layoutImpl: SceneTransitionLayoutImpl, + private val transitionState: TransitionState, +) : ModifierNodeElement<LayoutNode>() { + override fun create(): LayoutNode = LayoutNode(layoutImpl, transitionState) override fun update(node: LayoutNode) { node.layoutImpl = layoutImpl + node.transitionState = transitionState } } -private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) : - Modifier.Node(), ApproachLayoutModifierNode, LayoutAwareModifierNode { +private class LayoutNode( + var layoutImpl: SceneTransitionLayoutImpl, + var transitionState: TransitionState, +) : Modifier.Node(), ApproachLayoutModifierNode, LayoutAwareModifierNode { override fun onRemeasured(size: IntSize) { layoutImpl.lastSize = size } override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean { - return layoutImpl.state.isTransitioning() + return transitionState is TransitionState.Transition.ChangeScene } @ExperimentalComposeUiApi @@ -652,8 +659,7 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) : val width: Int val height: Int - val transition = - layoutImpl.state.currentTransition as? TransitionState.Transition.ChangeScene + val transition = transitionState as? TransitionState.Transition.ChangeScene if (transition == null) { width = placeable.width height = placeable.height @@ -662,6 +668,9 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) : val fromSize = layoutImpl.scene(transition.fromScene).targetSize val toSize = layoutImpl.scene(transition.toScene).targetSize + check(fromSize != Element.SizeUnspecified) { "fromSize is unspecified " } + check(toSize != Element.SizeUnspecified) { "toSize is unspecified" } + // Optimization: make sure we don't read state.progress if fromSize == // toSize to avoid running this code every frame when the layout size does // not change. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 56e8c458ad67..4e28dd569f21 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -234,6 +234,10 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState * `from` overlay by `to` overlay. * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other * [SceneTransitionLayoutState]s. + * @param deferTransitionProgress whether we should wait for the first composition to be done before + * changing the progress of a transition. This can help reduce perceivable jank at the start of a + * transition in case the first composition of a content takes a lot of time and we are going to + * miss that first frame. */ fun MutableSceneTransitionLayoutState( initialScene: SceneKey, @@ -246,6 +250,9 @@ fun MutableSceneTransitionLayoutState( canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true }, onTransitionStart: (TransitionState.Transition) -> Unit = {}, onTransitionEnd: (TransitionState.Transition) -> Unit = {}, + + // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released. + deferTransitionProgress: Boolean = false, ): MutableSceneTransitionLayoutState { return MutableSceneTransitionLayoutStateImpl( initialScene, @@ -258,6 +265,7 @@ fun MutableSceneTransitionLayoutState( canReplaceOverlay, onTransitionStart, onTransitionEnd, + deferTransitionProgress, ) } @@ -272,6 +280,9 @@ fun rememberMutableSceneTransitionLayoutState( canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true }, onTransitionStart: (TransitionState.Transition) -> Unit = {}, onTransitionEnd: (TransitionState.Transition) -> Unit = {}, + + // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released. + deferTransitionProgress: Boolean = false, ): MutableSceneTransitionLayoutState { val motionScheme = MaterialTheme.motionScheme val layoutState = remember { @@ -286,6 +297,7 @@ fun rememberMutableSceneTransitionLayoutState( canReplaceOverlay = canReplaceOverlay, onTransitionStart = onTransitionStart, onTransitionEnd = onTransitionEnd, + deferTransitionProgress = deferTransitionProgress, ) } @@ -298,6 +310,7 @@ fun rememberMutableSceneTransitionLayoutState( layoutState.canReplaceOverlay = canReplaceOverlay layoutState.onTransitionStart = onTransitionStart layoutState.onTransitionEnd = onTransitionEnd + layoutState.deferTransitionProgress = deferTransitionProgress } return layoutState } @@ -317,6 +330,8 @@ internal class MutableSceneTransitionLayoutStateImpl( }, internal var onTransitionStart: (TransitionState.Transition) -> Unit = {}, internal var onTransitionEnd: (TransitionState.Transition) -> Unit = {}, + // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released. + internal var deferTransitionProgress: Boolean = false, ) : MutableSceneTransitionLayoutState { private val creationThread: Thread = Thread.currentThread() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt index 2d2a81542f84..5d4232d8a8b7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt @@ -38,7 +38,7 @@ internal class ElementStateScopeImpl(private val layoutImpl: SceneTransitionLayo } override fun ContentKey.targetSize(): IntSize? { - return layoutImpl.content(this).targetSize.takeIf { it != IntSize.Zero } + return layoutImpl.content(this).targetSize.takeIf { it != Element.SizeUnspecified } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 90bf92ae1dd0..7492f3737edc 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -93,9 +93,10 @@ internal sealed class Content( val containerState = ContainerState() // Important: All fields in this class should be backed by State given that contents are updated - // directly during composition, outside of a SideEffect. + // directly during composition, outside of a SideEffect, or are observed during composition, + // layout or drawing. var content by mutableStateOf(content) - var targetSize by mutableStateOf(IntSize.Zero) + var targetSize by mutableStateOf(Element.SizeUnspecified) var userActions by mutableStateOf(actions) var zIndex by mutableFloatStateOf(zIndex) @@ -212,9 +213,17 @@ private class ContentNode( return if (isElevationPossible) delegate(ContainerNode(content.containerState)) else null } + override fun onDetach() { + this.content.targetSize = Element.SizeUnspecified + } + fun update(content: Content, isElevationPossible: Boolean, isInvisible: Boolean) { - if (content != this.content || isElevationPossible != this.isElevationPossible) { + if (content != this.content) { + this.content.targetSize = Element.SizeUnspecified this.content = content + } + + if (content != this.content || isElevationPossible != this.isElevationPossible) { this.isElevationPossible = isElevationPossible containerDelegate?.let { undelegate(it) } diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json index 5dbb01338090..770f85969ed1 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json @@ -76,7 +76,7 @@ }, { "x": 5.2, - "y": 5.6 + "y": 5.2 }, { "x": 5.2, @@ -326,11 +326,11 @@ }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json index 1543d186ea03..6b7de56c1da6 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json @@ -81,8 +81,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.8 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -344,7 +344,7 @@ }, { "width": 150, - "height": 294 + "height": 292.4 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json index 115483cf4013..015df8fd02fb 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json @@ -84,8 +84,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.4 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -259,7 +259,7 @@ }, { "width": 150, - "height": 290 + "height": 288.8 }, { "width": 150, @@ -279,34 +279,34 @@ }, { "width": 150, - "height": 223.6 + "height": 224 }, { "width": 150, - "height": 182.8 + "height": 183.2 }, { "width": 150, - "height": 141.2 + "height": 141.6 }, { "width": 150, - "height": 104 + "height": 104.4 }, { - "width": 147.6, - "height": 74 + "width": 148, + "height": 74.4 }, { "width": 139.6, - "height": 50.8 + "height": 51.2 }, { "width": 133.6, - "height": 32 + "height": 32.4 }, { - "width": 129.2, + "width": 129.6, "height": 15.6 }, { @@ -409,19 +409,19 @@ 1, 1, 1, - 0.99479187, - 0.8575029, - 0.65572864, - 0.4691311, - 0.3215357, - 0.21380007, - 0.13896108, - 0.0887118, - 0.05580789, - 0.03467691, - 0.021318138, - 0.0129826665, - 0.007839739, + 0.99532944, + 0.8594856, + 0.65783304, + 0.47089338, + 0.32286334, + 0.21474117, + 0.139602, + 0.089136004, + 0.056082606, + 0.03485179, + 0.02142787, + 0.013050735, + 0.007881463, 0, 0, 0, diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json index f44d4cd7c14e..5ac06217d161 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json @@ -72,7 +72,7 @@ }, { "x": 5.2, - "y": 5.6 + "y": 5.2 }, { "x": 5.2, @@ -306,11 +306,11 @@ }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json index 9b68c71a7a34..1cae67b3eba7 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json @@ -79,8 +79,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.8 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -334,7 +334,7 @@ }, { "width": 150, - "height": 294 + "height": 292.4 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json index 86805bd6ff29..ca87bc28c45d 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json @@ -83,8 +83,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.4 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -254,7 +254,7 @@ }, { "width": 150, - "height": 290 + "height": 288.8 }, { "width": 150, @@ -274,27 +274,27 @@ }, { "width": 150, - "height": 223.6 + "height": 224 }, { "width": 150, - "height": 182.8 + "height": 183.2 }, { "width": 150, - "height": 141.2 + "height": 141.6 }, { "width": 150, - "height": 104 + "height": 104.4 }, { "width": 150, - "height": 72 + "height": 72.4 }, { "width": 150, - "height": 46 + "height": 46.4 }, { "width": 150, @@ -400,19 +400,19 @@ 1, 1, 1, - 0.99479187, - 0.8575029, - 0.65572864, - 0.4691311, - 0.3215357, - 0.21380007, - 0.13896108, - 0.0887118, - 0.05580789, - 0.03467691, - 0.021318138, - 0.0129826665, - 0.007839739, + 0.99532944, + 0.8594856, + 0.65783304, + 0.47089338, + 0.32286334, + 0.21474117, + 0.139602, + 0.089136004, + 0.056082606, + 0.03485179, + 0.02142787, + 0.013050735, + 0.007881463, 0, 0, 0, diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt index e4a87ba3a26b..7d9a32b3948a 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt @@ -58,17 +58,24 @@ class ElementStateAccessTest { assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(1f, 1f)) } + at(16) { + val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode() + assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.75f) + assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.75f, 0.75f)) + } + at(32) { val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode() assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.5f) assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.5f, 0.5f)) } - at(64) { + at(48) { val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode() - assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0f) - assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0f, 0f)) + assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.25f) + assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.25f, 0.25f)) } + after { onElement(TestElements.Foo).assertDoesNotExist() } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt index c98d6c531d54..6710575e32d2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt @@ -56,6 +56,7 @@ class DeviceInactiveConditionTest : SysuiTestCase() { Kosmos.Fixture { DeviceInactiveCondition( applicationCoroutineScope, + applicationCoroutineScope, keyguardStateController, wakefulnessLifecycle, keyguardUpdateMonitor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt index 6c7783ae44e1..28b9e733be94 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.display.data.repository import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl import com.android.systemui.SysuiTestCase import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java index 0f631509bfba..d9990ba8e9ae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java @@ -275,14 +275,14 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ "abc/.def", /* validateActivity */ true, /* enableSetting */true, /* enableOnLockScreen */ true); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0, UserHandle.USER_CURRENT); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0, UserHandle.USER_CURRENT); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1, UserHandle.USER_CURRENT); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1, UserHandle.USER_CURRENT); // Once from setup + twice from this function verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged(); @@ -297,14 +297,14 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0, UserHandle.USER_CURRENT); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isAllowedOnLockScreen()).isTrue(); assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); - mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1", + mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1, UserHandle.USER_CURRENT); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); 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 a13b8647e170..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 @@ -24,6 +24,7 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -126,7 +127,7 @@ class BundleEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) fun isBubble() { - assertThat(underTest.isBubbleCapable).isFalse() + assertThat(underTest.isBubble).isFalse() } @Test @@ -152,4 +153,10 @@ class BundleEntryAdapterTest : SysuiTestCase() { fun canShowFullScreen() { assertThat(underTest.isFullScreenCapable()).isFalse() } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getPeopleNotificationType() { + assertThat(underTest.getPeopleNotificationType()).isEqualTo(TYPE_NON_PERSON) + } } 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 faafa073be4c..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 @@ -30,6 +30,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.res.R import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.notification.mockNotificationActivityStarter +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.entryAdapterFactory import com.android.systemui.statusbar.notification.row.mockNotificationActionClickManager @@ -41,6 +42,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito +import org.mockito.Mockito.mock import org.mockito.Mockito.verify @SmallTest @@ -108,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() @@ -126,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) @@ -173,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() @@ -191,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() @@ -211,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() @@ -256,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, "") @@ -282,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 @@ -334,11 +336,21 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getPeopleNotificationType() { + val entry = kosmos.msgStyleBubbleableFullPerson + + underTest = factory.create(entry) as NotificationEntryAdapter + + assertThat(underTest.peopleNotificationType).isEqualTo(TYPE_FULL_PERSON) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) fun canShowFullScreen() { 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 = @@ -353,6 +365,22 @@ class NotificationEntryAdapterTest : SysuiTestCase() { @Test @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun onDragSuccess() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .addAction(mock(Notification.Action::class.java)) + .build() + val entry = NotificationEntryBuilder().setNotification(notification).build() + + underTest = factory.create(entry) as NotificationEntryAdapter + + underTest.onDragSuccess() + verify(kosmos.mockNotificationActivityStarter).onDragSuccess(entry) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) fun onNotificationBubbleIconClicked() { val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() @@ -371,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() @@ -380,4 +408,36 @@ class NotificationEntryAdapterTest : SysuiTestCase() { underTest.onNotificationActionClicked() verify(kosmos.mockNotificationActionClickManager).onNotificationActionClicked(entry) } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun getDismissState() { + val notification: Notification = + Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + entry.dismissState = NotificationEntry.DismissState.PARENT_DISMISSED + + underTest = factory.create(entry) as NotificationEntryAdapter + + assertThat(underTest.dismissState).isEqualTo(entry.dismissState) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun onEntryClicked() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .addAction(mock(Notification.Action::class.java)) + .build() + val entry = NotificationEntryBuilder().setNotification(notification).build() + val row = mock(ExpandableNotificationRow::class.java) + + underTest = factory.create(entry) as NotificationEntryAdapter + + + underTest.onEntryClicked(row) + verify(kosmos.mockNotificationActivityStarter).onNotificationClicked(entry, row) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt index 7fa157fa7cb3..ba2d40ba6a0d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt @@ -27,7 +27,6 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest @@ -35,10 +34,10 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.core.StatusBarRootModernization -import com.android.systemui.statusbar.notification.buildNotificationEntry -import com.android.systemui.statusbar.notification.buildOngoingCallEntry -import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry import com.android.systemui.statusbar.notification.collection.buildEntry +import com.android.systemui.statusbar.notification.collection.buildNotificationEntry +import com.android.systemui.statusbar.notification.collection.buildOngoingCallEntry +import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.notifPipeline @@ -49,7 +48,6 @@ import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernizat import com.android.systemui.testKosmos import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before 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/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt index 6926677feda0..6192399c522b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt @@ -28,9 +28,9 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.core.StatusBarRootModernization -import com.android.systemui.statusbar.notification.buildNotificationEntry -import com.android.systemui.statusbar.notification.buildOngoingCallEntry -import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry +import com.android.systemui.statusbar.notification.collection.buildNotificationEntry +import com.android.systemui.statusbar.notification.collection.buildOngoingCallEntry +import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization 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/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index 9fdfca14a5b2..95366568a37a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.platform.test.annotations.DisableFlags; import android.provider.Settings; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -38,7 +39,10 @@ import android.view.ViewGroup; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; @@ -54,6 +58,7 @@ import org.mockito.Mockito; @SmallTest public class NotificationMenuRowTest extends LeakCheckedTest { + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); private ExpandableNotificationRow mRow; private View mView; private PeopleNotificationIdentifier mPeopleNotificationIdentifier; @@ -66,6 +71,8 @@ public class NotificationMenuRowTest extends LeakCheckedTest { mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class); NotificationEntry entry = new NotificationEntryBuilder().build(); when(mRow.getEntry()).thenReturn(entry); + EntryAdapter entryAdapter = mKosmos.getEntryAdapterFactory().create(entry); + when(mRow.getEntryAdapter()).thenReturn(entryAdapter); } @Test @@ -413,6 +420,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { assertTrue("when alpha is .5, menu is visible", row.isMenuVisible()); } + @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES) @Test public void testOnTouchMove() { NotificationMenuRow row = Mockito.spy( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt index ccc8be7de038..6c6ba933c03a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt @@ -130,7 +130,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled setTargets() @@ -150,7 +152,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled canRowBeDismissed = false @@ -172,7 +176,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled setTargets() @@ -192,7 +198,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled canRowBeDismissed = false @@ -294,6 +302,29 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { assertThat(underTest.isSwipedViewRoundableSet).isFalse() } + @Test + fun isMagneticRowDismissible_isDismissibleWhenDetached() = + kosmos.testScope.runTest { + setDetachedState() + + val isDismissible = underTest.isMagneticRowSwipeDetached(swipedRow) + assertThat(isDismissible).isTrue() + } + + @Test + fun setMagneticRowTranslation_whenDetached_belowAttachThreshold_reattaches() = + kosmos.testScope.runTest { + // GIVEN that the swiped view has been detached + setDetachedState() + + // WHEN setting a new translation above the attach threshold + val translation = 50f + underTest.setMagneticRowTranslation(swipedRow, translation) + + // THEN the swiped view reattaches magnetically and the state becomes PULLING + assertThat(underTest.currentState).isEqualTo(State.PULLING) + } + @After fun tearDown() { // We reset the manager so that all MagneticRowListener can cancel all animations @@ -302,7 +333,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { private fun setDetachedState() { val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // Set the pulling state setTargets() @@ -327,8 +360,8 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener { val delegate = this return object : MagneticRowListener { - override fun setMagneticTranslation(translation: Float) { - delegate.setMagneticTranslation(translation) + override fun setMagneticTranslation(translation: Float, trackEagerly: Boolean) { + delegate.setMagneticTranslation(translation, trackEagerly) } override fun triggerMagneticForce( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 789701f5e4b0..de48f4018989 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -49,6 +49,7 @@ import android.view.ViewConfiguration; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.flags.FakeFeatureFlags; @@ -362,6 +363,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { verify(mSwipeHelper, times(1)).isFalseGesture(); } + @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES) @Test public void testIsDismissGesture_farEnough() { doReturn(false).when(mSwipeHelper).isFalseGesture(); @@ -374,6 +376,20 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { verify(mSwipeHelper, times(1)).isFalseGesture(); } + @EnableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES) + @Test + public void testIsDismissGesture_magneticSwipeIsDismissible() { + doReturn(false).when(mSwipeHelper).isFalseGesture(); + doReturn(false).when(mSwipeHelper).swipedFarEnough(); + doReturn(false).when(mSwipeHelper).swipedFastEnough(); + doReturn(true).when(mCallback).isMagneticViewDetached(any()); + when(mCallback.canChildBeDismissedInDirection(any(), anyBoolean())).thenReturn(true); + when(mEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_UP); + + assertTrue("Should be a dismissal", mSwipeHelper.isDismissGesture(mEvent)); + verify(mSwipeHelper, times(1)).isFalseGesture(); + } + @Test public void testIsDismissGesture_notFarOrFastEnough() { doReturn(false).when(mSwipeHelper).isFalseGesture(); diff --git a/packages/SystemUI/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/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt index 46430afecbb1..1f37291efbab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt @@ -790,6 +790,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) fun animateToGlanceableHub_affectsAlpha() = testScope.runTest { try { @@ -809,6 +810,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() = testScope.runTest { try { 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/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index bafa8cf05a7f..da5622a2a8da 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -17,8 +17,10 @@ package com.android.systemui.user.data.repository +import android.app.admin.DevicePolicyManager import android.app.admin.devicePolicyManager import android.content.Intent +import android.content.applicationContext import android.content.pm.UserInfo import android.internal.statusbar.fakeStatusBarService import android.os.UserHandle @@ -77,10 +79,7 @@ class UserRepositoryImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) tracker = FakeUserTracker() - context.orCreateTestableResources.addOverride( - R.bool.config_userSwitchingMustGoThroughLoginScreen, - false, - ) + setUserSwitchingMustGoThroughLoginScreen(false) } @Test @@ -308,6 +307,117 @@ class UserRepositoryImplTest : SysuiTestCase() { job.cancel() } + @Test + fun isSecondaryUserLogoutEnabled_secondaryLogoutDisabled_alwaysFalse() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT) + setSecondaryUserLogoutEnabled(false) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val secondaryUserLogoutEnabled by + collectLastValue(underTest.isSecondaryUserLogoutEnabled) + + assertThat(secondaryUserLogoutEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(secondaryUserLogoutEnabled).isFalse() + } + + @Test + fun isSecondaryUserLogoutEnabled_secondaryLogoutEnabled_NullLogoutUser_alwaysFalse() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NONE) + setSecondaryUserLogoutEnabled(true) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val secondaryUserLogoutEnabled by + collectLastValue(underTest.isSecondaryUserLogoutEnabled) + + assertThat(secondaryUserLogoutEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(secondaryUserLogoutEnabled).isFalse() + } + + @Test + fun isSecondaryUserLogoutEnabled_secondaryLogoutEnabled_NonSystemLogoutUser_trueWhenNonSystem() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT) + setSecondaryUserLogoutEnabled(true) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val secondaryUserLogoutEnabled by + collectLastValue(underTest.isSecondaryUserLogoutEnabled) + + assertThat(secondaryUserLogoutEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(secondaryUserLogoutEnabled).isTrue() + } + + @Test + fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenDisabled_alwaysFalse() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT) + setUserSwitchingMustGoThroughLoginScreen(false) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled) + + assertThat(logoutToSystemUserEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(logoutToSystemUserEnabled).isFalse() + } + + @Test + fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenEnabled_NullLogoutUser_alwaysFalse() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NONE) + setUserSwitchingMustGoThroughLoginScreen(true) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled) + + assertThat(logoutToSystemUserEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(logoutToSystemUserEnabled).isFalse() + } + + @Test + fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenEnabled_NonSystemLogoutUser_trueWhenNonSystem() = + testScope.runTest { + underTest = create(testScope.backgroundScope) + mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT) + setUserSwitchingMustGoThroughLoginScreen(true) + setUpUsers(count = 2, selectedIndex = 0) + tracker.onProfileChanged() + + val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled) + + assertThat(logoutToSystemUserEnabled).isFalse() + + setUpUsers(count = 2, selectedIndex = 1) + tracker.onProfileChanged() + assertThat(logoutToSystemUserEnabled).isTrue() + } + private fun createUserInfo(id: Int, isGuest: Boolean): UserInfo { val flags = 0 return UserInfo( @@ -354,6 +464,38 @@ class UserRepositoryImplTest : SysuiTestCase() { assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled) } + private fun setSecondaryUserLogoutEnabled(enabled: Boolean) { + whenever(devicePolicyManager.isLogoutEnabled).thenReturn(enabled) + broadcastDispatcher.sendIntentToMatchingReceiversOnly( + kosmos.applicationContext, + Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + ) + } + + private fun setUserSwitchingMustGoThroughLoginScreen(enabled: Boolean) { + context.orCreateTestableResources.addOverride( + R.bool.config_userSwitchingMustGoThroughLoginScreen, + enabled, + ) + } + + private fun mockLogoutUser(result: LogoutUserResult) { + when (result) { + LogoutUserResult.NONE -> { + whenever(devicePolicyManager.logoutUser).thenReturn(null) + } + LogoutUserResult.NON_SYSTEM_CURRENT -> { + whenever(devicePolicyManager.logoutUser).thenAnswer { + if (tracker.userHandle != UserHandle.SYSTEM) { + tracker.userHandle + } else { + null + } + } + } + } + } + private fun create(scope: CoroutineScope): UserRepositoryImpl { return UserRepositoryImpl( appContext = context, @@ -373,4 +515,9 @@ class UserRepositoryImplTest : SysuiTestCase() { companion object { @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate } + + private enum class LogoutUserResult { + NONE, + NON_SYSTEM_CURRENT, + } } 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/color/brightness_slider_track.xml b/packages/SystemUI/res/color/brightness_slider_track.xml new file mode 100644 index 000000000000..c6e9b65fb7e8 --- /dev/null +++ b/packages/SystemUI/res/color/brightness_slider_track.xml @@ -0,0 +1,19 @@ +<?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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_500" android:lStar="40" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml index 88d3ecb3f423..d38da7b766c1 100644 --- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml +++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml @@ -25,7 +25,7 @@ <shape> <size android:height="@dimen/rounded_slider_track_width" /> <corners android:radius="@dimen/rounded_slider_track_corner_radius" /> - <solid android:color="@androidprv:color/customColorShadeInactive" /> + <solid android:color="@color/brightness_slider_track" /> </shape> </inset> </item> 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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 089466707298..d017754ae653 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -778,18 +778,26 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { protected boolean swipedFarEnough() { float translation = getTranslation(mTouchedView); - return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize( - mTouchedView); + return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mTouchedView); } public boolean isDismissGesture(MotionEvent ev) { float translation = getTranslation(mTouchedView); return ev.getActionMasked() == MotionEvent.ACTION_UP && !mFalsingManager.isUnlockingDisabled() - && !isFalseGesture() && (swipedFastEnough() || swipedFarEnough()) + && !isFalseGesture() && isSwipeDismissible() && mCallback.canChildBeDismissedInDirection(mTouchedView, translation > 0); } + /** Can the swipe gesture on the touched view be considered as a dismiss intention */ + public boolean isSwipeDismissible() { + if (magneticNotificationSwipes()) { + return mCallback.isMagneticViewDetached(mTouchedView) || swipedFastEnough(); + } else { + return swipedFastEnough() || swipedFarEnough(); + } + } + /** Returns true if the gesture should be rejected. */ public boolean isFalseGesture() { boolean falsingDetected = mCallback.isAntiFalsingNeeded(); @@ -970,6 +978,13 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { void onMagneticInteractionEnd(View view, float velocity); /** + * Determine if a view managed by magnetic interactions is magnetically detached + * @param view The magnetic view + * @return if the view is detached according to its magnetic state. + */ + boolean isMagneticViewDetached(View view); + + /** * Called when the child is long pressed and available to start drag and drop. * * @param v the view that was long pressed. diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/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/domain/interactor/SysUIStateDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt index 9db7b50905f8..1301fb87069e 100644 --- a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt @@ -17,9 +17,9 @@ package com.android.systemui.common.domain.interactor import android.util.Log +import com.android.app.displaylib.PerDisplayRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.display.data.repository.DisplayRepository -import com.android.systemui.display.data.repository.PerDisplayRepository import com.android.systemui.model.StateChange import com.android.systemui.model.SysUiState import javax.inject.Inject 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/DeviceInactiveCondition.kt b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.kt index 11c285b3e4e9..70dce2ef0cac 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.kt @@ -18,6 +18,7 @@ package com.android.systemui.communal import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff @@ -28,6 +29,7 @@ import com.android.systemui.util.kotlin.JavaAdapter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.launch /** * Condition which estimates device inactivity in order to avoid launching a full-screen activity @@ -36,13 +38,14 @@ import kotlinx.coroutines.Job class DeviceInactiveCondition @Inject constructor( - @Application scope: CoroutineScope, + @Application private val applicationScope: CoroutineScope, + @Background backgroundScope: CoroutineScope, private val keyguardStateController: KeyguardStateController, private val wakefulnessLifecycle: WakefulnessLifecycle, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val keyguardInteractor: KeyguardInteractor, private val javaAdapter: JavaAdapter, -) : Condition(scope) { +) : Condition(backgroundScope) { private var anyDozeListenerJob: Job? = null private var anyDoze = false private val keyguardStateCallback: KeyguardStateController.Callback = @@ -67,7 +70,9 @@ constructor( override suspend fun start() { updateState() keyguardStateController.addCallback(keyguardStateCallback) - keyguardUpdateMonitor.registerCallback(keyguardUpdateCallback) + + // Keyguard update monitor callbacks must be registered on the main thread + applicationScope.launch { keyguardUpdateMonitor.registerCallback(keyguardUpdateCallback) } wakefulnessLifecycle.addObserver(wakefulnessObserver) anyDozeListenerJob = javaAdapter.alwaysCollectFlow(keyguardInteractor.dozeTransitionModel) { 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/dagger/PerDisplayRepositoriesModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt index 39708a743c23..3520439fda88 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt @@ -16,9 +16,9 @@ package com.android.systemui.dagger -import com.android.systemui.display.data.repository.DefaultDisplayOnlyInstanceRepositoryImpl -import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl -import com.android.systemui.display.data.repository.PerDisplayRepository +import com.android.app.displaylib.DefaultDisplayOnlyInstanceRepositoryImpl +import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl +import com.android.app.displaylib.PerDisplayRepository import com.android.systemui.model.SysUIStateInstanceProvider import com.android.systemui.model.SysUiState import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index c201fbf0051b..edee64e50b53 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -65,7 +65,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.dagger.DemoModeModule; import com.android.systemui.deviceentry.DeviceEntryModule; import com.android.systemui.display.DisplayModule; -import com.android.systemui.display.data.repository.PerDisplayRepository; +import com.android.app.displaylib.PerDisplayRepository; import com.android.systemui.doze.dagger.DozeComponent; import com.android.systemui.dreams.dagger.DreamModule; import com.android.systemui.flags.FeatureFlags; diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt index f3316958f01d..908d0aafb2b9 100644 --- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt +++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt @@ -18,7 +18,9 @@ package com.android.systemui.display import android.hardware.display.DisplayManager import android.os.Handler +import com.android.app.displaylib.DisplayLibBackground import com.android.app.displaylib.DisplayLibComponent +import com.android.app.displaylib.PerDisplayRepository import com.android.app.displaylib.createDisplayLibComponent import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton @@ -34,7 +36,6 @@ import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepos import com.android.systemui.display.data.repository.FocusedDisplayRepository import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl import com.android.systemui.display.data.repository.PerDisplayRepoDumpHelper -import com.android.systemui.display.data.repository.PerDisplayRepository import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractorModule @@ -85,6 +86,10 @@ interface DisplayModule { @Binds fun dumpRegistrationLambda(helper: PerDisplayRepoDumpHelper): PerDisplayRepository.InitCallback + @Binds + @DisplayLibBackground + fun bindDisplayLibBackground(@Background bgScope: CoroutineScope): CoroutineScope + companion object { @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt index a56710ee3772..86c9d84c27b1 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt @@ -16,6 +16,9 @@ package com.android.systemui.display.data.repository +import com.android.app.displaylib.PerDisplayRepository + +// TODO b/401305290 - move to displaylib class FakePerDisplayRepository<T> : PerDisplayRepository<T> { private val instances = mutableMapOf<Int, T>() diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt index 212d55612935..efbae5d04caf 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt @@ -16,6 +16,7 @@ package com.android.systemui.display.data.repository +import com.android.app.displaylib.PerDisplayRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpableFromToString diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt deleted file mode 100644 index 7e00c60dc43a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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.systemui.display.data.repository - -import android.util.Log -import android.view.Display -import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.app.tracing.traceSection -import com.android.systemui.dagger.qualifiers.Background -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import java.util.concurrent.ConcurrentHashMap -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collectLatest - -/** - * Used to create instances of type `T` for a specific display. - * - * This is useful for resources or objects that need to be managed independently for each connected - * display (e.g., UI state, rendering contexts, or display-specific configurations). - * - * Note that in most cases this can be implemented by a simple `@AssistedFactory` with `displayId` - * parameter - * - * ```kotlin - * class SomeType @AssistedInject constructor(@Assisted displayId: Int,..) - * @AssistedFactory - * interface Factory { - * fun create(displayId: Int): SomeType - * } - * } - * ``` - * - * Then it can be used to create a [PerDisplayRepository] as follows: - * ```kotlin - * // Injected: - * val repositoryFactory: PerDisplayRepositoryImpl.Factory - * val instanceFactory: PerDisplayRepositoryImpl.Factory - * // repository creation: - * repositoryFactory.create(instanceFactory::create) - * ``` - * - * @see PerDisplayRepository For how to retrieve and manage instances created by this factory. - */ -fun interface PerDisplayInstanceProvider<T> { - /** Creates an instance for a display. */ - fun createInstance(displayId: Int): T? -} - -/** - * Extends [PerDisplayInstanceProvider], adding support for destroying the instance. - * - * This is useful for releasing resources associated with a display when it is disconnected or when - * the per-display instance is no longer needed. - */ -interface PerDisplayInstanceProviderWithTeardown<T> : PerDisplayInstanceProvider<T> { - /** Destroys a previously created instance of `T` forever. */ - fun destroyInstance(instance: T) -} - -/** - * Provides access to per-display instances of type `T`. - * - * Acts as a repository, managing the caching and retrieval of instances created by a - * [PerDisplayInstanceProvider]. It ensures that only one instance of `T` exists per display ID. - */ -interface PerDisplayRepository<T> { - /** Gets the cached instance or create a new one for a given display. */ - operator fun get(displayId: Int): T? - - /** Debug name for this repository, mainly for tracing and logging. */ - val debugName: String - - /** - * Callback to run when a given repository is initialized. - * - * This allows the caller to perform custom logic when the repository is ready to be used, e.g. - * register to dumpManager. - * - * Note that the instance is *leaked* outside of this class, so it should only be done when - * repository is meant to live as long as the caller. In systemUI this is ok because the - * repository lives as long as the process itself. - */ - interface InitCallback { - fun onInit(debugName: String, instance: Any) - } -} - -/** - * Default implementation of [PerDisplayRepository]. - * - * This class manages a cache of per-display instances of type `T`, creating them using a provided - * [PerDisplayInstanceProvider] and optionally tearing them down using a - * [PerDisplayInstanceProviderWithTeardown] when displays are disconnected. - * - * It listens to the [DisplayRepository] to detect when displays are added or removed, and - * automatically manages the lifecycle of the per-display instances. - * - * Note that this is a [PerDisplayStoreImpl] 2.0 that doesn't require [CoreStartable] bindings, - * providing all args in the constructor. - */ -class PerDisplayInstanceRepositoryImpl<T> -@AssistedInject -constructor( - @Assisted override val debugName: String, - @Assisted private val instanceProvider: PerDisplayInstanceProvider<T>, - @Background private val backgroundApplicationScope: CoroutineScope, - private val displayRepository: DisplayRepository, - private val initCallback: PerDisplayRepository.InitCallback, -) : PerDisplayRepository<T> { - - private val perDisplayInstances = ConcurrentHashMap<Int, T?>() - - init { - backgroundApplicationScope.launch("$debugName#start") { start() } - } - - private suspend fun start() { - initCallback.onInit(debugName, this) - displayRepository.displayIds.collectLatest { displayIds -> - val toRemove = perDisplayInstances.keys - displayIds - toRemove.forEach { displayId -> - Log.d(TAG, "<$debugName> destroying instance for displayId=$displayId.") - perDisplayInstances.remove(displayId)?.let { instance -> - (instanceProvider as? PerDisplayInstanceProviderWithTeardown)?.destroyInstance( - instance - ) - } - } - } - } - - override fun get(displayId: Int): T? { - if (displayRepository.getDisplay(displayId) == null) { - Log.e(TAG, "<$debugName: Display with id $displayId doesn't exist.") - return null - } - - // If it doesn't exist, create it and put it in the map. - return perDisplayInstances.computeIfAbsent(displayId) { key -> - Log.d(TAG, "<$debugName> creating instance for displayId=$key, as it wasn't available.") - val instance = - traceSection({ "creating instance of $debugName for displayId=$key" }) { - instanceProvider.createInstance(key) - } - if (instance == null) { - Log.e( - TAG, - "<$debugName> returning null because createInstance($key) returned null.", - ) - } - instance - } - } - - @AssistedFactory - interface Factory<T> { - fun create( - debugName: String, - instanceProvider: PerDisplayInstanceProvider<T>, - ): PerDisplayInstanceRepositoryImpl<T> - } - - companion object { - private const val TAG = "PerDisplayInstanceRepo" - } - - override fun toString(): String { - return "PerDisplayInstanceRepositoryImpl(" + - "debugName='$debugName', instances=$perDisplayInstances)" - } -} - -/** - * Provides an instance of a given class **only** for the default display, even if asked for another - * display. - * - * This is useful in case of **flag refactors**: it can be provided instead of an instance of - * [PerDisplayInstanceRepositoryImpl] when a flag related to multi display refactoring is off. - * - * Note that this still requires all instances to be provided by a [PerDisplayInstanceProvider]. If - * you want to provide an existing instance instead for the default display, either implement it in - * a custom [PerDisplayInstanceProvider] (e.g. inject it in the constructor and return it if the - * displayId is zero), or use [SingleInstanceRepositoryImpl]. - */ -class DefaultDisplayOnlyInstanceRepositoryImpl<T>( - override val debugName: String, - private val instanceProvider: PerDisplayInstanceProvider<T>, -) : PerDisplayRepository<T> { - private val lazyDefaultDisplayInstance by lazy { - instanceProvider.createInstance(Display.DEFAULT_DISPLAY) - } - - override fun get(displayId: Int): T? = lazyDefaultDisplayInstance -} - -/** - * Always returns [instance] for any display. - * - * This can be used to provide a single instance based on a flag value during a refactor. Similar to - * [DefaultDisplayOnlyInstanceRepositoryImpl], but also avoids creating the - * [PerDisplayInstanceProvider]. This is useful when you want to provide an existing instance only, - * without even instantiating a [PerDisplayInstanceProvider]. - */ -class SingleInstanceRepositoryImpl<T>(override val debugName: String, private val instance: T) : - PerDisplayRepository<T> { - override fun get(displayId: Int): T? = instance -} 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/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt index 0b116ded42da..438dff9cb476 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt @@ -139,7 +139,7 @@ constructor( fun setWallpaperSupportsAmbientMode(supportsAmbientMode: Boolean) { repository.maxAlpha.value = if (supportsAmbientMode) { - 0.7f + 0.54f } else { 1f } 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/lowlightclock/DirectBootCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt index cdac61cea10b..ce5fd195d151 100644 --- a/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt @@ -20,7 +20,7 @@ import android.content.Intent import android.content.IntentFilter import android.os.UserManager import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.shared.condition.Condition import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -35,7 +35,7 @@ class DirectBootCondition constructor( broadcastDispatcher: BroadcastDispatcher, private val userManager: UserManager, - @Application private val coroutineScope: CoroutineScope, + @Background private val coroutineScope: CoroutineScope, ) : Condition(coroutineScope) { private var job: Job? = null private val directBootFlow = diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.kt index f75894da2c85..85a94306d0ea 100644 --- a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.kt +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.kt @@ -17,7 +17,7 @@ package com.android.systemui.lowlightclock import android.text.TextUtils import android.util.Log -import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.shared.condition.Condition import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry @@ -31,7 +31,7 @@ import kotlinx.coroutines.CoroutineScope */ class ForceLowLightCondition @Inject -constructor(@Application scope: CoroutineScope, commandRegistry: CommandRegistry) : +constructor(@Background scope: CoroutineScope, commandRegistry: CommandRegistry) : Condition(scope, null, true) { /** * Default Constructor. diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.kt index a29c666ff792..34e9a6351180 100644 --- a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.kt +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.kt @@ -16,7 +16,7 @@ package com.android.systemui.lowlightclock import com.android.internal.logging.UiEventLogger -import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.shared.condition.Condition import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -25,7 +25,7 @@ import kotlinx.coroutines.CoroutineScope class LowLightCondition @Inject constructor( - @Application scope: CoroutineScope, + @Background scope: CoroutineScope, private val ambientLightModeMonitor: AmbientLightModeMonitor, private val uiEventLogger: UiEventLogger, ) : Condition(scope) { diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.kt index f6b0f6276440..e761cc0692f7 100644 --- a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.kt +++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.kt @@ -21,7 +21,7 @@ import android.os.UserHandle import android.provider.Settings import android.util.Log import com.android.internal.R -import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.shared.condition.Condition import com.android.systemui.util.settings.SecureSettings @@ -32,7 +32,7 @@ import kotlinx.coroutines.CoroutineScope class ScreenSaverEnabledCondition @Inject constructor( - @Application scope: CoroutineScope, + @Background scope: CoroutineScope, @Main resources: Resources, private val secureSettings: SecureSettings, ) : Condition(scope) { 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/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt index 71cb74543485..68cd80798c4b 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt @@ -17,9 +17,9 @@ package com.android.systemui.model import android.util.Log import android.view.Display +import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.display.data.repository.PerDisplayInstanceProviderWithTeardown import com.android.systemui.dump.DumpManager import com.android.systemui.model.SysUiState.SysUiStateCallback import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java index 58ddbf60e8fb..39482bea0111 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java @@ -24,10 +24,10 @@ import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; +import com.android.app.displaylib.PerDisplayRepository; import com.android.app.viewcapture.ViewCapture; import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.systemui.dagger.qualifiers.DisplayId; -import com.android.systemui.display.data.repository.PerDisplayRepository; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; import com.android.systemui.navigationbar.views.NavigationBarFrame; diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index d20b360756d7..728652e6e5c4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -82,6 +82,7 @@ fun ContentScope.QuickQuickSettings( viewModel.tileHapticsViewModelFactoryProvider, // There should be no QuickQuickSettings when the details view is enabled. detailsViewModel = null, + isVisible = listening, ) } } 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 50012abc69d6..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,12 +39,14 @@ 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 import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -102,6 +104,7 @@ fun LargeTileContent( sideDrawable: Drawable?, colors: TileColors, squishiness: () -> Float, + isVisible: () -> Boolean = { true }, accessibilityUiState: AccessibilityUiState? = null, iconShape: RoundedCornerShape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius), toggleClick: (() -> Unit)? = null, @@ -158,6 +161,7 @@ fun LargeTileContent( secondaryLabel = secondaryLabel, colors = colors, accessibilityUiState = accessibilityUiState, + isVisible = isVisible, ) if (sideDrawable != null) { @@ -170,12 +174,14 @@ fun LargeTileContent( } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun LargeTileLabels( label: String, secondaryLabel: String?, colors: TileColors, modifier: Modifier = Modifier, + isVisible: () -> Boolean = { true }, accessibilityUiState: AccessibilityUiState? = null, ) { val animatedLabelColor by animateColorAsState(colors.label, label = "QSTileLabelColor") @@ -184,14 +190,16 @@ fun LargeTileLabels( Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) { TileLabel( text = label, - style = MaterialTheme.typography.labelLarge, + style = MaterialTheme.typography.titleSmallEmphasized, color = { animatedLabelColor }, + isVisible = isVisible, ) if (!TextUtils.isEmpty(secondaryLabel)) { TileLabel( secondaryLabel ?: "", color = { animatedSecondaryLabelColor }, - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.labelMedium, + isVisible = isVisible, modifier = Modifier.thenIf( accessibilityUiState?.stateDescription?.contains(secondaryLabel ?: "") == @@ -277,36 +285,50 @@ private fun TileLabel( color: ColorProducer, style: TextStyle, modifier: Modifier = Modifier, + isVisible: () -> Boolean = { true }, ) { + var textSize by remember { mutableIntStateOf(0) } + BasicText( text = text, color = color, style = style, maxLines = 1, + onTextLayout = { textSize = it.size.width }, modifier = modifier .fillMaxWidth() - .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } + .graphicsLayer { + if (textSize > size.width) { + compositingStrategy = CompositingStrategy.Offscreen + } + } .drawWithContent { drawContent() - // Draw a blur over the end of the text - val edgeWidthPx = TileLabelBlurWidth.toPx() - drawRect( - topLeft = Offset(size.width - edgeWidthPx, 0f), - size = Size(edgeWidthPx, size.height), - brush = - Brush.horizontalGradient( - colors = listOf(Color.Transparent, Color.Black), - startX = size.width, - endX = size.width - edgeWidthPx, - ), - blendMode = BlendMode.DstIn, - ) + if (textSize > size.width) { + // Draw a blur over the end of the text + val edgeWidthPx = TileLabelBlurWidth.toPx() + drawRect( + topLeft = Offset(size.width - edgeWidthPx, 0f), + size = Size(edgeWidthPx, size.height), + brush = + Brush.horizontalGradient( + colors = listOf(Color.Transparent, Color.Black), + startX = size.width, + endX = size.width - edgeWidthPx, + ), + blendMode = BlendMode.DstIn, + ) + } } - .basicMarquee( - iterations = TILE_MARQUEE_ITERATIONS, - initialDelayMillis = TILE_INITIAL_DELAY_MILLIS, - ), + .thenIf(isVisible()) { + // Only apply the marquee when the label is visible, which is needed for the + // always composed QS + Modifier.basicMarquee( + iterations = TILE_MARQUEE_ITERATIONS, + initialDelayMillis = TILE_INITIAL_DELAY_MILLIS, + ) + }, ) } 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/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 27e609232a4c..984343a45797 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -119,6 +119,7 @@ constructor( isLastInRow = isLastInColumn, ), detailsViewModel = detailsViewModel, + isVisible = listening, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index 6bafd432669a..e24718285312 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -139,6 +139,7 @@ fun Tile( bounceableInfo: BounceableInfo, tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider, modifier: Modifier = Modifier, + isVisible: () -> Boolean = { true }, detailsViewModel: DetailsViewModel?, ) { trace(tile.traceName) { @@ -249,6 +250,7 @@ fun Tile( onLongClick = longClick, accessibilityUiState = uiState.accessibilityUiState, squishiness = squishiness, + isVisible = isVisible, ) } } 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/recents/LauncherProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java index d2639654d206..5b9717535ab5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java @@ -78,6 +78,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.annotation.NonNull; +import com.android.app.displaylib.PerDisplayRepository; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AssistUtils; import com.android.internal.app.IVoiceInteractionSessionListener; @@ -90,7 +91,6 @@ import com.android.systemui.contextualeducation.GestureType; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.display.data.repository.DisplayRepository; -import com.android.systemui.display.data.repository.PerDisplayRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardWmStateRefactor; @@ -126,8 +126,6 @@ import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInterface; -import dagger.Lazy; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -139,6 +137,8 @@ import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Provider; +import dagger.Lazy; + /** * Class to send information from SysUI to Launcher with a binder. */ 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/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt index 3168a22c56ad..cb1002a83179 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt @@ -17,14 +17,14 @@ package com.android.systemui.statusbar.data.repository import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR +import com.android.app.displaylib.PerDisplayInstanceProvider +import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl +import com.android.app.displaylib.PerDisplayRepository +import com.android.app.displaylib.SingleInstanceRepositoryImpl import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.ConfigurationStateImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository -import com.android.systemui.display.data.repository.PerDisplayInstanceProvider -import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl -import com.android.systemui.display.data.repository.PerDisplayRepository -import com.android.systemui.display.data.repository.SingleInstanceRepositoryImpl import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import dagger.Lazy import dagger.Module @@ -39,7 +39,6 @@ constructor( private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore, private val factory: ConfigurationStateImpl.Factory, ) : PerDisplayInstanceProvider<ConfigurationState> { - override fun createInstance(displayId: Int): ConfigurationState? { val displayWindowProperties = displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null 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 fcdcc3f698de..fed9417edd88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -23,6 +23,7 @@ import android.view.View; import com.android.systemui.DejankUtils; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; @@ -44,13 +45,20 @@ public final class NotificationClicker implements View.OnClickListener { private final Optional<Bubbles> mBubblesOptional; private final NotificationActivityStarter mNotificationActivityStarter; - private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener = - new ExpandableNotificationRow.OnDragSuccessListener() { - @Override - public void onDragSuccess(NotificationEntry entry) { - mNotificationActivityStarter.onDragSuccess(entry); - } - }; + private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener + = new ExpandableNotificationRow.OnDragSuccessListener() { + @Override + public void onDragSuccess(NotificationEntry entry) { + NotificationBundleUi.assertInLegacyMode(); + mNotificationActivityStarter.onDragSuccess(entry); + } + + @Override + public void onDragSuccess(EntryAdapter entryAdapter) { + NotificationBundleUi.isUnexpectedlyInLegacyMode(); + entryAdapter.onDragSuccess(); + } + }; private NotificationClicker( NotificationClickerLogger logger, @@ -73,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 @@ -101,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) { @@ -121,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 b1a26af336d8..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 @@ -22,6 +22,7 @@ import android.os.Build import android.service.notification.StatusBarNotification import android.util.Log import com.android.systemui.statusbar.notification.icon.IconPack +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import kotlinx.coroutines.flow.StateFlow @@ -97,7 +98,7 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { return false } - override fun isBubbleCapable(): Boolean { + override fun isBubble(): Boolean { return false } @@ -113,6 +114,10 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { return false } + override fun getPeopleNotificationType(): Int { + return TYPE_NON_PERSON + } + override fun isPromotedOngoing(): Boolean { return false } @@ -121,6 +126,11 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { return false } + override fun onDragSuccess() { + // do nothing. these should not be draggable + Log.wtf(TAG, "onDragSuccess() called") + } + override fun onNotificationBubbleIconClicked() { // do nothing. these cannot be a bubble Log.wtf(TAG, "onNotificationBubbleIconClicked() called") @@ -130,6 +140,16 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { // do nothing. these have no actions Log.wtf(TAG, "onNotificationActionClicked() called") } + + override fun getDismissState(): NotificationEntry.DismissState { + // TODO(b/394483200): setDismissState is only called in NotifCollection so it does not + // work on bundles yet + return NotificationEntry.DismissState.NOT_DISMISSED + } + + override fun onEntryClicked(row: ExpandableNotificationRow) { + // TODO(b/396446620): should anything happen when you click on a bundle? + } } private const val TAG = "BundleEntryAdapter" 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 4299825bd5e3..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 @@ -23,6 +23,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.statusbar.notification.icon.IconPack; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import kotlinx.coroutines.flow.StateFlow; @@ -124,7 +125,7 @@ public interface EntryAdapter { boolean canDragAndDrop(); - boolean isBubbleCapable(); + boolean isBubble(); @Nullable String getStyle(); @@ -132,6 +133,8 @@ public interface EntryAdapter { boolean isAmbient(); + @PeopleNotificationIdentifier.Companion.PeopleNotificationType int getPeopleNotificationType(); + /** * Returns whether this row represents promoted ongoing notification. */ @@ -141,6 +144,8 @@ public interface EntryAdapter { return false; } + void onDragSuccess(); + /** * Process a click on a notification bubble icon */ @@ -150,5 +155,9 @@ public interface EntryAdapter { * Processes a click on a notification action */ void onNotificationActionClicked(); + + NotificationEntry.DismissState getDismissState(); + + void onEntryClicked(ExpandableNotificationRow row); } 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 345b6aae9673..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 } @@ -137,6 +137,10 @@ class NotificationEntryAdapter( return entry.ranking.isAmbient } + override fun getPeopleNotificationType(): Int { + return peopleNotificationIdentifier.getPeopleNotificationType(entry) + } + override fun isPromotedOngoing(): Boolean { return entry.isPromotedOngoing } @@ -145,6 +149,10 @@ class NotificationEntryAdapter( return entry.sbn.notification.fullScreenIntent != null } + override fun onDragSuccess() { + notificationActivityStarter.onDragSuccess(entry) + } + override fun onNotificationBubbleIconClicked() { notificationActivityStarter.onNotificationBubbleIconClicked(entry) } @@ -152,4 +160,12 @@ class NotificationEntryAdapter( override fun onNotificationActionClicked() { notificationActionClickManager.onNotificationActionClicked(entry) } + + override fun getDismissState(): NotificationEntry.DismissState { + return entry.dismissState + } + + override fun onEntryClicked(row: ExpandableNotificationRow) { + notificationActivityStarter.onNotificationClicked(entry, row) + } } 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/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt index 147a5afea306..619d48f8ba81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import android.view.Display import androidx.lifecycle.lifecycleScope +import com.android.app.displaylib.PerDisplayRepository import com.android.app.tracing.traceSection import com.android.systemui.common.ui.ConfigurationState -import com.android.systemui.display.data.repository.PerDisplayRepository import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore 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 5160b909fd6a..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 @@ -22,9 +22,9 @@ import static android.service.notification.NotificationListenerService.REASON_CA import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_EXPANDED; import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; +import static com.android.systemui.Flags.notificationRowAccessibilityExpanded; import static com.android.systemui.Flags.notificationRowTransparency; import static com.android.systemui.Flags.notificationsPinnedHunInShade; -import static com.android.systemui.Flags.notificationRowAccessibilityExpanded; import static com.android.systemui.flags.Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; @@ -671,8 +671,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private boolean isConversation() { - return mPeopleNotificationIdentifier.getPeopleNotificationType(getEntry()) - != PeopleNotificationIdentifier.TYPE_NON_PERSON; + if (NotificationBundleUi.isEnabled()) { + return getEntryAdapter().getPeopleNotificationType() + != PeopleNotificationIdentifier.TYPE_NON_PERSON; + } else { + return mPeopleNotificationIdentifier.getPeopleNotificationType(getEntryLegacy()) + != PeopleNotificationIdentifier.TYPE_NON_PERSON; + } } public void onNotificationUpdated() { @@ -1786,7 +1791,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** @return true if the User has dismissed this notif's parent */ public boolean isParentDismissed() { - return getEntry().getDismissState() == PARENT_DISMISSED; + if (NotificationBundleUi.isEnabled()) { + return getEntryAdapter().getDismissState() == PARENT_DISMISSED; + } else { + return getEntryLegacy().getDismissState() == PARENT_DISMISSED; + } } @Override @@ -2511,7 +2520,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public void dragAndDropSuccess() { if (mOnDragSuccessListener != null) { - mOnDragSuccessListener.onDragSuccess(getEntry()); + if (NotificationBundleUi.isEnabled()) { + mOnDragSuccessListener.onDragSuccess(getEntryAdapter()); + } else { + mOnDragSuccessListener.onDragSuccess(getEntryLegacy()); + } } } @@ -3998,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; @@ -4412,6 +4426,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * @param entry NotificationEntry that succeed to drop on proper target window. */ void onDragSuccess(NotificationEntry entry); + + /** + * @param entryAdapter The EntryAdapter that successfully dropped on the proper + * target window + */ + void onDragSuccess(EntryAdapter entryAdapter); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 292f74a65554..f36a0cf51b97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.Flags.notificationColorUpdateLogger; import static com.android.systemui.Flags.physicalNotificationMovement; +import static java.lang.Math.abs; + import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Configuration; @@ -29,6 +31,7 @@ import android.util.FloatProperty; import android.util.IndentingPrintWriter; import android.util.Log; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.FrameLayout; @@ -110,14 +113,27 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro protected SpringAnimation mMagneticAnimator = new SpringAnimation( this /* object */, DynamicAnimation.TRANSLATION_X); + private int mTouchSlop; + protected MagneticRowListener mMagneticRowListener = new MagneticRowListener() { @Override - public void setMagneticTranslation(float translation) { - if (mMagneticAnimator.isRunning()) { - mMagneticAnimator.animateToFinalPosition(translation); - } else { + public void setMagneticTranslation(float translation, boolean trackEagerly) { + if (!mMagneticAnimator.isRunning()) { setTranslation(translation); + return; + } + + if (trackEagerly) { + float delta = abs(getTranslation() - translation); + if (delta > mTouchSlop) { + mMagneticAnimator.animateToFinalPosition(translation); + } else { + mMagneticAnimator.cancel(); + setTranslation(translation); + } + } else { + mMagneticAnimator.animateToFinalPosition(translation); } } @@ -183,6 +199,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro private void initDimens() { mContentShift = getResources().getDimensionPixelSize( R.dimen.shelf_transform_content_shift); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/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 26d318bea5cc..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 @@ -1604,12 +1604,15 @@ public class NotificationContentView extends FrameLayout implements Notification } if (shouldShowBubbleButton(entry)) { + boolean isBubble = NotificationBundleUi.isEnabled() + ? mContainingNotification.getEntryAdapter().isBubble() + : entry.isBubble(); // explicitly resolve drawable resource using SystemUI's theme - Drawable d = mContext.getDrawable(entry.isBubble() + Drawable d = mContext.getDrawable(isBubble ? com.android.wm.shell.R.drawable.bubble_ic_stop_bubble : com.android.wm.shell.R.drawable.bubble_ic_create_bubble); - String contentDescription = mContext.getResources().getString(entry.isBubble() + String contentDescription = mContext.getResources().getString(isBubble ? R.string.notification_conversation_unbubble : R.string.notification_conversation_bubble); @@ -1652,12 +1655,18 @@ public class NotificationContentView extends FrameLayout implements Notification @VisibleForTesting boolean shouldShowBubbleButton(NotificationEntry entry) { - boolean isPersonWithShortcut = - mPeopleIdentifier.getPeopleNotificationType(entry) - >= PeopleNotificationIdentifier.TYPE_FULL_PERSON; + int peopleType = NotificationBundleUi.isEnabled() + ? mContainingNotification.getEntryAdapter().getPeopleNotificationType() + : mPeopleIdentifier.getPeopleNotificationType(entry); + Notification.BubbleMetadata bubbleMetadata = NotificationBundleUi.isEnabled() + ? mContainingNotification.getEntryAdapter().getSbn().getNotification() + .getBubbleMetadata() + : entry.getBubbleMetadata(); + boolean isPersonWithShortcut = peopleType + >= PeopleNotificationIdentifier.TYPE_FULL_PERSON; return mBubblesEnabledForUser && isPersonWithShortcut - && entry.getBubbleMetadata() != null; + && bubbleMetadata != null; } private void applySnoozeAction(View layout) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index e89a76fd5a69..c03dc279888f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -46,9 +46,9 @@ import com.android.systemui.Flags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.res.R; import com.android.systemui.statusbar.AlphaOptimizedImageView; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import java.util.ArrayList; @@ -261,15 +261,19 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mSnoozeItem = createSnoozeItem(mContext); } mFeedbackItem = createFeedbackItem(mContext); - NotificationEntry entry = mParent.getEntry(); - int personNotifType = mPeopleNotificationIdentifier.getPeopleNotificationType(entry); + int personNotifType = NotificationBundleUi.isEnabled() + ? mParent.getEntryAdapter().getPeopleNotificationType() + : mPeopleNotificationIdentifier.getPeopleNotificationType(mParent.getEntryLegacy()); + StatusBarNotification sbn = NotificationBundleUi.isEnabled() + ? mParent.getEntryAdapter().getSbn() + : mParent.getEntryLegacy().getSbn(); if (personNotifType == PeopleNotificationIdentifier.TYPE_PERSON) { mInfoItem = createPartialConversationItem(mContext); } else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) { mInfoItem = createConversationItem(mContext); } else if (android.app.Flags.uiRichOngoing() && Flags.permissionHelperUiRichOngoing() - && entry.getSbn().getNotification().isPromotedOngoing()) { + && sbn.getNotification().isPromotedOngoing()) { mInfoItem = createPromotedItem(mContext); } else { mInfoItem = createInfoItem(mContext); @@ -358,7 +362,9 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl final float dismissThreshold = getDismissThreshold(); final boolean snappingToDismiss = delta < -dismissThreshold || delta > dismissThreshold; if (mSnappingToDismiss != snappingToDismiss) { - getMenuView().performHapticFeedback(CLOCK_TICK); + if (!Flags.magneticNotificationSwipes()) { + getMenuView().performHapticFeedback(CLOCK_TICK); + } } mSnappingToDismiss = snappingToDismiss; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt index 08c1d71b86c9..03990bf3af84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt @@ -87,9 +87,15 @@ constructor(private val userManager: UserManager, dumpManager: DumpManager) : // It's not a system app at all. return false } else { - // If there's no launch intent, it's probably a headless app. - val pm = context.packageManager - return (pm.getLaunchIntentForPackage(info.packageName) == null) + // If there's no launch intent, it's probably a headless app. Check for both + // direct-aware and -unaware intents; otherwise this will almost certainly fail + // for notifications posted before unlocking. + val packageLaunchIntent = + context.packageManager.getLaunchIntentForPackage( + info.packageName, + /* includeDirectBootUnaware= */ true, + ) + return packageLaunchIntent == null } } else { // If for some reason we don't have the app info, we don't know; best assume it's diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt index aa6951715755..48cff7497e3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt @@ -33,12 +33,12 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow interface MagneticNotificationRowManager { /** - * Set the swipe threshold in pixels. After crossing the threshold, the magnetic target detaches - * and the magnetic neighbors snap back. + * Notifies a change in the device density. The density can be used to compute the values of + * thresholds in pixels. * - * @param[threshold] Swipe threshold in pixels. + * @param[density] The device density. */ - fun setSwipeThresholdPx(thresholdPx: Float) + fun onDensityChange(density: Float) /** * Set the magnetic and roundable targets of a magnetic swipe interaction. @@ -87,6 +87,9 @@ interface MagneticNotificationRowManager { */ fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float? = null) + /** Determine if the given [ExpandableNotificationRow] has been magnetically detached. */ + fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean + /* Reset any roundness that magnetic targets may have */ fun resetRoundness() @@ -104,12 +107,15 @@ interface MagneticNotificationRowManager { /** Detaching threshold in dp */ const val MAGNETIC_DETACH_THRESHOLD_DP = 56 + /** Re-attaching threshold in dp */ + const val MAGNETIC_ATTACH_THRESHOLD_DP = 40 + /* An empty implementation of a manager */ @JvmStatic val Empty: MagneticNotificationRowManager get() = object : MagneticNotificationRowManager { - override fun setSwipeThresholdPx(thresholdPx: Float) {} + override fun onDensityChange(density: Float) {} override fun setMagneticAndRoundableTargets( swipingRow: ExpandableNotificationRow, @@ -127,6 +133,10 @@ interface MagneticNotificationRowManager { velocity: Float?, ) {} + override fun isMagneticRowSwipeDetached( + row: ExpandableNotificationRow + ): Boolean = false + override fun resetRoundness() {} override fun reset() {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt index 5a23f7cc2861..6e8b2226b4f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt @@ -47,6 +47,7 @@ constructor( private set private var magneticDetachThreshold = Float.POSITIVE_INFINITY + private var magneticAttachThreshold = 0f // Has the roundable target been set for the magnetic view that is being swiped. val isSwipedViewRoundableSet: Boolean @@ -57,13 +58,25 @@ constructor( SpringForce().setStiffness(DETACH_STIFFNESS).setDampingRatio(DETACH_DAMPING_RATIO) private val snapForce = SpringForce().setStiffness(SNAP_BACK_STIFFNESS).setDampingRatio(SNAP_BACK_DAMPING_RATIO) + private val attachForce = + SpringForce().setStiffness(ATTACH_STIFFNESS).setDampingRatio(ATTACH_DAMPING_RATIO) // Multiplier applied to the translation of a row while swiped val swipedRowMultiplier = MAGNETIC_TRANSLATION_MULTIPLIERS[MAGNETIC_TRANSLATION_MULTIPLIERS.size / 2] - override fun setSwipeThresholdPx(thresholdPx: Float) { - magneticDetachThreshold = thresholdPx + /** + * An offset applied to input translation that increases on subsequent re-attachments of a + * detached magnetic view. This helps keep computations consistent when the drag gesture input + * and the swiped notification don't share the same origin point after a re-attaching animation. + */ + private var translationOffset = 0f + + override fun onDensityChange(density: Float) { + magneticDetachThreshold = + density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + magneticAttachThreshold = + density * MagneticNotificationRowManager.MAGNETIC_ATTACH_THRESHOLD_DP } override fun setMagneticAndRoundableTargets( @@ -72,6 +85,7 @@ constructor( sectionsManager: NotificationSectionsManager, ) { if (currentState == State.IDLE) { + translationOffset = 0f updateMagneticAndRoundableTargets(swipingRow, stackScrollLayout, sectionsManager) currentState = State.TARGETS_SET } else { @@ -121,36 +135,36 @@ constructor( val canTargetBeDismissed = currentMagneticListeners.swipedListener()?.canRowBeDismissed() ?: false + val correctedTranslation = translation - translationOffset when (currentState) { State.IDLE -> { logger.logMagneticRowTranslationNotSet(currentState, row.getLoggingKey()) return false } State.TARGETS_SET -> { - pullTargets(translation, canTargetBeDismissed) + pullTargets(correctedTranslation, canTargetBeDismissed) currentState = State.PULLING } State.PULLING -> { - updateRoundness(translation) + updateRoundness(correctedTranslation) if (canTargetBeDismissed) { - pullDismissibleRow(translation) + pullDismissibleRow(correctedTranslation) } else { - pullTargets(translation, canSwipedBeDismissed = false) + pullTargets(correctedTranslation, canSwipedBeDismissed = false) } } State.DETACHED -> { - val swiped = currentMagneticListeners.swipedListener() - swiped?.setMagneticTranslation(translation) + translateDetachedRow(correctedTranslation) } } return true } - private fun updateRoundness(translation: Float) { + private fun updateRoundness(translation: Float, animate: Boolean = false) { val normalizedTranslation = abs(swipedRowMultiplier * translation) / magneticDetachThreshold notificationRoundnessManager.setRoundnessForAffectedViews( /* roundness */ normalizedTranslation.coerceIn(0f, MAX_PRE_DETACH_ROUNDNESS), - /* animate */ false, + animate, ) } @@ -232,7 +246,28 @@ constructor( ) } + private fun translateDetachedRow(translation: Float) { + val targetTranslation = swipedRowMultiplier * translation + val crossedThreshold = abs(targetTranslation) <= magneticAttachThreshold + if (crossedThreshold) { + translationOffset += translation + updateRoundness(translation = 0f, animate = true) + currentMagneticListeners.swipedListener()?.let { attach(it) } + currentState = State.PULLING + } else { + val swiped = currentMagneticListeners.swipedListener() + swiped?.setMagneticTranslation(translation, trackEagerly = false) + } + } + + private fun attach(listener: MagneticRowListener) { + listener.cancelMagneticAnimations() + listener.triggerMagneticForce(endTranslation = 0f, attachForce) + msdlPlayer.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR) + } + override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) { + translationOffset = 0f if (row.isSwipedTarget()) { when (currentState) { State.PULLING -> { @@ -254,9 +289,13 @@ constructor( } } + override fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean = + row.isSwipedTarget() && currentState == State.DETACHED + override fun resetRoundness() = notificationRoundnessManager.clear() override fun reset() { + translationOffset = 0f currentMagneticListeners.forEach { it?.cancelMagneticAnimations() it?.cancelTranslationAnimations() @@ -300,6 +339,8 @@ constructor( private const val DETACH_DAMPING_RATIO = 0.95f private const val SNAP_BACK_STIFFNESS = 550f private const val SNAP_BACK_DAMPING_RATIO = 0.6f + private const val ATTACH_STIFFNESS = 800f + private const val ATTACH_DAMPING_RATIO = 0.95f // Maximum value of corner roundness that gets applied during the pre-detach dragging private const val MAX_PRE_DETACH_ROUNDNESS = 0.8f diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt index 5959ef1e093b..344dab4369f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt @@ -21,8 +21,17 @@ import androidx.dynamicanimation.animation.SpringForce /** A listener that responds to magnetic forces applied to an [ExpandableNotificationRow] */ interface MagneticRowListener { - /** Set a translation due to a magnetic attachment. */ - fun setMagneticTranslation(translation: Float) + /** + * Set a translation due to a magnetic attachment. + * + * If a magnetic animation is running, [trackEagerly] decides if the new translation is applied + * immediately or if the animation finishes first. When applying the translation immediately, + * the change in translation must be greater than a touch slop threshold. + * + * @param[translation] Incoming gesture translation. + * @param[trackEagerly] Whether we eagerly track the incoming translation or not. + */ + fun setMagneticTranslation(translation: Float, trackEagerly: Boolean = true) /** * Trigger the magnetic behavior when the row detaches or snaps back from its magnetic diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index f3d8ee245540..612c19fc6696 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -486,15 +486,22 @@ public class NotificationStackScrollLayoutController implements Dumpable { } @Override + public boolean isMagneticViewDetached(View view) { + if (view instanceof ExpandableNotificationRow row) { + return mMagneticNotificationRowManager.isMagneticRowSwipeDetached(row); + } else { + return false; + } + } + + @Override public float getTotalTranslationLength(View animView) { return mView.getTotalTranslationLength(animView); } @Override public void onDensityScaleChange(float density) { - mMagneticNotificationRowManager.setSwipeThresholdPx( - density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP - ); + mMagneticNotificationRowManager.onDensityChange(density); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index c5a846e1da05..5105e55b0a5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -255,12 +255,13 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc int menuSnapTarget = menuRow.getMenuSnapTarget(); boolean isNonFalseMenuRevealingGesture = isMenuRevealingGestureAwayFromMenu && !isFalseGesture(); + boolean isMagneticViewDetached = mCallback.isMagneticViewDetached(animView); if ((isNonDismissGestureTowardsMenu || isNonFalseMenuRevealingGesture) && menuSnapTarget != 0) { // Menu has not been snapped to previously and this is menu revealing gesture snapOpen(animView, menuSnapTarget, velocity); menuRow.onSnapOpen(); - } else if (isDismissGesture && !gestureTowardsMenu) { + } else if (isDismissGesture && (!gestureTowardsMenu || isMagneticViewDetached)) { dismiss(animView, velocity); menuRow.onDismiss(); } else { @@ -272,6 +273,7 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc private void handleSwipeFromOpenState(MotionEvent ev, View animView, float velocity, NotificationMenuRowPlugin menuRow) { boolean isDismissGesture = isDismissGesture(ev); + boolean isMagneticViewDetached = mCallback.isMagneticViewDetached(animView); final boolean withinSnapMenuThreshold = menuRow.isWithinSnapMenuThreshold(); @@ -280,7 +282,7 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc // Haven't moved enough to unsnap from the menu menuRow.onSnapOpen(); snapOpen(animView, menuRow.getMenuSnapTarget(), velocity); - } else if (isDismissGesture && !menuRow.shouldSnapBack()) { + } else if (isDismissGesture && (!menuRow.shouldSnapBack() || isMagneticViewDetached)) { // Only dismiss if we're not moving towards the menu dismiss(animView, velocity); menuRow.onDismiss(); diff --git a/packages/SystemUI/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/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 36193bd87ce2..3c144625b685 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -49,6 +49,7 @@ import com.android.keyguard.CarrierTextController; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.logging.KeyguardLogger; +import com.android.systemui.Flags; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor; import com.android.systemui.dagger.qualifiers.Background; @@ -475,12 +476,14 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat UserHandle.USER_ALL); updateUserSwitcher(); onThemeChanged(); - collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer, - mCoroutineDispatcher); - collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(), - mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher); - collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(), - mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher); + if (!Flags.glanceableHubV2()) { + collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer, + mCoroutineDispatcher); + collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(), + mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher); + collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(), + mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher); + } if (NewStatusBarIcons.isEnabled()) { ComposeView batteryComposeView = new ComposeView(mContext); UnifiedBatteryViewBinder.bind( @@ -645,7 +648,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat && !mDozing && !hideForBypass && !mDisableStateTracker.isDisabled() - && (!mCommunalShowing || mExplicitAlpha != -1) + && (Flags.glanceableHubV2() || (!mCommunalShowing || mExplicitAlpha != -1)) ? View.VISIBLE : View.INVISIBLE; updateViewState(newAlpha, newVisibility); 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/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt index 1a8ca9577bd7..f4afc248d11a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt @@ -22,6 +22,7 @@ import com.android.systemui.KairosBuilder import com.android.systemui.kairos.BuildSpec import com.android.systemui.kairos.ExperimentalKairosApi import com.android.systemui.kairos.State +import com.android.systemui.kairos.combine import com.android.systemui.kairos.flatMap import com.android.systemui.kairosBuilder import com.android.systemui.log.table.TableLogBuffer @@ -55,9 +56,15 @@ constructor( @Assisted private val isCarrierMerged: State<Boolean>, ) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() { + private var dumpCache: DumpCache? = null + init { onActivated { logDiffsForTable(isCarrierMerged, tableLogBuffer, columnName = "isCarrierMerged") + combine(isCarrierMerged, activeRepo) { isCarrierMerged, activeRepo -> + DumpCache(isCarrierMerged, activeRepo) + } + .observe { dumpCache = it } } } @@ -198,13 +205,6 @@ constructor( override val isInEcmMode: State<Boolean> = activeRepo.flatMap { it.isInEcmMode } - private var dumpCache: DumpCache? = null - - private data class DumpCache( - val isCarrierMerged: Boolean, - val activeRepo: MobileConnectionRepositoryKairos, - ) - fun dump(pw: PrintWriter) { val cache = dumpCache ?: return val ipw = IndentingPrintWriter(pw, " ") @@ -227,6 +227,11 @@ constructor( ipw.decreaseIndent() } + private data class DumpCache( + val isCarrierMerged: Boolean, + val activeRepo: MobileConnectionRepositoryKairos, + ) + @AssistedFactory interface Factory { fun create( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt index e46815954e64..e6c29214e3d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt @@ -131,6 +131,8 @@ constructor( private val mobileRepoFactory: Lazy<ConnectionRepoFactory>, ) : MobileConnectionsRepositoryKairos, Dumpable, KairosBuilder by kairosBuilder() { + private var dumpCache: DumpCache? = null + init { dumpManager.registerNormalDumpable("MobileConnectionsRepositoryKairos", this) } @@ -253,6 +255,7 @@ constructor( .asIncremental() .mapValues { (subId, sub) -> mobileRepoFactory.get().create(subId) } .applyLatestSpecForKey() + .apply { observe { dumpCache = DumpCache(it) } } } private val telephonyManagerState: State<Pair<Int?, Set<Int>>> = buildState { @@ -479,10 +482,6 @@ constructor( profileClass = profileClass, ) - private var dumpCache: DumpCache? = null - - private data class DumpCache(val repos: Map<Int, FullMobileConnectionRepositoryKairos>) - override fun dump(pw: PrintWriter, args: Array<String>) { val cache = dumpCache ?: return val ipw = IndentingPrintWriter(pw, " ") @@ -494,10 +493,16 @@ constructor( ipw.println("Connections (${cache.repos.size} total):") ipw.increaseIndent() - cache.repos.values.forEach { it.dump(ipw) } + cache.repos.values.forEach { + if (it is FullMobileConnectionRepositoryKairos) { + it.dump(ipw) + } + } ipw.decreaseIndent() } + private data class DumpCache(val repos: Map<Int, MobileConnectionRepositoryKairos>) + fun interface ConnectionRepoFactory { fun create(subId: Int): BuildSpec<MobileConnectionRepositoryKairos> } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index 0eabb4ecee84..af4e61afaaaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -61,31 +61,31 @@ interface MobileIconInteractor { * consider this connection to be serving data, and thus want to show a network type icon, when * data is connected. Other data connection states would typically cause us not to show the icon */ - val isDataConnected: StateFlow<Boolean> + val isDataConnected: Flow<Boolean> /** True if we consider this connection to be in service, i.e. can make calls */ - val isInService: StateFlow<Boolean> + val isInService: Flow<Boolean> /** True if this connection is emergency only */ - val isEmergencyOnly: StateFlow<Boolean> + val isEmergencyOnly: Flow<Boolean> /** Observable for the data enabled state of this connection */ - val isDataEnabled: StateFlow<Boolean> + val isDataEnabled: Flow<Boolean> /** True if the RAT icon should always be displayed and false otherwise. */ - val alwaysShowDataRatIcon: StateFlow<Boolean> + val alwaysShowDataRatIcon: Flow<Boolean> /** Canonical representation of the current mobile signal strength as a triangle. */ - val signalLevelIcon: StateFlow<SignalIconModel> + val signalLevelIcon: Flow<SignalIconModel> /** Observable for RAT type (network type) indicator */ - val networkTypeIconGroup: StateFlow<NetworkTypeIconModel> + val networkTypeIconGroup: Flow<NetworkTypeIconModel> /** Whether or not to show the slice attribution */ - val showSliceAttribution: StateFlow<Boolean> + val showSliceAttribution: Flow<Boolean> /** True if this connection is satellite-based */ - val isNonTerrestrial: StateFlow<Boolean> + val isNonTerrestrial: Flow<Boolean> /** * Provider name for this network connection. The name can be one of 3 values: @@ -95,7 +95,7 @@ interface MobileIconInteractor { * override in [connectionInfo.operatorAlphaShort], a value that is derived from * [ServiceState] */ - val networkName: StateFlow<NetworkNameModel> + val networkName: Flow<NetworkNameModel> /** * Provider name for this network connection. The name can be one of 3 values: @@ -108,26 +108,26 @@ interface MobileIconInteractor { * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data * provided is identical */ - val carrierName: StateFlow<String> + val carrierName: Flow<String> /** True if there is only one active subscription. */ - val isSingleCarrier: StateFlow<Boolean> + val isSingleCarrier: Flow<Boolean> /** * True if this connection is considered roaming. The roaming bit can come from [ServiceState], * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a * connection to be roaming while carrier network change is active */ - val isRoaming: StateFlow<Boolean> + val isRoaming: Flow<Boolean> /** See [MobileIconsInteractor.isForceHidden]. */ val isForceHidden: Flow<Boolean> /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */ - val isAllowedDuringAirplaneMode: StateFlow<Boolean> + val isAllowedDuringAirplaneMode: Flow<Boolean> /** True when in carrier network change mode */ - val carrierNetworkChangeActive: StateFlow<Boolean> + val carrierNetworkChangeActive: Flow<Boolean> } /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt index 87877b3e9f43..6b9c5374ef2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt @@ -32,11 +32,20 @@ import com.android.systemui.kairos.map import com.android.systemui.kairos.mapValues import com.android.systemui.kairos.toColdConflatedFlow import com.android.systemui.kairosBuilder +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import dagger.Provides import dagger.multibindings.ElementsIntoSet import javax.inject.Inject @@ -45,6 +54,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @ExperimentalKairosApi @@ -60,6 +71,7 @@ constructor( context: Context, mobileMappingsProxy: MobileMappingsProxy, private val userSetupRepo: UserSetupRepository, + private val logFactory: TableLogBufferFactory, ) : MobileIconsInteractor, KairosBuilder by kairosBuilder() { private val interactorsBySubIdK = buildIncremental { @@ -158,7 +170,37 @@ constructor( get() = repo.isDeviceEmergencyCallCapable override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = - interactorsBySubId.value[subId] ?: error("Unknown subscription id: $subId") + object : MobileIconInteractor { + override val tableLogBuffer: TableLogBuffer = + logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE) + override val activity: Flow<DataActivityModel> = latest { activity } + override val mobileIsDefault: Flow<Boolean> = latest { mobileIsDefault } + override val isDataConnected: Flow<Boolean> = latest { isDataConnected } + override val isInService: Flow<Boolean> = latest { isInService } + override val isEmergencyOnly: Flow<Boolean> = latest { isEmergencyOnly } + override val isDataEnabled: Flow<Boolean> = latest { isDataEnabled } + override val alwaysShowDataRatIcon: Flow<Boolean> = latest { alwaysShowDataRatIcon } + override val signalLevelIcon: Flow<SignalIconModel> = latest { signalLevelIcon } + override val networkTypeIconGroup: Flow<NetworkTypeIconModel> = latest { + networkTypeIconGroup + } + override val showSliceAttribution: Flow<Boolean> = latest { showSliceAttribution } + override val isNonTerrestrial: Flow<Boolean> = latest { isNonTerrestrial } + override val networkName: Flow<NetworkNameModel> = latest { networkName } + override val carrierName: Flow<String> = latest { carrierName } + override val isSingleCarrier: Flow<Boolean> = latest { isSingleCarrier } + override val isRoaming: Flow<Boolean> = latest { isRoaming } + override val isForceHidden: Flow<Boolean> = latest { isForceHidden } + override val isAllowedDuringAirplaneMode: Flow<Boolean> = latest { + isAllowedDuringAirplaneMode + } + override val carrierNetworkChangeActive: Flow<Boolean> = latest { + carrierNetworkChangeActive + } + + private fun <T> latest(block: MobileIconInteractor.() -> Flow<T>): Flow<T> = + interactorsBySubId.flatMapLatestConflated { it[subId]?.block() ?: emptyFlow() } + } @dagger.Module object Module { 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/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 05b2e0d1423e..b33eafcdfa84 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -414,16 +414,15 @@ constructor( } } + private suspend fun SelectedUserModel.isEligibleForLogout(): Boolean { + return withContext(backgroundDispatcher) { + selectionStatus == SelectionStatus.SELECTION_COMPLETE && + devicePolicyManager.logoutUser != null + } + } + companion object { private const val TAG = "UserRepository" @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher" } } - -fun SelectedUserModel.isEligibleForLogout(): Boolean { - // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of - // hardcode it to USER_SYSTEM so it properly supports headless system user mode - // (and then call mDevicePolicyManager.clearLogoutUser() after switched) - return selectionStatus == SelectionStatus.SELECTION_COMPLETE && - userInfo.id != android.os.UserHandle.USER_SYSTEM -} 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/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index cf8278eb8ac6..82082cc778b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -38,9 +38,13 @@ import com.android.internal.widget.NotificationExpandButton import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.FeedbackIcon import com.android.systemui.statusbar.notification.collection.EntryAdapter +import com.android.systemui.statusbar.notification.collection.EntryAdapterFactory import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.msgStyleBubbleableFullPerson +import com.android.systemui.statusbar.notification.people.peopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.shared.NotificationBundleUi +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -51,7 +55,6 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt @@ -67,10 +70,12 @@ import org.mockito.MockitoAnnotations.initMocks @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NotificationContentViewTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private val factory: EntryAdapterFactory = kosmos.entryAdapterFactory private lateinit var row: ExpandableNotificationRow private lateinit var fakeParent: ViewGroup - @Mock private lateinit var mPeopleNotificationIdentifier: PeopleNotificationIdentifier private val testableResources = mContext.getOrCreateTestableResources() private val contractedHeight = @@ -82,24 +87,19 @@ class NotificationContentViewTest : SysuiTestCase() { fun setup() { initMocks(this) fakeParent = - spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE }) - val mockEntry = createMockNotificationEntry() - val mockEntryAdapter = createMockNotificationEntryAdapter() + spy(FrameLayout(mContext, /* attrs= */ null)).also { it.visibility = View.GONE } + val entry = kosmos.msgStyleBubbleableFullPerson + val mockEntryAdapter = factory.create(entry) row = spy( when (NotificationBundleUi.isEnabled) { true -> { - ExpandableNotificationRow( - mContext, - /* attrs= */ null, - UserHandle.CURRENT - ).apply { - entryAdapter = mockEntryAdapter - } + ExpandableNotificationRow(mContext, /* attrs= */ null, UserHandle.CURRENT) + .apply { entryAdapter = mockEntryAdapter } } false -> { - ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply { - entryLegacy = mockEntry + ExpandableNotificationRow(mContext, /* attrs= */ null, entry).apply { + entryLegacy = entry } } } @@ -406,11 +406,11 @@ class NotificationContentViewTest : SysuiTestCase() { fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin // Bubble button should not be shown for the given NotificationEntry - val mockNotificationEntry = createMockNotificationEntry() + val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson val mockContainingNotification = createMockContainingNotification(mockNotificationEntry) val actionListMarginTarget = spy(createLinearLayoutWithBottomMargin(notificationContentMargin)) - val mockExpandedChild = createMockExpandedChild(mockNotificationEntry) + val mockExpandedChild = createMockExpandedChild() whenever( mockExpandedChild.findViewById<LinearLayout>( R.id.notification_action_list_margin_target @@ -434,11 +434,11 @@ class NotificationContentViewTest : SysuiTestCase() { fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin // Bubble button should be shown for the given NotificationEntry - val mockNotificationEntry = createMockNotificationEntry() + val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson val mockContainingNotification = createMockContainingNotification(mockNotificationEntry) val actionListMarginTarget = spy(createLinearLayoutWithBottomMargin(notificationContentMargin)) - val mockExpandedChild = createMockExpandedChild(mockNotificationEntry) + val mockExpandedChild = createMockExpandedChild() whenever( mockExpandedChild.findViewById<LinearLayout>( R.id.notification_action_list_margin_target @@ -463,11 +463,11 @@ class NotificationContentViewTest : SysuiTestCase() { @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES) fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin - val mockNotificationEntry = createMockNotificationEntry() + val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson val mockContainingNotification = createMockContainingNotification(mockNotificationEntry) val actionListMarginTarget = spy(createLinearLayoutWithBottomMargin(notificationContentMargin)) - val mockExpandedChild = createMockExpandedChild(mockNotificationEntry) + val mockExpandedChild = createMockExpandedChild() whenever( mockExpandedChild.findViewById<LinearLayout>( R.id.notification_action_list_margin_target @@ -482,7 +482,7 @@ class NotificationContentViewTest : SysuiTestCase() { // When: call NotificationContentView.onNotificationUpdated() to update the // NotificationEntry, which should not show bubble button - view.onNotificationUpdated(createMockNotificationEntry()) + view.onNotificationUpdated(kosmos.msgStyleBubbleableFullPerson) // Then: bottom margin of actionListMarginTarget should not change, still be 20 assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget)) @@ -492,11 +492,11 @@ class NotificationContentViewTest : SysuiTestCase() { @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES) fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() { // Given: bottom margin of actionListMarginTarget is notificationContentMargin - val mockNotificationEntry = createMockNotificationEntry() + val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson val mockContainingNotification = createMockContainingNotification(mockNotificationEntry) val actionListMarginTarget = spy(createLinearLayoutWithBottomMargin(notificationContentMargin)) - val mockExpandedChild = createMockExpandedChild(mockNotificationEntry) + val mockExpandedChild = createMockExpandedChild() whenever( mockExpandedChild.findViewById<LinearLayout>( R.id.notification_action_list_margin_target @@ -510,7 +510,7 @@ class NotificationContentViewTest : SysuiTestCase() { // When: call NotificationContentView.onNotificationUpdated() to update the // NotificationEntry, which should show bubble button - view.onNotificationUpdated(createMockNotificationEntry(/*true*/ )) + view.onNotificationUpdated(kosmos.msgStyleBubbleableFullPerson) // Then: no bubble yet assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget)) @@ -615,15 +615,17 @@ class NotificationContentViewTest : SysuiTestCase() { private fun createMockContainingNotification(notificationEntry: NotificationEntry) = mock<ExpandableNotificationRow>().apply { - whenever(this.entry).thenReturn(notificationEntry) + if (!NotificationBundleUi.isEnabled) { + whenever(this.entryLegacy).thenReturn(notificationEntry) + } whenever(this.context).thenReturn(mContext) whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {}) - whenever(this.entryAdapter).thenReturn(createMockNotificationEntryAdapter()) + whenever(this.entryAdapter).thenReturn(factory.create(notificationEntry)) } private fun createMockNotificationEntry() = mock<NotificationEntry>().apply { - whenever(mPeopleNotificationIdentifier.getPeopleNotificationType(this)) + whenever(kosmos.peopleNotificationIdentifier.getPeopleNotificationType(this)) .thenReturn(PeopleNotificationIdentifier.TYPE_FULL_PERSON) whenever(this.bubbleMetadata).thenReturn(mock()) val sbnMock: StatusBarNotification = mock() @@ -632,7 +634,8 @@ class NotificationContentViewTest : SysuiTestCase() { whenever(sbnMock.user).thenReturn(userMock) } - private fun createMockNotificationEntryAdapter() = mock<EntryAdapter>() + private fun createMockNotificationEntryAdapter() = + mock<EntryAdapter>() private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout { val outerLayout = LinearLayout(mContext) @@ -643,7 +646,7 @@ class NotificationContentViewTest : SysuiTestCase() { return innerLayout } - private fun createMockExpandedChild(notificationEntry: NotificationEntry) = + private fun createMockExpandedChild() = spy(createViewWithHeight(expandedHeight)).apply { whenever(this.findViewById<ImageView>(R.id.bubble_button)).thenReturn(mock()) whenever(this.findViewById<View>(R.id.actions_container)).thenReturn(mock()) @@ -664,9 +667,16 @@ class NotificationContentViewTest : SysuiTestCase() { val height = if (isSystemExpanded) expandedHeight else contractedHeight doReturn(height).whenever(row).intrinsicHeight - return spy(NotificationContentView(mContext, /* attrs= */ null)) + return NotificationContentView(mContext, /* attrs= */ null) .apply { - initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock(), mock()) + initialize( + kosmos.peopleNotificationIdentifier, + mock(), + mock(), + mock(), + mock(), + mock(), + ) setContainingNotification(row) setHeights( /* smallHeight= */ contractedHeight, 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/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 15cb95a99967..846db6389d0c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -15,6 +15,7 @@ */ package com.android.systemui; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -49,11 +50,13 @@ import com.android.systemui.log.LogWtfHandlerRule; import org.junit.After; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.mockito.Mockito; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.annotation.Retention; @@ -190,6 +193,7 @@ public abstract class SysuiTestCase { @Before public void SysuiSetup() throws Exception { + assertTempFilesAreCreatable(); ProtoLog.REQUIRE_PROTOLOGTOOL = false; mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver()); mDependency = mSysuiDependency.install(); @@ -211,6 +215,28 @@ public abstract class SysuiTestCase { } } + private static Boolean sCanCreateTempFiles = null; + + private static void assertTempFilesAreCreatable() { + // TODO(b/391948934): hopefully remove this hack + if (sCanCreateTempFiles == null) { + try { + File tempFile = File.createTempFile("confirm_temp_file_createable", "txt"); + sCanCreateTempFiles = true; + assertTrue(tempFile.delete()); + } catch (IOException e) { + sCanCreateTempFiles = false; + throw new RuntimeException(e); + } + } + if (!sCanCreateTempFiles) { + Assert.fail( + "Cannot create temp files, so mockito will probably fail (b/391948934). Temp" + + " folder should be: " + + System.getProperty("java.io.tmpdir")); + } + } + protected boolean shouldFailOnLeakedReceiver() { return false; } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt index 338e4bec7aa2..122e6a507cbf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.display.data.repository import android.view.Display +import com.android.app.displaylib.DisplayRepository.PendingDisplay import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.mockito.mock import dagger.Binds @@ -26,7 +27,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import com.android.app.displaylib.DisplayRepository.PendingDisplay import org.mockito.Mockito.`when` as whenever /** Creates a mock display. */ @@ -50,8 +50,7 @@ fun createPendingDisplay(id: Int = 0): PendingDisplay = class FakeDisplayRepository @Inject constructor() : DisplayRepository { private val flow = MutableStateFlow<Set<Display>>(emptySet()) private val displayIdFlow = MutableStateFlow<Set<Int>>(emptySet()) - private val pendingDisplayFlow = - MutableSharedFlow<PendingDisplay?>(replay = 1) + private val pendingDisplayFlow = MutableSharedFlow<PendingDisplay?>(replay = 1) private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0) private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0) private val displayIdsWithSystemDecorationsFlow = MutableStateFlow<Set<Int>>(emptySet()) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt index 5ab3b3de49f4..4b516e9c74bc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt @@ -16,6 +16,8 @@ package com.android.systemui.display.data.repository +import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown +import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 623989ec5809..c80d7386f67a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -81,6 +81,7 @@ import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlag import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor +import com.android.systemui.statusbar.notification.row.entryAdapterFactory import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.statusbar.phone.fakeAutoHideControllerStore @@ -206,4 +207,5 @@ class KosmosJavaAdapter() { val displayTracker by lazy { kosmos.displayTracker } val fakeShadeDisplaysRepository by lazy { kosmos.fakeShadeDisplaysRepository } val sysUIStateDispatcher by lazy { kosmos.sysUIStateDispatcher } + val entryAdapterFactory by lazy { kosmos.entryAdapterFactory } } 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/statusbar/notification/NotificationEntryBuilderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt index 59f5ecd2563f..00c6c9445fe7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt @@ -14,18 +14,25 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification +package com.android.systemui.statusbar.notification.collection import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.PendingIntent import android.app.Person import android.content.Intent import android.content.applicationContext +import android.graphics.Bitmap import android.graphics.drawable.Icon +import com.android.systemui.activity.EmptyTestActivity import com.android.systemui.kosmos.Kosmos -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.res.R import com.android.systemui.statusbar.notification.icon.IconPack +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.promoted.setPromotedContent import org.mockito.kotlin.mock @@ -40,6 +47,11 @@ fun Kosmos.setIconPackWithMockIconViews(entry: NotificationEntry) { ) } +fun Kosmos.buildPromotedOngoingEntry( + block: NotificationEntryBuilder.() -> Unit = {} +): NotificationEntry = + buildNotificationEntry(tag = "ron", promoted = true, style = null, block = block) + fun Kosmos.buildOngoingCallEntry( promoted: Boolean = false, block: NotificationEntryBuilder.() -> Unit = {}, @@ -51,11 +63,6 @@ fun Kosmos.buildOngoingCallEntry( block = block, ) -fun Kosmos.buildPromotedOngoingEntry( - block: NotificationEntryBuilder.() -> Unit = {} -): NotificationEntry = - buildNotificationEntry(tag = "ron", promoted = true, style = null, block = block) - fun Kosmos.buildNotificationEntry( tag: String? = null, promoted: Boolean = false, @@ -88,3 +95,49 @@ private fun Kosmos.makeOngoingCallStyle(): Notification.CallStyle { val person = Person.Builder().setName("person").build() return Notification.CallStyle.forOngoingCall(person, pendingIntent) } + +private fun Kosmos.makeMessagingStyleNotification(): Notification.Builder { + val personIcon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)) + val person = Person.Builder().setIcon(personIcon).setName("Person").build() + val message = Notification.MessagingStyle.Message("Message!", 4323, person) + val bubbleIntent = + PendingIntent.getActivity( + applicationContext, + 0, + Intent(applicationContext, EmptyTestActivity::class.java), + PendingIntent.FLAG_MUTABLE, + ) + + return Notification.Builder(applicationContext, "channelId") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text") + .setStyle(Notification.MessagingStyle(person).addMessage(message)) + .setBubbleMetadata( + Notification.BubbleMetadata.Builder( + bubbleIntent, + Icon.createWithResource(applicationContext, R.drawable.android), + ) + .setDeleteIntent(mock<PendingIntent>()) + .setDesiredHeight(314) + .setAutoExpandBubble(false) + .build() + ) +} + +fun Kosmos.makeEntryOfPeopleType(@PeopleNotificationType type: Int): NotificationEntryBuilder { + val channel = NotificationChannel("messages", "messages", IMPORTANCE_DEFAULT) + channel.isImportantConversation = (type == TYPE_IMPORTANT_PERSON) + channel.setConversationId("parent", "convo") + + val entry = + NotificationEntryBuilder().apply { + updateRanking { + it.setIsConversation(type != TYPE_NON_PERSON) + it.setShortcutInfo(if (type >= TYPE_FULL_PERSON) mock() else null) + it.setChannel(channel) + } + setNotification(makeMessagingStyleNotification().build()) + } + return entry +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt new file mode 100644 index 000000000000..e127a70dc07d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.systemui.statusbar.notification.collection + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON + +val Kosmos.msgStyleBubbleableFullPerson by + Kosmos.Fixture { makeEntryOfPeopleType(TYPE_FULL_PERSON).build() } 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/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java index 7502664a9628..180ef855cfda 100644 --- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java +++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java @@ -39,6 +39,7 @@ import android.os.Trace; import android.os.UserHandle; import android.util.ArraySet; import android.util.IntArray; +import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.media.permission.INativePermissionController; @@ -62,6 +63,8 @@ import java.util.stream.Collectors; /** Responsible for synchronizing system server permission state to the native audioserver. */ public class AudioServerPermissionProvider { + static final String TAG = "AudioServerPermissionProvider"; + static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE]; static final byte[] HDS_PERMS = new byte[] {PermissionEnum.CAPTURE_AUDIO_HOTWORD, @@ -219,10 +222,13 @@ public class AudioServerPermissionProvider { public void setIsolatedServiceUid(int uid, int owningUid) { synchronized (mLock) { if (mHdsUid == uid) return; - var packageNameSet = mPackageMap.get(owningUid); - if (packageNameSet == null) return; - var packageName = packageNameSet.iterator().next(); - onModifyPackageState(uid, packageName, /* isRemove= */ false); + var packageNameSet = mPackageMap.get(UserHandle.getAppId(owningUid)); + if (packageNameSet != null) { + var packageName = packageNameSet.iterator().next(); + onModifyPackageState(uid, packageName, /* isRemove= */ false); + } else { + Log.wtf(TAG, "setIsolatedService owning uid not found"); + } // permissions mHdsUid = uid; if (mDest == null) { @@ -249,11 +255,19 @@ public class AudioServerPermissionProvider { public void clearIsolatedServiceUid(int uid) { synchronized (mLock) { - if (mHdsUid != uid) return; - var packageNameSet = mPackageMap.get(uid); - if (packageNameSet == null) return; - var packageName = packageNameSet.iterator().next(); - onModifyPackageState(uid, packageName, /* isRemove= */ true); + var packageNameSet = mPackageMap.get(UserHandle.getAppId(uid)); + if (mHdsUid != uid) { + Log.wtf(TAG, + "Unexpected isolated service uid cleared: " + uid + packageNameSet + + ", expected " + mHdsUid); + return; + } + if (packageNameSet != null) { + var packageName = packageNameSet.iterator().next(); + onModifyPackageState(uid, packageName, /* isRemove= */ true); + } else { + Log.wtf(TAG, "clearIsolatedService uid not found"); + } // permissions if (mDest == null) { mIsUpdateDeferred = true; 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/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index d9db178e0dc2..6e6d00d62819 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1230,7 +1230,7 @@ public class InputManagerService extends IInputManager.Stub "registerTabletModeChangedListener()")) { throw new SecurityException("Requires TABLET_MODE_LISTENER permission"); } - Objects.requireNonNull(listener, "event must not be null"); + Objects.requireNonNull(listener, "listener must not be null"); synchronized (mTabletModeLock) { final int callingPid = Binder.getCallingPid(); @@ -1342,7 +1342,7 @@ public class InputManagerService extends IInputManager.Stub @Override public void requestPointerCapture(IBinder inputChannelToken, boolean enabled) { - Objects.requireNonNull(inputChannelToken, "event must not be null"); + Objects.requireNonNull(inputChannelToken, "inputChannelToken must not be null"); mNative.requestPointerCapture(inputChannelToken, enabled); } 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/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index 7b4c56334868..7fd400ec8ac9 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -518,7 +518,6 @@ public class GnssNative { if (!Flags.gnssAssistanceInterfaceJni()) { return; } - Preconditions.checkState(!mRegistered); Preconditions.checkState(mGnssAssistanceCallbacks == null); mGnssAssistanceCallbacks = Objects.requireNonNull(callbacks); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 8948bd196789..78554bdcf6aa 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6173,10 +6173,15 @@ public class NotificationManagerService extends SystemService { } @Override - public Map<String, AutomaticZenRule> getAutomaticZenRules() { + public ParceledListSlice getAutomaticZenRules() { int callingUid = Binder.getCallingUid(); enforcePolicyAccess(callingUid, "getAutomaticZenRules"); - return mZenModeHelper.getAutomaticZenRules(getCallingZenUser(), callingUid); + List<AutomaticZenRule.AzrWithId> ruleList = new ArrayList<>(); + for (Map.Entry<String, AutomaticZenRule> rule : mZenModeHelper.getAutomaticZenRules( + getCallingZenUser(), callingUid).entrySet()) { + ruleList.add(new AutomaticZenRule.AzrWithId(rule.getKey(), rule.getValue())); + } + return new ParceledListSlice<>(ruleList); } @Override 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/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java index 6872ca9e46ee..f5daa8036726 100644 --- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java +++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java @@ -59,6 +59,7 @@ import com.android.server.security.advancedprotection.features.DisallowCellular2 import com.android.server.security.advancedprotection.features.DisallowInstallUnknownSourcesAdvancedProtectionHook; import com.android.server.security.advancedprotection.features.MemoryTaggingExtensionHook; import com.android.server.security.advancedprotection.features.UsbDataAdvancedProtectionHook; +import com.android.server.security.advancedprotection.features.DisallowWepAdvancedProtectionProvider; import java.io.File; import java.io.FileDescriptor; @@ -131,7 +132,9 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub } catch (Exception e) { Slog.e(TAG, "Failed to initialize UsbDataAdvancedProtection", e); } - } + } + + mProviders.add(new DisallowWepAdvancedProtectionProvider()); } // Only for tests @@ -303,7 +306,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub getAdvancedProtectionFeatures_enforcePermission(); List<AdvancedProtectionFeature> features = new ArrayList<>(); for (int i = 0; i < mProviders.size(); i++) { - features.addAll(mProviders.get(i).getFeatures()); + features.addAll(mProviders.get(i).getFeatures(mContext)); } for (int i = 0; i < mHooks.size(); i++) { @@ -341,7 +344,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub writer.println(" Providers: "); mProviders.stream().forEach(provider -> { writer.println(" " + provider.getClass().getSimpleName()); - provider.getFeatures().stream().forEach(feature -> { + provider.getFeatures(mContext).stream().forEach(feature -> { writer.println(" " + feature.getClass().getSimpleName()); }); }); diff --git a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java index ed451f1e2257..6498cfc97aac 100644 --- a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java +++ b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java @@ -16,6 +16,8 @@ package com.android.server.security.advancedprotection.features; +import android.annotation.NonNull; +import android.content.Context; import android.security.advancedprotection.AdvancedProtectionFeature; import java.util.List; @@ -23,5 +25,5 @@ import java.util.List; /** @hide */ public abstract class AdvancedProtectionProvider { /** The list of features provided */ - public abstract List<AdvancedProtectionFeature> getFeatures(); + public abstract List<AdvancedProtectionFeature> getFeatures(@NonNull Context context); } diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java new file mode 100644 index 000000000000..1505f68b699e --- /dev/null +++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java @@ -0,0 +1,31 @@ +/* + * 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.security.advancedprotection.features; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.wifi.WifiManager; +import android.security.advancedprotection.AdvancedProtectionFeature; + +import java.util.List; + +public class DisallowWepAdvancedProtectionProvider extends AdvancedProtectionProvider { + public List<AdvancedProtectionFeature> getFeatures(@NonNull Context context) { + WifiManager wifiManager = context.getSystemService(WifiManager.class); + return wifiManager.getAvailableAdvancedProtectionFeatures(); + } +} 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/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index b7ef1057388c..0e14f83c96f8 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2650,9 +2650,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { */ void endDeferResume() { mDeferResumeCount--; - if (readyToResume() && mLastReportedTopResumedActivity != null - && mTopResumedActivity != mLastReportedTopResumedActivity) { - scheduleTopResumedActivityStateLossIfNeeded(); + if (readyToResume()) { + if (mLastReportedTopResumedActivity != null + && mTopResumedActivity != mLastReportedTopResumedActivity) { + scheduleTopResumedActivityStateLossIfNeeded(); + } else if (mLastReportedTopResumedActivity == null) { + scheduleTopResumedActivityStateIfNeeded(); + } } } 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/PointerEventDispatcher.java b/services/core/java/com/android/server/wm/PointerEventDispatcher.java index 4f8ec631d9e2..be259ec3f7c0 100644 --- a/services/core/java/com/android/server/wm/PointerEventDispatcher.java +++ b/services/core/java/com/android/server/wm/PointerEventDispatcher.java @@ -80,7 +80,7 @@ public class PointerEventDispatcher extends InputEventReceiver { public void unregisterInputEventListener(PointerEventListener listener) { synchronized (mListeners) { if (!mListeners.contains(listener)) { - throw new IllegalStateException("registerInputEventListener: " + listener + + throw new IllegalStateException("unregisterInputEventListener: " + listener + " not registered."); } mListeners.remove(listener); 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/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index bdd13722aba4..d356128205df 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -337,6 +337,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio public static final int ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE = 1 << 25; public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff; + private static final int ACTIVITY_STATE_VISIBLE = + com.android.window.flags.Flags.useVisibleRequestedForProcessTracker() + ? ACTIVITY_STATE_FLAG_IS_VISIBLE + : ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE; + /** * The state for oom-adjustment calculation. The higher 16 bits are the activity states, and the * lower 16 bits are the task layer rank (see {@link Task#mLayerRank}). This field is written by @@ -1260,8 +1265,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio int nonOccludedRatio = 0; long perceptibleTaskStoppedTimeMillis = Long.MIN_VALUE; final boolean wasResumed = hasResumedActivity(); - final boolean wasAnyVisible = (mActivityStateFlags - & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0; + final boolean wasAnyVisible = (mActivityStateFlags & ACTIVITY_STATE_VISIBLE) != 0; for (int i = mActivities.size() - 1; i >= 0; i--) { final ActivityRecord r = mActivities.get(i); if (r.isVisible()) { @@ -1275,8 +1279,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio if (task.mLayerRank != Task.LAYER_RANK_INVISIBLE) { stateFlags |= ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK; } + final ActivityRecord.State state = r.getState(); if (r.isVisibleRequested()) { - if (r.isState(RESUMED)) { + if (state == RESUMED) { stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED; final int windowingMode = r.getWindowingMode(); if (windowingMode == WINDOWING_MODE_MULTI_WINDOW @@ -1301,13 +1306,21 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // this process, we'd find out the one with the minimal layer, thus it'll // get a higher adj score. } else if (!visible && bestInvisibleState != PAUSING) { - if (r.isState(PAUSING, PAUSED)) { + if (state == PAUSING) { bestInvisibleState = PAUSING; - } else if (r.isState(STOPPING)) { + // Treat PAUSING as visible in case the next activity in the same process has + // not yet been set as visible-requested. + if (com.android.window.flags.Flags.useVisibleRequestedForProcessTracker() + && r.isVisible()) { + stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE; + } + } else if (state == PAUSED) { + bestInvisibleState = PAUSED; + } else if (state == STOPPING) { bestInvisibleState = STOPPING; // Not "finishing" if any of activity isn't finishing. allStoppingFinishing &= r.finishing; - } else if (bestInvisibleState == DESTROYED && r.isState(STOPPED)) { + } else if (bestInvisibleState == DESTROYED && state == STOPPED) { if (task.mIsPerceptible) { perceptibleTaskStoppedTimeMillis = Long.max(r.mStoppedTime, perceptibleTaskStoppedTimeMillis); @@ -1340,7 +1353,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; if (visible) { stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE; - } else if (bestInvisibleState == PAUSING) { + } else if (bestInvisibleState == PAUSING || bestInvisibleState == PAUSED) { stateFlags |= ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED; } else if (bestInvisibleState == STOPPING) { stateFlags |= ACTIVITY_STATE_FLAG_IS_STOPPING; @@ -1351,8 +1364,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio mActivityStateFlags = stateFlags; mPerceptibleTaskStoppedTimeMillis = perceptibleTaskStoppedTimeMillis; - final boolean anyVisible = (stateFlags - & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0; + final boolean anyVisible = (stateFlags & ACTIVITY_STATE_VISIBLE) != 0; if (!wasAnyVisible && anyVisible) { mAtm.mVisibleActivityProcessTracker.onAnyActivityVisible(this); mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, true /*visible*/); 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/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 51ed6bb2aa40..f055febca3d5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -276,6 +276,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS; import static com.android.server.devicepolicy.DevicePolicyEngine.DEFAULT_POLICY_SIZE_LIMIT; +import static com.android.server.devicepolicy.DevicePolicyEngine.SYSTEM_SUPERVISION_ROLE; import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE; import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__COPE; import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__DEVICE_OWNER; @@ -16296,6 +16297,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } + /** + * When multiple admins enforce a policy, this method returns an admin according to this order: + * 1. Supervision + * 2. DPC + * + * Otherwise, it returns any other admin. + */ private android.app.admin.EnforcingAdmin getEnforcingAdminInternal(int userId, String identifier) { Objects.requireNonNull(identifier); @@ -16304,16 +16312,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (admins.isEmpty()) { return null; } - - final EnforcingAdmin admin; if (admins.size() == 1) { - admin = admins.iterator().next(); - } else { - Optional<EnforcingAdmin> dpc = admins.stream() - .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst(); - admin = dpc.orElseGet(() -> admins.stream().findFirst().get()); + return admins.iterator().next().getParcelableAdmin(); + } + Optional<EnforcingAdmin> supervision = admins.stream() + .filter(a -> a.hasAuthority( + EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE))) + .findFirst(); + if (supervision.isPresent()) { + return supervision.get().getParcelableAdmin(); + } + Optional<EnforcingAdmin> dpc = admins.stream() + .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst(); + if (dpc.isPresent()) { + return dpc.get().getParcelableAdmin(); } - return admin == null ? null : admin.getParcelableAdmin(); + return admins.iterator().next().getParcelableAdmin(); } private <V> Set<EnforcingAdmin> getEnforcingAdminsForIdentifier(int userId, String identifier) { 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/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java index c7a06b8eec7b..339bac4f768b 100644 --- a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java @@ -259,7 +259,7 @@ public class AdvancedProtectionServiceTest { AdvancedProtectionProvider provider = new AdvancedProtectionProvider() { @Override - public List<AdvancedProtectionFeature> getFeatures() { + public List<AdvancedProtectionFeature> getFeatures(Context context) { return List.of(feature2); } }; @@ -291,7 +291,7 @@ public class AdvancedProtectionServiceTest { AdvancedProtectionProvider provider = new AdvancedProtectionProvider() { @Override - public List<AdvancedProtectionFeature> getFeatures() { + public List<AdvancedProtectionFeature> getFeatures(Context context) { return List.of(feature2); } }; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index bc8b7becc919..902171d614d9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -16844,22 +16844,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void updateAutomaticZenRule_implicitRuleWithoutCPS_disallowedFromApp() throws Exception { setUpRealZenTest(); mService.setCallerIsNormalPackage(); - assertThat(mBinderService.getAutomaticZenRules()).isEmpty(); + assertThat(mBinderService.getAutomaticZenRules().getList()).isEmpty(); // Create an implicit zen rule by calling setNotificationPolicy from an app. mBinderService.setNotificationPolicy(mPkg, new NotificationManager.Policy(0, 0, 0), false); - assertThat(mBinderService.getAutomaticZenRules()).hasSize(1); - Map.Entry<String, AutomaticZenRule> rule = getOnlyElement( - mBinderService.getAutomaticZenRules().entrySet()); - assertThat(rule.getValue().getOwner()).isNull(); - assertThat(rule.getValue().getConfigurationActivity()).isNull(); + assertThat(mBinderService.getAutomaticZenRules().getList()).hasSize(1); + AutomaticZenRule.AzrWithId rule = getOnlyElement( + (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList()); + assertThat(rule.mRule.getOwner()).isNull(); + assertThat(rule.mRule.getConfigurationActivity()).isNull(); // Now try to update said rule (e.g. disable it). Should fail. // We also validate the exception message because NPE could be thrown by all sorts of test // issues (e.g. misconfigured mocks). - rule.getValue().setEnabled(false); + rule.mRule.setEnabled(false); NullPointerException e = assertThrows(NullPointerException.class, - () -> mBinderService.updateAutomaticZenRule(rule.getKey(), rule.getValue(), false)); + () -> mBinderService.updateAutomaticZenRule(rule.mId, rule.mRule, false)); assertThat(e.getMessage()).isEqualTo( "Rule must have a ConditionProviderService and/or configuration activity"); } @@ -16869,24 +16869,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void updateAutomaticZenRule_implicitRuleWithoutCPS_allowedFromSystem() throws Exception { setUpRealZenTest(); mService.setCallerIsNormalPackage(); - assertThat(mBinderService.getAutomaticZenRules()).isEmpty(); + assertThat(mBinderService.getAutomaticZenRules().getList()).isEmpty(); // Create an implicit zen rule by calling setNotificationPolicy from an app. mBinderService.setNotificationPolicy(mPkg, new NotificationManager.Policy(0, 0, 0), false); - assertThat(mBinderService.getAutomaticZenRules()).hasSize(1); - Map.Entry<String, AutomaticZenRule> rule = getOnlyElement( - mBinderService.getAutomaticZenRules().entrySet()); - assertThat(rule.getValue().getOwner()).isNull(); - assertThat(rule.getValue().getConfigurationActivity()).isNull(); + assertThat(mBinderService.getAutomaticZenRules().getList()).hasSize(1); + AutomaticZenRule.AzrWithId rule = getOnlyElement( + (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList()); + assertThat(rule.mRule.getOwner()).isNull(); + assertThat(rule.mRule.getConfigurationActivity()).isNull(); // Now update said rule from Settings (e.g. disable it). Should work! mService.isSystemUid = true; - rule.getValue().setEnabled(false); - mBinderService.updateAutomaticZenRule(rule.getKey(), rule.getValue(), false); + rule.mRule.setEnabled(false); + mBinderService.updateAutomaticZenRule(rule.mId, rule.mRule, false); - Map.Entry<String, AutomaticZenRule> updatedRule = getOnlyElement( - mBinderService.getAutomaticZenRules().entrySet()); - assertThat(updatedRule.getValue().isEnabled()).isFalse(); + AutomaticZenRule.AzrWithId updatedRule = getOnlyElement( + (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList()); + assertThat(updatedRule.mRule.isEnabled()).isFalse(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index 9dc70266bf3d..5347f9a36652 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -382,12 +382,17 @@ public class WindowProcessControllerTests extends WindowTestsBase { assertFalse(tracker.hasResumedActivity(mWpc.mUid)); assertTrue(mWpc.hasForegroundActivities()); - activity.setVisibility(false); activity.setVisibleRequested(false); - activity.setState(STOPPED, "test"); - + if (com.android.window.flags.Flags.useVisibleRequestedForProcessTracker()) { + assertTrue("PAUSING is visible", mWpc.hasVisibleActivities()); + activity.setState(PAUSED, "test"); + } else { + activity.setVisible(false); + } verify(tracker).onAllActivitiesInvisible(mWpc); assertFalse(mWpc.hasVisibleActivities()); + + activity.setState(STOPPED, "test"); assertFalse(mWpc.hasForegroundActivities()); } 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); |