Merge "Revert^2 "InputManagerService: fix requireNonNull failure messages"" into main
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 a9c4a15..6dd7521 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.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.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.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 @@
@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 @@
*/
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 @@
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 @@
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 @@
}
}
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 @@
/** @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 @@
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 @@
}
}
+ 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 @@
}
}
+ 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 @@
@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 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;
+ 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 = 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;
+ 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 @@
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 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 @@
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 @@
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 @@
}
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 @@
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 @@
}
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 @@
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 4cd6d6f..bba21f4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -24820,7 +24820,7 @@
method public android.view.Surface getSurface();
method public boolean isPrivacySensitive();
method public void pause() throws java.lang.IllegalStateException;
- method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
+ method @RequiresPermission(value=android.Manifest.permission.RECORD_AUDIO, conditional=true) public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void registerAudioRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioRecordingCallback);
method public void release();
method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
@@ -24861,7 +24861,7 @@
method public void setVideoProfile(@NonNull android.media.EncoderProfiles.VideoProfile);
method public void setVideoSize(int, int) throws java.lang.IllegalStateException;
method public void setVideoSource(int) throws java.lang.IllegalStateException;
- method public void start() throws java.lang.IllegalStateException;
+ method @RequiresPermission(value=android.Manifest.permission.RECORD_AUDIO, conditional=true) public void start() throws java.lang.IllegalStateException;
method public void stop() throws java.lang.IllegalStateException;
method public void unregisterAudioRecordingCallback(@NonNull android.media.AudioManager.AudioRecordingCallback);
field public static final int MEDIA_ERROR_SERVER_DIED = 100; // 0x64
@@ -34264,6 +34264,7 @@
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 @@
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 @@
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 35720fd..12f302a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7354,7 +7354,15 @@
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 @@
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 @@
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 @@
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 4c82839..daa1902 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1955,6 +1955,10 @@
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 @@
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 62816a2..9cc7b8f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1188,7 +1188,7 @@
/** @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 96b5096..f44c230 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4014,7 +4014,7 @@
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 00fa1c1..bdecbae 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -302,13 +302,23 @@
@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 @@
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/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 2e8031d..2559bd0 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -231,9 +231,9 @@
public static final int START_COMPONENT_OTHER = 5;
/**
- * @see #getMonoticCreationTimeMs
+ * @see #getMonotonicCreationTimeMs
*/
- private long mMonoticCreationTimeMs;
+ private long mMonotonicCreationTimeMs;
/**
* @see #getStartupState
@@ -545,8 +545,8 @@
*
* @hide
*/
- public long getMonoticCreationTimeMs() {
- return mMonoticCreationTimeMs;
+ public long getMonotonicCreationTimeMs() {
+ return mMonotonicCreationTimeMs;
}
/**
@@ -751,14 +751,14 @@
dest.writeParcelable(mStartIntent, flags);
dest.writeInt(mLaunchMode);
dest.writeBoolean(mWasForceStopped);
- dest.writeLong(mMonoticCreationTimeMs);
+ dest.writeLong(mMonotonicCreationTimeMs);
dest.writeInt(mStartComponent);
}
// LINT.ThenChange(:read_parcel)
/** @hide */
public ApplicationStartInfo(long monotonicCreationTimeMs) {
- mMonoticCreationTimeMs = monotonicCreationTimeMs;
+ mMonotonicCreationTimeMs = monotonicCreationTimeMs;
}
/** @hide */
@@ -776,7 +776,7 @@
mStartIntent = other.mStartIntent;
mLaunchMode = other.mLaunchMode;
mWasForceStopped = other.mWasForceStopped;
- mMonoticCreationTimeMs = other.mMonoticCreationTimeMs;
+ mMonotonicCreationTimeMs = other.mMonotonicCreationTimeMs;
mStartComponent = other.mStartComponent;
}
@@ -803,7 +803,7 @@
in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class);
mLaunchMode = in.readInt();
mWasForceStopped = in.readBoolean();
- mMonoticCreationTimeMs = in.readLong();
+ mMonotonicCreationTimeMs = in.readLong();
mStartComponent = in.readInt();
}
// LINT.ThenChange(:write_parcel)
@@ -887,7 +887,7 @@
}
proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped);
- proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonoticCreationTimeMs);
+ proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonotonicCreationTimeMs);
proto.write(ApplicationStartInfoProto.START_COMPONENT, mStartComponent);
proto.end(token);
}
@@ -980,7 +980,7 @@
ApplicationStartInfoProto.WAS_FORCE_STOPPED);
break;
case (int) ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS:
- mMonoticCreationTimeMs = proto.readLong(
+ mMonotonicCreationTimeMs = proto.readLong(
ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS);
break;
case (int) ApplicationStartInfoProto.START_COMPONENT:
@@ -999,7 +999,7 @@
sb.append(prefix)
.append("ApplicationStartInfo ").append(seqSuffix).append(':')
.append('\n')
- .append(" monotonicCreationTimeMs=").append(mMonoticCreationTimeMs)
+ .append(" monotonicCreationTimeMs=").append(mMonotonicCreationTimeMs)
.append('\n')
.append(" pid=").append(mPid)
.append(" realUid=").append(mRealUid)
@@ -1094,7 +1094,7 @@
&& TextUtils.equals(mProcessName, o.mProcessName)
&& timestampsEquals(o)
&& mWasForceStopped == o.mWasForceStopped
- && mMonoticCreationTimeMs == o.mMonoticCreationTimeMs
+ && mMonotonicCreationTimeMs == o.mMonotonicCreationTimeMs
&& mStartComponent == o.mStartComponent;
}
@@ -1102,7 +1102,7 @@
public int hashCode() {
return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
mStartType, mLaunchMode, mPackageName, mProcessName, mStartupTimestampsNs,
- mMonoticCreationTimeMs, mStartComponent);
+ mMonotonicCreationTimeMs, mStartComponent);
}
private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
diff --git a/core/java/android/app/AutomaticZenRule.aidl b/core/java/android/app/AutomaticZenRule.aidl
index feb21d6..92f7d52 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 fa977c9..1ce38ac 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -228,7 +228,7 @@
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 @@
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 @@
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 @@
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 @@
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 6efc4ef..3003b79 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -49,11 +49,14 @@
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.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 @@
@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 @@
? 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 00df724..1f0cd39 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -224,7 +224,7 @@
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 8af5b1b..19fecb9 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -101,14 +101,20 @@
*/
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 050ef23..69e3ef9 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1747,7 +1747,15 @@
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 afe915e..dd87d28 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -594,7 +594,7 @@
* @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 9574824..7f1870b 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -248,7 +248,9 @@
/**
* 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 @@
"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/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index c6d0f61..8c99bd8 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -790,12 +790,6 @@
|| mWindowingMode == WINDOWING_MODE_MULTI_WINDOW;
}
- /** Returns true if the task bounds should persist across power cycles.
- * @hide */
- public boolean persistTaskBounds() {
- return mWindowingMode == WINDOWING_MODE_FREEFORM;
- }
-
/**
* Returns true if the tasks associated with this window configuration are floating.
* Floating tasks are laid out differently as they are allowed to extend past the display bounds
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 720e045..29c84ee 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -163,3 +163,30 @@
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"
+}
+
+flag {
+ namespace: "system_performance"
+ name: "app_start_info_cleanup_old_records"
+ description: "Cleanup old records to reduce size of in memory store."
+ bug: "384539178"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "system_performance"
+ name: "app_start_info_keep_records_sorted"
+ description: "Ensure records are kept sorted to avoid extra work"
+ bug: "384539178"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/admin/StringSetIntersection.java b/core/java/android/app/admin/StringSetIntersection.java
new file mode 100644
index 0000000..5f2031e
--- /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 572bffe..b87ef70 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -412,3 +412,13 @@
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 7aba172..4a15a72 100644
--- a/core/java/android/app/wallpaper.aconfig
+++ b/core/java/android/app/wallpaper.aconfig
@@ -24,6 +24,16 @@
}
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 a13af7f..d7d6262 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -168,6 +168,12 @@
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 49fd634..53966b8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5964,7 +5964,39 @@
*
* @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 53203eb..c5412a9 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -494,10 +494,14 @@
// 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 3411a48..3dbd5b2 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -646,3 +646,10 @@
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 a7fbce5..7dc6afb 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -188,24 +188,6 @@
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 @@
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 4ed0fc0..c3c8c3d 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -600,8 +600,7 @@
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 2e7bc6d..84d96bd 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3504,6 +3504,10 @@
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 ee62dea..6b1e918 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -149,6 +149,11 @@
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 @@
/** @hide */
@Override
- public final native @Nullable IBinder getExtension();
+ public final @Nullable IBinder getExtension() {
+ return mExtension;
+ }
/**
* Set the binder extension.
@@ -1245,7 +1252,12 @@
*
* @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 6cb49b3..4a99285 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -20,6 +20,7 @@
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.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 @@
* @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 @@
* 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 86acb2b..0150d17 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -354,6 +354,15 @@
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 4cbd5be..fce2df1 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -62,6 +62,7 @@
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 @@
}
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 @@
}
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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/InsetsController.java b/core/java/android/view/InsetsController.java
index 6b7b818..7e9dfe6 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1030,10 +1030,18 @@
handlePendingControlRequest(statsToken);
} else {
if (showTypes[0] != 0) {
+ if ((showTypes[0] & ime()) != 0) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_ON_CONTROLS_CHANGED);
+ }
applyAnimation(showTypes[0], true /* show */, false /* fromIme */,
false /* skipsCallbacks */, statsToken);
}
if (hideTypes[0] != 0) {
+ if ((hideTypes[0] & ime()) != 0) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_ON_CONTROLS_CHANGED);
+ }
applyAnimation(hideTypes[0], false /* show */, false /* fromIme */,
// The animation of hiding transient types shouldn't be detected by the
// app. Otherwise, it might be able to react to the callbacks and cause
@@ -1041,6 +1049,10 @@
(hideTypes[0] & ~transientTypes[0]) == 0 /* skipsCallbacks */,
statsToken);
}
+ if ((showTypes[0] & ime()) == 0 && (hideTypes[0] & ime()) == 0) {
+ ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_ON_CONTROLS_CHANGED);
+ }
}
} else {
if (showTypes[0] != 0) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1b57b00..94e9aa7 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1070,9 +1070,9 @@
}
if (mSurfacePackage != null) {
- mSurfaceControlViewHostParent.detach();
mEmbeddedWindowParams.clear();
if (releaseSurfacePackage) {
+ mSurfaceControlViewHostParent.detach();
mSurfacePackage.release();
mSurfacePackage = null;
}
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 3b444c4..fc66e49 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 @@
/**
* 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 0000000..82f3300
--- /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/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 5dadf32..b1ba8b3 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -231,6 +231,7 @@
PHASE_WM_WINDOW_ANIMATING_TYPES_CHANGED,
PHASE_WM_NOTIFY_HIDE_ANIMATION_FINISHED,
PHASE_WM_UPDATE_DISPLAY_WINDOW_ANIMATING_TYPES,
+ PHASE_CLIENT_ON_CONTROLS_CHANGED,
})
@Retention(RetentionPolicy.SOURCE)
@interface Phase {}
@@ -469,6 +470,9 @@
/** The control target reported its animatingTypes back to WindowManagerService. */
int PHASE_WM_UPDATE_DISPLAY_WINDOW_ANIMATING_TYPES =
ImeProtoEnums.PHASE_WM_UPDATE_DISPLAY_WINDOW_ANIMATING_TYPES;
+ /** InsetsController received a control for the IME. */
+ int PHASE_CLIENT_ON_CONTROLS_CHANGED =
+ ImeProtoEnums.PHASE_CLIENT_ON_CONTROLS_CHANGED;
/**
* Called when an IME request is started.
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 2ed9c3a..8f7f941 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.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.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 @@
*/
@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 @@
*/
@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 @@
@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 8162702..8dd0457 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -287,6 +287,17 @@
}
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 e125e25..c25f6b1 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -322,8 +322,18 @@
*/
public static final int CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY = 129;
+ /**
+ * Track the animation of an ongoing call app back into its status bar chip (displaying the call
+ * icon and timer) when returning Home.
+ *
+ * <p>Tracking starts when the RemoteTransition registered to handle the transition from the app
+ * to Home is sent the onAnimationStart() signal and start the animation. Tracking ends when
+ * the animation is fully settled and the transition is complete.</p>
+ */
+ public static final int CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP = 130;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP;
/** @hide */
@IntDef({
@@ -444,7 +454,8 @@
CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND,
CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK,
CUJ_DEFAULT_TASK_TO_TASK_ANIMATION,
- CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY
+ CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY,
+ CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -576,6 +587,7 @@
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;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_RETURN_TO_CALL_CHIP;
}
private Cuj() {
@@ -830,6 +842,8 @@
return "DEFAULT_TASK_TO_TASK_ANIMATION";
case CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY:
return "DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY";
+ case CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP:
+ return "STATUS_BAR_APP_RETURN_TO_CALL_CHIP";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 8151429..f1c47a7 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -760,9 +760,20 @@
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 3472d68..f6e2a4d 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -78,6 +78,13 @@
@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 @@
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 @@
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 @@
mProgressModel.getProgress(),
getMax(),
width,
- segSegGap,
- segPointGap,
+ mSegSegGap,
+ mSegPointGap,
pointRadius,
mHasTrackerIcon,
- segmentMinWidth,
+ mSegMinWidth,
isStyledByProgress,
mTrackerDrawWidth);
} catch (NotEnoughWidthToFitAllPartsException ex) {
@@ -521,11 +528,11 @@
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 b109610..32b283a 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -84,27 +84,6 @@
}
/**
- * 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
}
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/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index 766fbf1..8fbd10c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -64,16 +64,16 @@
private static final boolean DEBUG = false;
// Semantic version
- public static final int MAJOR_VERSION = 0;
- public static final int MINOR_VERSION = 4;
+ public static final int MAJOR_VERSION = 1;
+ public static final int MINOR_VERSION = 0;
public static final int PATCH_VERSION = 0;
// Internal version level
- public static final int DOCUMENT_API_LEVEL = 4;
+ public static final int DOCUMENT_API_LEVEL = 5;
// We also keep a more fine-grained BUILD number, exposed as
// ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD
- static final float BUILD = 0.7f;
+ static final float BUILD = 0.0f;
private static final boolean UPDATE_VARIABLES_BEFORE_LAYOUT = false;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index add9d5b..2025236 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -30,6 +30,7 @@
import com.android.internal.widget.remotecompose.core.operations.DataListIds;
import com.android.internal.widget.remotecompose.core.operations.DataMapIds;
import com.android.internal.widget.remotecompose.core.operations.DataMapLookup;
+import com.android.internal.widget.remotecompose.core.operations.DebugMessage;
import com.android.internal.widget.remotecompose.core.operations.DrawArc;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmapFontText;
@@ -231,6 +232,7 @@
public static final int PATH_COMBINE = 175;
public static final int HAPTIC_FEEDBACK = 177;
public static final int CONDITIONAL_OPERATIONS = 178;
+ public static final int DEBUG_MESSAGE = 179;
///////////////////////////////////////// ======================
@@ -443,6 +445,7 @@
map.put(PATH_COMBINE, PathCombine::read);
map.put(HAPTIC_FEEDBACK, HapticFeedback::read);
map.put(CONDITIONAL_OPERATIONS, ConditionalOperations::read);
+ map.put(DEBUG_MESSAGE, DebugMessage::read);
// map.put(ACCESSIBILITY_CUSTOM_ACTION, CoreSemantics::read);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index 1f02668..a86b62e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -33,6 +33,7 @@
import com.android.internal.widget.remotecompose.core.operations.DataListIds;
import com.android.internal.widget.remotecompose.core.operations.DataMapIds;
import com.android.internal.widget.remotecompose.core.operations.DataMapLookup;
+import com.android.internal.widget.remotecompose.core.operations.DebugMessage;
import com.android.internal.widget.remotecompose.core.operations.DrawArc;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmapFontText;
@@ -1332,7 +1333,7 @@
* @return the nan id of float
*/
public float reserveFloatVariable() {
- int id = mRemoteComposeState.cacheFloat(0f);
+ int id = mRemoteComposeState.nextId();
return Utils.asNan(id);
}
@@ -1870,6 +1871,46 @@
}
/**
+ * Add a scroll modifier
+ *
+ * @param direction HORIZONTAL(0) or VERTICAL(1)
+ * @param positionId the position id as a NaN
+ */
+ public void addModifierScroll(int direction, float positionId) {
+ float max = this.reserveFloatVariable();
+ float notchMax = this.reserveFloatVariable();
+ float touchExpressionDirection =
+ direction != 0 ? RemoteContext.FLOAT_TOUCH_POS_X : RemoteContext.FLOAT_TOUCH_POS_Y;
+
+ ScrollModifierOperation.apply(mBuffer, direction, positionId, max, notchMax);
+ this.addTouchExpression(
+ positionId,
+ 0f,
+ 0f,
+ max,
+ 0f,
+ 3,
+ new float[] {
+ touchExpressionDirection, -1, MUL,
+ },
+ TouchExpression.STOP_GENTLY,
+ null,
+ null);
+ ContainerEnd.apply(mBuffer);
+ }
+
+ /**
+ * Add a scroll modifier
+ *
+ * @param direction HORIZONTAL(0) or VERTICAL(1)
+ */
+ public void addModifierScroll(int direction) {
+ float max = this.reserveFloatVariable();
+ ScrollModifierOperation.apply(mBuffer, direction, 0f, max, 0f);
+ ContainerEnd.apply(mBuffer);
+ }
+
+ /**
* Add a background modifier of provided color
*
* @param color the color of the background
@@ -2464,4 +2505,15 @@
public void addConditionalOperations(byte type, float a, float b) {
ConditionalOperations.apply(mBuffer, type, a, b);
}
+
+ /**
+ * Add a debug message
+ *
+ * @param textId text id
+ * @param value
+ * @param flags
+ */
+ public void addDebugMessage(int textId, float value, int flags) {
+ DebugMessage.apply(mBuffer, textId, value, flags);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DebugMessage.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DebugMessage.java
new file mode 100644
index 0000000..c27bd8b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DebugMessage.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+
+import java.util.List;
+
+/**
+ * This prints debugging message useful for debugging. It should not be use in production documents
+ */
+public class DebugMessage extends Operation implements VariableSupport {
+ private static final int OP_CODE = Operations.DEBUG_MESSAGE;
+ private static final String CLASS_NAME = "DebugMessage";
+ int mTextID;
+ float mFloatValue;
+ float mOutFloatValue;
+ int mFlags = 0;
+
+ public DebugMessage(int textID, float value, int flags) {
+ mTextID = textID;
+ mFloatValue = value;
+ mFlags = flags;
+ }
+
+ @Override
+ public void updateVariables(@NonNull RemoteContext context) {
+ System.out.println("Debug message : updateVariables ");
+ mOutFloatValue =
+ Float.isNaN(mFloatValue)
+ ? context.getFloat(Utils.idFromNan(mFloatValue))
+ : mFloatValue;
+ System.out.println(
+ "Debug message : updateVariables "
+ + Utils.floatToString(mFloatValue, mOutFloatValue));
+ }
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {
+ System.out.println("Debug message : registerListening ");
+
+ if (Float.isNaN(mFloatValue)) {
+ System.out.println("Debug message : registerListening " + mFloatValue);
+ context.listensTo(Utils.idFromNan(mFloatValue), this);
+ }
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mTextID, mFloatValue, mFlags);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "DebugMessage "
+ + mTextID
+ + ", "
+ + Utils.floatToString(mFloatValue, mOutFloatValue)
+ + ", "
+ + mFlags;
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int text = buffer.readInt();
+ float floatValue = buffer.readFloat();
+ int flags = buffer.readInt();
+ DebugMessage op = new DebugMessage(text, floatValue, flags);
+ operations.add(op);
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return OP_CODE;
+ }
+
+ /**
+ * Writes out the operation to the buffer
+ *
+ * @param buffer write the command to the buffer
+ * @param textID id of the text
+ * @param value value to print
+ * @param flags flags to print
+ */
+ public static void apply(@NonNull WireBuffer buffer, int textID, float value, int flags) {
+ buffer.start(OP_CODE);
+ buffer.writeInt(textID);
+ buffer.writeFloat(value);
+ buffer.writeInt(flags);
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("DebugMessage Operations", id(), CLASS_NAME)
+ .description("Print debugging messages")
+ .field(DocumentedOperation.INT, "textId", "test to print")
+ .field(DocumentedOperation.FLOAT, "value", "value of a float to print")
+ .field(DocumentedOperation.INT, "flags", "print additional information");
+ }
+
+ @Override
+ public void apply(@NonNull RemoteContext context) {
+ String str = context.getText(mTextID);
+ System.out.println("Debug message : " + str + " " + mOutFloatValue + " " + mFlags);
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return indent + toString();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
index dee79a4..67d3a65 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
@@ -266,10 +266,12 @@
case TIME_FROM_NOW_SEC:
case TIME_FROM_ARG_SEC:
ctx.loadFloat(mId, (delta) * 1E-3f);
+ ctx.needsRepaint();
break;
case TIME_FROM_ARG_MIN:
case TIME_FROM_NOW_MIN:
ctx.loadFloat(mId, (float) (delta * 1E-3 / 60));
+ ctx.needsRepaint();
break;
case TIME_FROM_ARG_HR:
case TIME_FROM_NOW_HR:
@@ -298,6 +300,7 @@
break;
case TIME_FROM_LOAD_SEC:
ctx.loadFloat(mId, (value - load_time) * 1E-3f);
+ ctx.needsRepaint();
break;
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
index f246729..3e5dff8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
@@ -494,7 +494,7 @@
mTouchUpTime = context.getAnimationTime();
float dest = getStopPosition(value, slope);
- float time = mMaxTime * Math.abs(dest - value) / (2 * mMaxVelocity);
+ float time = Math.min(2, mMaxTime * Math.abs(dest - value) / (2 * mMaxVelocity));
mEasyTouch.config(value, dest, slope, time, mMaxAcceleration, mMaxVelocity, null);
mEasingToStop = true;
context.needsRepaint();
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index 17f4fc8..e76fb06 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -42,6 +42,9 @@
public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachStateChangeListener {
static final boolean USE_VIEW_AREA_CLICK = true; // Use views to represent click areas
+ static final float DEFAULT_FRAME_RATE = 60f;
+ static final float POST_TO_NEXT_FRAME_THRESHOLD = 60f;
+
RemoteComposeDocument mDocument = null;
int mTheme = Theme.LIGHT;
boolean mInActionDown = false;
@@ -53,9 +56,11 @@
long mStart = System.nanoTime();
long mLastFrameDelay = 1;
- float mMaxFrameRate = 60f; // frames per seconds
+ float mMaxFrameRate = DEFAULT_FRAME_RATE; // frames per seconds
long mMaxFrameDelay = (long) (1000 / mMaxFrameRate);
+ long mLastFrameCall = System.currentTimeMillis();
+
private Choreographer mChoreographer;
private Choreographer.FrameCallback mFrameCallback =
new Choreographer.FrameCallback() {
@@ -100,6 +105,7 @@
public void setDocument(RemoteComposeDocument value) {
mDocument = value;
+ mMaxFrameRate = DEFAULT_FRAME_RATE;
mDocument.initializeContext(mARContext);
mDisable = false;
mARContext.setDocLoadTime();
@@ -546,8 +552,25 @@
}
int nextFrame = mDocument.needsRepaint();
if (nextFrame > 0) {
- mLastFrameDelay = Math.max(mMaxFrameDelay, nextFrame);
+ if (mMaxFrameRate >= POST_TO_NEXT_FRAME_THRESHOLD) {
+ mLastFrameDelay = nextFrame;
+ } else {
+ mLastFrameDelay = Math.max(mMaxFrameDelay, nextFrame);
+ }
if (mChoreographer != null) {
+ if (mDebug == 1) {
+ System.err.println(
+ "RC : POST CHOREOGRAPHER WITH "
+ + mLastFrameDelay
+ + " (nextFrame was "
+ + nextFrame
+ + ", max delay "
+ + mMaxFrameDelay
+ + ", "
+ + " max framerate is "
+ + mMaxFrameRate
+ + ")");
+ }
mChoreographer.postFrameCallbackDelayed(mFrameCallback, mLastFrameDelay);
}
if (!mARContext.useChoreographer()) {
@@ -567,6 +590,16 @@
mDisable = true;
invalidate();
}
+ if (mDebug == 1) {
+ long frameDelay = System.currentTimeMillis() - mLastFrameCall;
+ System.err.println(
+ "RC : Delay since last frame "
+ + frameDelay
+ + " ms ("
+ + (1000f / (float) frameDelay)
+ + " fps)");
+ mLastFrameCall = System.currentTimeMillis();
+ }
}
private void drawDisable(Canvas canvas) {
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index a0c8f30..36bda61 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -73,6 +73,7 @@
jmethodID mExecTransact;
jmethodID mGetInterfaceDescriptor;
jmethodID mTransactionCallback;
+ jmethodID mGetExtension;
// Object state.
jfieldID mObject;
@@ -488,8 +489,12 @@
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 @@
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 @@
// 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 @@
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 @@
{ "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 @@
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 e16ce98..9e02004 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 ff5450e..92a0a6a 100644
--- a/core/res/res/drawable/notification_progress.xml
+++ b/core/res/res/drawable/notification_progress.xml
@@ -19,12 +19,9 @@
android:gravity="center_vertical|fill_horizontal">
<com.android.internal.widget.NotificationProgressDrawable
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:segSegGap="@dimen/notification_progress_segSeg_gap"
- android:segPointGap="@dimen/notification_progress_segPoint_gap">
+ android:layout_height="wrap_content">
<segments
android:color="?attr/colorProgressBackgroundNormal"
- android:minWidth="@dimen/notification_progress_segments_min_width"
android:height="@dimen/notification_progress_segments_height"
android:fadedHeight="@dimen/notification_progress_segments_faded_height"
android:cornerRadius="@dimen/notification_progress_segments_corner_radius"/>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d2c993ae..647e3dc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5573,6 +5573,14 @@
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressBar">
+ <!-- Minimum required drawing width for segments. The drawing width refers to the width
+ after the original segments have been adjusted for the neighboring Points and gaps.
+ This is enforced by stretching the segments that are too short. -->
+ <attr name="segMinWidth" format="dimension" />
+ <!-- The gap between two segments. -->
+ <attr name="segSegGap" format="dimension" />
+ <!-- The gap between a segment and a point. -->
+ <attr name="segPointGap" format="dimension" />
<!-- Draws the tracker on a NotificationProgressBar. -->
<attr name="tracker" format="reference" />
<!-- Height of the tracker. -->
@@ -7580,25 +7588,9 @@
<!-- NotificationProgressDrawable class -->
<!-- ================================== -->
- <!-- Drawable used to render a notification progress bar, with segments and points. -->
- <!-- @hide internal use only -->
- <declare-styleable name="NotificationProgressDrawable">
- <!-- The gap between two segments. -->
- <attr name="segSegGap" format="dimension" />
- <!-- The gap between a segment and a point. -->
- <attr name="segPointGap" format="dimension" />
- </declare-styleable>
-
<!-- Used to config the segments of a NotificationProgressDrawable. -->
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressDrawableSegments">
- <!-- TODO: b/390196782 - maybe move this to NotificationProgressBar, because that's the only
- place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap
- above. -->
- <!-- Minimum required drawing width. The drawing width refers to the width after
- the original segments have been adjusted for the neighboring Points and gaps. This is
- enforced by stretching the segments that are too short. -->
- <attr name="minWidth" />
<!-- Height of the solid segments. -->
<attr name="height" />
<!-- Height of the faded segments. -->
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 73681d2..8f13ee1 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -503,6 +503,9 @@
<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 62d89f6..146b386 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 junit.framework.TestCase;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.xmlpull.v1.XmlPullParser;
@@ -102,14 +121,14 @@
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 @@
}
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 @@
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 @@
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 @@
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 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 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 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 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 @@
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 edeea6d..c84c215 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 @@
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 @@
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 bb05910..3e652010 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -29,8 +29,6 @@
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 @@
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 b6a1501..19455a3 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -211,3 +211,10 @@
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 14c152102..90011f4 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.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 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.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 @@
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 @@
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 @@
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 @@
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 @@
getInstrumentation().runOnMainSync {
bubbleBarLayerView.showExpandedView(bubble)
- bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true)
+ bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */)
}
waitForExpandedViewAnimation()
@@ -305,6 +321,7 @@
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 @@
getInstrumentation().runOnMainSync {
bubbleBarLayerView.showExpandedView(bubble)
- bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true)
+ bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */)
}
waitForExpandedViewAnimation()
@@ -330,6 +347,76 @@
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 @@
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 9bb51a8..ef30d89 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 1616707..9076d6a 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 225303b..17ebac9 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/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
index 25b9f8c..f68afea 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.windowingModeToString;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.view.Display.INVALID_DISPLAY;
import android.annotation.IntDef;
import android.app.ActivityManager.RecentTaskInfo;
@@ -65,6 +66,11 @@
private final int mDeskId;
/**
+ * The ID of the display that desk with [mDeskId] is in.
+ */
+ private final int mDeskDisplayId;
+
+ /**
* The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or
* TYPE_DESK.
*/
@@ -109,17 +115,19 @@
* Create new for a stack of fullscreen tasks
*/
public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) {
- return new GroupedTaskInfo(/* deskId = */ -1, List.of(task), null, TYPE_FULLSCREEN,
- /* minimizedFreeformTaskIds = */ null);
+ return new GroupedTaskInfo(/* deskId = */ -1, /* displayId = */ INVALID_DISPLAY,
+ List.of(task), null,
+ TYPE_FULLSCREEN, /* minimizedFreeformTaskIds = */ null);
}
/**
* Create new for a pair of tasks in split screen
*/
public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1,
- @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) {
- return new GroupedTaskInfo(/* deskId = */ -1, List.of(task1, task2), splitBounds,
- TYPE_SPLIT, /* minimizedFreeformTaskIds = */ null);
+ @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) {
+ return new GroupedTaskInfo(/* deskId = */ -1, /* displayId = */ INVALID_DISPLAY,
+ List.of(task1, task2),
+ splitBounds, TYPE_SPLIT, /* minimizedFreeformTaskIds = */ null);
}
/**
@@ -127,9 +135,11 @@
*/
public static GroupedTaskInfo forDeskTasks(
int deskId,
+ int deskDisplayId,
@NonNull List<TaskInfo> tasks,
@NonNull Set<Integer> minimizedFreeformTaskIds) {
- return new GroupedTaskInfo(deskId, tasks, /* splitBounds = */ null, TYPE_DESK,
+ return new GroupedTaskInfo(deskId, deskDisplayId, tasks, /* splitBounds = */ null,
+ TYPE_DESK,
minimizedFreeformTaskIds.stream().mapToInt(i -> i).toArray());
}
@@ -149,11 +159,13 @@
private GroupedTaskInfo(
int deskId,
+ int deskDisplayId,
@NonNull List<TaskInfo> tasks,
@Nullable SplitBounds splitBounds,
@GroupType int type,
@Nullable int[] minimizedFreeformTaskIds) {
mDeskId = deskId;
+ mDeskDisplayId = deskDisplayId;
mTasks = tasks;
mGroupedTasks = null;
mSplitBounds = splitBounds;
@@ -164,6 +176,7 @@
private GroupedTaskInfo(@NonNull List<GroupedTaskInfo> groupedTasks) {
mDeskId = -1;
+ mDeskDisplayId = INVALID_DISPLAY;
mTasks = null;
mGroupedTasks = groupedTasks;
mSplitBounds = null;
@@ -185,6 +198,7 @@
protected GroupedTaskInfo(@NonNull Parcel parcel) {
mDeskId = parcel.readInt();
+ mDeskDisplayId = parcel.readInt();
mTasks = new ArrayList();
final int numTasks = parcel.readInt();
for (int i = 0; i < numTasks; i++) {
@@ -295,6 +309,16 @@
}
/**
+ * Returns the ID of the display that hosts the desk represented by [mDeskId].
+ */
+ public int getDeskDisplayId() {
+ if (mType != TYPE_DESK) {
+ throw new IllegalStateException("No display ID for non desktop task");
+ }
+ return mDeskDisplayId;
+ }
+
+ /**
* Get type of this recents entry. One of {@link GroupType}.
* Note: This is deprecated, callers should use `isBaseType()` and not make assumptions about
* specific group types
@@ -323,6 +347,7 @@
}
GroupedTaskInfo other = (GroupedTaskInfo) obj;
return mDeskId == other.mDeskId
+ && mDeskDisplayId == other.mDeskDisplayId
&& mType == other.mType
&& Objects.equals(mTasks, other.mTasks)
&& Objects.equals(mGroupedTasks, other.mGroupedTasks)
@@ -332,7 +357,7 @@
@Override
public int hashCode() {
- return Objects.hash(mDeskId, mType, mTasks, mGroupedTasks, mSplitBounds,
+ return Objects.hash(mDeskId, mDeskDisplayId, mType, mTasks, mGroupedTasks, mSplitBounds,
Arrays.hashCode(mMinimizedTaskIds));
}
@@ -345,6 +370,7 @@
.collect(Collectors.joining(",\n\t", "[\n\t", "\n]")));
} else {
taskString.append("Desk ID= ").append(mDeskId).append(", ");
+ taskString.append("Desk Display ID=").append(mDeskDisplayId).append(", ");
taskString.append("Tasks=" + mTasks.stream()
.map(taskInfo -> getTaskInfoDumpString(taskInfo))
.collect(Collectors.joining(", ", "[", "]")));
@@ -377,6 +403,7 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mDeskId);
+ parcel.writeInt(mDeskDisplayId);
// We don't use the parcel list methods because we want to only write the TaskInfo state
// and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated
final int tasksSize = mTasks != null ? mTasks.size() : 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 7f8cfae..5e36a10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -332,7 +332,11 @@
@Override
public void onThresholdCrossed() {
- BackAnimationController.this.onThresholdCrossed();
+ if (predictiveBackDelayWmTransition()) {
+ mShellExecutor.execute(BackAnimationController.this::onThresholdCrossed);
+ } else {
+ BackAnimationController.this.onThresholdCrossed();
+ }
}
@Override
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 426c3ee..290ef16 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_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.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 @@
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 da6948d..92007a4 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 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 @@
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 64f54b8..e901e0c 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.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 @@
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 @@
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 dd5a23a..3dce456 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.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 @@
// 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 39a2a7b..d2ad708 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.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 @@
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 1660619..9ac05989 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.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 @@
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 9d4f904..3543556 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 @@
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 3997412..2cc9387 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 @@
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 @@
@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 6c14d83..bccc6dc 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.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 @@
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 dfbf655..7c0f8e1 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 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 @@
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 7adec39..0bd3a54 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.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 @@
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 @@
educationView =
createEducationView(R.layout.bubble_bar_manage_education, root).apply {
+ TypefaceUtils.setTypeface(findViewById(R.id.education_manage_title),
+ TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+ TypefaceUtils.setTypeface(findViewById(R.id.education_manage_text),
+ TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
pivotY = 0f
doOnLayout { it.pivotX = it.width / 2f }
setOnClickListener { hideEducation(animated = true) }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index dd5827a..320de2a 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 @@
## 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 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 cef18f5..c58bb6e 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.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 @@
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 880e143..92f36d0 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 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 @@
// 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 cfcd563..5d8d8b6 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.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 @@
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 @@
}
// 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 @@
}
// 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 01cda6c..e562f33 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 @@
}
/**
+ * @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/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index bb5b5ce..382fa96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -536,17 +536,20 @@
}
/**
- * Represents a desk whose ID is `mDeskId` and contains the tasks in `mDeskTasks`. Some of these
- * tasks are minimized and their IDs are contained in the `mMinimizedDeskTasks` set.
+ * Represents a desk whose ID is `mDeskId` inside the display with `mDisplayId` and contains
+ * the tasks in `mDeskTasks`. Some of these tasks are minimized and their IDs are contained
+ * in the `mMinimizedDeskTasks` set.
*/
private static class Desk {
final int mDeskId;
+ final int mDisplayId;
boolean mHasVisibleTasks = false;
final ArrayList<TaskInfo> mDeskTasks = new ArrayList<>();
final Set<Integer> mMinimizedDeskTasks = new HashSet<>();
- Desk(int deskId) {
+ Desk(int deskId, int displayId) {
mDeskId = deskId;
+ mDisplayId = displayId;
}
void addTask(TaskInfo taskInfo, boolean isMinimized, boolean isVisible) {
@@ -562,7 +565,8 @@
}
GroupedTaskInfo createDeskTaskInfo() {
- return GroupedTaskInfo.forDeskTasks(mDeskId, mDeskTasks, mMinimizedDeskTasks);
+ return GroupedTaskInfo.forDeskTasks(mDeskId, mDisplayId, mDeskTasks,
+ mMinimizedDeskTasks);
}
}
@@ -601,7 +605,8 @@
private Desk getOrCreateDesk(int deskId) {
var desk = mTmpDesks.get(deskId);
if (desk == null) {
- desk = new Desk(deskId);
+ desk = new Desk(deskId,
+ mDesktopUserRepositories.get().getCurrent().getDisplayForDesk(deskId));
mTmpDesks.put(deskId, desk);
}
return desk;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 1853ffa..320a63a 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.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 @@
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/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 23dfb41..cca9821 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -153,7 +153,6 @@
return;
}
mPendingStartDragTransition = null;
- if (aborted) return;
if (mPendingHomeVisibilityUpdate != null) {
notifyHomeVisibilityChanged(mPendingHomeVisibilityUpdate);
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 e28a7fa..003ef1d 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 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
&& 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 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
// 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 @@
) {
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 @@
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 @@
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 @@
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 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 @@
}
// 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 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 @@
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 5e8c1fe..e08ef58 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.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 @@
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 bcf9396..e8019e4 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.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 @@
.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 @@
}
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 @@
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 @@
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 @@
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 @@
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/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
index eb324f7..2382427 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -278,13 +278,16 @@
currentDisplayLayout,
)
)
-
- multiDisplayDragMoveIndicatorController.onDragEnd(
- desktopWindowDecoration.mTaskInfo.taskId,
- transactionSupplier,
- )
}
+ // Call the MultiDisplayDragMoveIndicatorController to clear any active indicator
+ // surfaces. This is necessary even if the drag ended on the same display, as surfaces
+ // may have been created for other displays during the drag.
+ multiDisplayDragMoveIndicatorController.onDragEnd(
+ desktopWindowDecoration.mTaskInfo.taskId,
+ transactionSupplier,
+ )
+
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 91a899c..6fd963f 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.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 @@
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 @@
.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 @@
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 @@
@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 @@
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 @@
int mWidth;
int mHeight;
T mRootView;
+ int mCornerRadius;
+ int mShadowRadius;
void reset() {
mWidth = 0;
@@ -904,6 +938,10 @@
mCaptionTopPadding = 0;
mCustomizableCaptionRegion.setEmpty();
mRootView = null;
+ if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
+ mCornerRadius = INVALID_CORNER_RADIUS;
+ mShadowRadius = INVALID_SHADOW_RADIUS;
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index 509f4f2..8e1cf167 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 @@
}
}
+/**
+ * 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 @@
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 @@
Region(
(dividerRegion.left + dividerRegion.right) / 2,
0,
- displayBounds.right,
+ displayBounds.right * 2, // the receding app can go offscreen
displayBounds.bottom
)
}
@@ -288,7 +300,7 @@
if (portraitPosTop) {
Region(
0,
- 0,
+ -displayBounds.bottom, // the receding app can go offscreen
displayBounds.right,
(dividerRegion.top + dividerRegion.bottom) / 2
)
@@ -297,7 +309,7 @@
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 49d6877..e4183f1 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 @@
}
}
+ /**
+ * 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 aa1b241..33ea0ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
@@ -18,6 +18,8 @@
<!-- Resources used in WindowDecorationTests -->
<dimen name="test_freeform_decor_caption_height">32dp</dimen>
<dimen name="test_freeform_decor_caption_menu_width">216dp</dimen>
+ <dimen name="test_freeform_shadow_radius">20dp</dimen>
+ <dimen name="test_freeform_corner_radius">16dp</dimen>
<dimen name="test_window_decor_left_outset">10dp</dimen>
<dimen name="test_window_decor_top_outset">20dp</dimen>
<dimen name="test_window_decor_right_outset">30dp</dimen>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
index 75f6bda..4e8812d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
@@ -21,6 +21,7 @@
import android.graphics.Rect
import android.os.Parcel
import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
import android.window.IWindowContainerToken
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
@@ -281,7 +282,8 @@
val task2 = createTaskInfo(id = 2)
val taskInfo = GroupedTaskInfo.forDeskTasks(
- /* deskId = */ 500, listOf(task1, task2), setOf())
+ /* deskId = */ 500, DEFAULT_DISPLAY, listOf(task1, task2), setOf()
+ )
assertThat(taskInfo.deskId).isEqualTo(500)
assertThat(taskInfo.getTaskById(1)).isEqualTo(task1)
@@ -335,6 +337,7 @@
): GroupedTaskInfo {
return GroupedTaskInfo.forDeskTasks(
deskId,
+ DEFAULT_DISPLAY,
freeformTaskIds.map { createTaskInfo(it) }.toList(),
minimizedTaskIds.toSet())
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index a122c38..55bff09 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -222,7 +222,7 @@
@Test
@EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX})
- public void startDragToDesktopAborted_doesNotTriggerCallback() throws RemoteException {
+ public void startDragToDesktopAborted_triggersCallback() throws RemoteException {
TransitionInfo info = mock(TransitionInfo.class);
TransitionInfo.Change change = mock(TransitionInfo.Change.class);
ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
@@ -239,7 +239,7 @@
mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ true);
- verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean());
+ verify(mListener).onHomeVisibilityChanged(/* isVisible= */ true);
}
@Test
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 f37f2fb..f7b9c335 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.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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+ public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreSetForFreeform() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ updateRelayoutParams(relayoutParams, taskInfo);
+
+ assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+ public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForFullscreen() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ updateRelayoutParams(relayoutParams, taskInfo);
+
+ assertThat(relayoutParams.mShadowRadiusId).isEqualTo(Resources.ID_NULL);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+ public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForSplit() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ updateRelayoutParams(relayoutParams, taskInfo);
+
+ assertThat(relayoutParams.mShadowRadiusId).isEqualTo(Resources.ID_NULL);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+ public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersSetForFreeform() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ updateRelayoutParams(relayoutParams, taskInfo);
+
+ assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+ public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForFullscreen() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ updateRelayoutParams(relayoutParams, taskInfo);
+
+ assertThat(relayoutParams.mCornerRadiusId).isEqualTo(Resources.ID_NULL);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+ public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForSplit() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ updateRelayoutParams(relayoutParams, taskInfo);
+
+ assertThat(relayoutParams.mCornerRadiusId).isEqualTo(Resources.ID_NULL);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+ public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
+ DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
+ DEFAULT_IS_STATUSBAR_VISIBLE,
+ DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
+ DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+ DEFAULT_IS_DRAGGING,
+ new InsetsState(),
+ DEFAULT_HAS_GLOBAL_FOCUS,
+ mExclusionRegion,
+ /* shouldIgnoreCornerRadius= */ true,
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+ DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+ DEFAULT_IS_MOVING_TO_BACK);
+
+ assertThat(relayoutParams.mCornerRadiusId).isEqualTo(Resources.ID_NULL);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
public void updateRelayoutParams_appHeader_usesTaskDensity() {
final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
index 0798613..24a46aa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -63,6 +63,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoInteractions
import org.mockito.Mockito.`when`
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -210,6 +211,7 @@
eq(taskPositioner),
)
verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ verifyNoInteractions(mockMultiDisplayDragMoveIndicatorController)
}
@Test
@@ -248,6 +250,7 @@
verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any())
Assert.assertEquals(rectAfterEnd, endBounds)
}
@@ -268,6 +271,7 @@
verify(spyDisplayLayout0, never()).localPxToGlobalDp(any(), any())
verify(spyDisplayLayout0, never()).globalDpToLocalPx(any(), any())
+ verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any())
}
@Test
@@ -290,6 +294,7 @@
verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any())
Assert.assertEquals(rectAfterEnd, endBounds)
}
@@ -346,6 +351,7 @@
},
eq(taskPositioner),
)
+ verifyNoInteractions(mockMultiDisplayDragMoveIndicatorController)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 2e95a97..c691dc7 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.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 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.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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
}
@Override
- public void setTaskFocusState(boolean focused) {}
+ public void setTaskFocusState(boolean focused) {
+ }
}
private class TestWindowDecoration extends WindowDecoration<TestView> {
diff --git a/libs/hostgraphics/include/gui/BufferItemConsumer.h b/libs/hostgraphics/include/gui/BufferItemConsumer.h
index 5c96c82..b9ff0a77 100644
--- a/libs/hostgraphics/include/gui/BufferItemConsumer.h
+++ b/libs/hostgraphics/include/gui/BufferItemConsumer.h
@@ -48,6 +48,10 @@
return mConsumer->acquireBuffer(item, presentWhen, 0);
}
+ status_t attachBuffer(BufferItem*, const sp<GraphicBuffer>&) {
+ return INVALID_OPERATION;
+ }
+
status_t releaseBuffer(const BufferItem& item,
const sp<Fence>& releaseFence = Fence::NO_FENCE) {
return OK;
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index a892e88..ab1be7e 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -139,6 +139,7 @@
"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 9d16ee8..7e1eb70 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 @@
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 @@
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 @@
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 @@
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 @@
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 ec17640..ac16f91 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 @@
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 @@
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 d3fc91b..b3badd0 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -203,4 +203,11 @@
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 27d4ac7..104ece6 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 @@
#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 @@
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 cfec24b..009974b 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_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 @@
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 @@
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 dc669a5..aa8cbd1 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -175,6 +175,8 @@
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 4ba206b..66646b2 100644
--- a/libs/hwui/platform/host/WebViewFunctorManager.cpp
+++ b/libs/hwui/platform/host/WebViewFunctorManager.cpp
@@ -45,7 +45,7 @@
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 f9d0f47..ece4530 100644
--- a/libs/hwui/platform/host/renderthread/RenderThread.cpp
+++ b/libs/hwui/platform/host/renderthread/RenderThread.cpp
@@ -27,8 +27,6 @@
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 b248c4b..d5ac993 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 @@
stopDrawing();
setHardwareBuffer(nullptr);
setSurface(nullptr);
+#ifdef __ANDROID__
setSurfaceControl(nullptr);
+#endif
freePrefetchedLayers();
destroyHardwareResources();
mAnimationContext->destroy();
@@ -220,10 +228,15 @@
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 @@
}
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 @@
}
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 @@
instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter, frameNumber,
surfaceControlId);
}
+#endif
}
// Called by choreographer to do an RT-driven animation
@@ -1140,10 +1169,11 @@
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 3de8e05..655aeba 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 @@
*/
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 @@
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 @@
// 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 @@
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 @@
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 ebfd8fde9..e4be5fa 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::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 ad6d54b..23b3ebd 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 @@
namespace android {
class GraphicBuffer;
+class SurfaceControl;
class Surface;
namespace uirenderer {
@@ -80,7 +80,7 @@
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 6ab8e4e..5e40424 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -55,66 +55,6 @@
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 86fddba..f733c7c 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -77,49 +77,6 @@
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 @@
void preload();
- const ASurfaceControlFunctions& getASurfaceControlFunctions() {
- return mASurfaceControlFunctions;
- }
-
void trimMemory(TrimLevel level);
void trimCaches(CacheTrimLevel level);
@@ -244,7 +197,6 @@
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 56d3df3..311d64f 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -29,6 +29,7 @@
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 @@
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 @@
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 @@
/**
* @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 @@
@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 @@
@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 @@
@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 @@
*/
void onDeviceVolumeBehaviorChanged(
@NonNull AudioDeviceAttributes device,
- @AudioManager.DeviceVolumeBehavior int volumeBehavior);
+ @DeviceVolumeBehavior int volumeBehavior);
}
/**
@@ -580,19 +790,19 @@
* @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 @@
@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 32af7c6..4aba491 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -19,6 +19,7 @@
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 @@
* @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 @@
* 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 @@
* 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 @@
* 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 @@
@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 @@
* @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 @@
* 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 @@
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 @@
}
/**
- * @hide
- * Returns {@code true} if the volume device behavior is {@link #DEVICE_VOLUME_BEHAVIOR_FULL}.
- */
- @TestApi
- @RequiresPermission(anyOf = {
- Manifest.permission.MODIFY_AUDIO_ROUTING,
- Manifest.permission.QUERY_AUDIO_STATE,
- Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
- })
- public boolean isFullVolumeDevice() {
- final AudioAttributes attributes = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_MEDIA)
- .build();
- final List<AudioDeviceAttributes> devices = getDevicesForAttributes(attributes);
- for (AudioDeviceAttributes device : devices) {
- if (getDeviceVolumeBehavior(device) == DEVICE_VOLUME_BEHAVIOR_FULL) {
- return true;
- }
- }
- return false;
- }
-
- /**
* Indicate wired accessory connection state change.
* @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
* @param state new connection state: 1 connected, 0 disconnected
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index f3b21bf..3b560b7 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -219,13 +219,14 @@
private static final int DEFAULT_MAX_SUPPORTED_INSTANCES = 32;
private static final int MAX_SUPPORTED_INSTANCES_LIMIT = 256;
- private static final class LazyHolder {
- private static final Range<Integer> SIZE_RANGE = Process.is64Bit()
- ? Range.create(1, 32768)
- : Range.create(1, MediaProperties.resolution_limit_32bit().orElse(4096));
- }
- private static Range<Integer> getSizeRange() {
- return LazyHolder.SIZE_RANGE;
+ private static Range<Integer> SIZE_RANGE;
+ private static synchronized Range<Integer> getSizeRange() {
+ if (SIZE_RANGE == null) {
+ SIZE_RANGE = Process.is64Bit()
+ ? Range.create(1, 32768)
+ : Range.create(1, MediaProperties.resolution_limit_32bit().orElse(4096));
+ }
+ return SIZE_RANGE;
}
// found stuff that is not supported by framework (=> this should not happen)
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 7af78b8..03bcc6a 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -1299,6 +1299,7 @@
* start() or before setOutputFormat().
* @throws IOException if prepare fails otherwise.
*/
+ @RequiresPermission(value = android.Manifest.permission.RECORD_AUDIO, conditional = true)
public void prepare() throws IllegalStateException, IOException
{
if (mPath != null) {
@@ -1337,6 +1338,7 @@
* @throws IllegalStateException if it is called before
* prepare() or when the camera is already in use by another app.
*/
+ @RequiresPermission(value = android.Manifest.permission.RECORD_AUDIO, conditional = true)
public native void start() throws IllegalStateException;
/**
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index be711ac..89e5372 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -27,6 +27,7 @@
import android.widget.LinearLayout;
import androidx.annotation.GravityInt;
+import androidx.annotation.IntDef;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
@@ -34,21 +35,46 @@
import com.google.android.material.button.MaterialButton;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A preference handled a button
*/
public class ButtonPreference extends Preference implements GroupSectionDividerMixin {
+ public static final int TYPE_FILLED = 0;
+ public static final int TYPE_TONAL = 1;
+ public static final int TYPE_OUTLINE = 2;
+
+ @IntDef({TYPE_FILLED, TYPE_TONAL, TYPE_OUTLINE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {
+ }
+
+ public static final int SIZE_NORMAL = 0;
+ public static final int SIZE_LARGE = 1;
+ public static final int SIZE_EXTRA_LARGE = 2;
+
+ @IntDef({SIZE_NORMAL, SIZE_LARGE, SIZE_EXTRA_LARGE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Size {
+ }
+
enum ButtonStyle {
- FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled),
- FILLED_LARGE(0, 1, R.layout.settingslib_expressive_button_filled_large),
- FILLED_EXTRA(0, 2, R.layout.settingslib_expressive_button_filled_extra),
- TONAL_NORMAL(1, 0, R.layout.settingslib_expressive_button_tonal),
- TONAL_LARGE(1, 1, R.layout.settingslib_expressive_button_tonal_large),
- TONAL_EXTRA(1, 2, R.layout.settingslib_expressive_button_tonal_extra),
- OUTLINE_NORMAL(2, 0, R.layout.settingslib_expressive_button_outline),
- OUTLINE_LARGE(2, 1, R.layout.settingslib_expressive_button_outline_large),
- OUTLINE_EXTRA(2, 2, R.layout.settingslib_expressive_button_outline_extra);
+ FILLED_NORMAL(TYPE_FILLED, SIZE_NORMAL, R.layout.settingslib_expressive_button_filled),
+ FILLED_LARGE(TYPE_FILLED, SIZE_LARGE, R.layout.settingslib_expressive_button_filled_large),
+ FILLED_EXTRA(TYPE_FILLED, SIZE_EXTRA_LARGE,
+ R.layout.settingslib_expressive_button_filled_extra),
+ TONAL_NORMAL(TYPE_TONAL, SIZE_NORMAL, R.layout.settingslib_expressive_button_tonal),
+ TONAL_LARGE(TYPE_TONAL, SIZE_LARGE, R.layout.settingslib_expressive_button_tonal_large),
+ TONAL_EXTRA(TYPE_TONAL, SIZE_EXTRA_LARGE,
+ R.layout.settingslib_expressive_button_tonal_extra),
+ OUTLINE_NORMAL(TYPE_OUTLINE, SIZE_NORMAL, R.layout.settingslib_expressive_button_outline),
+ OUTLINE_LARGE(TYPE_OUTLINE, SIZE_LARGE,
+ R.layout.settingslib_expressive_button_outline_large),
+ OUTLINE_EXTRA(TYPE_OUTLINE, SIZE_EXTRA_LARGE,
+ R.layout.settingslib_expressive_button_outline_extra);
private final int mType;
private final int mSize;
@@ -60,7 +86,7 @@
this.mLayoutId = layoutId;
}
- static int getLayoutId(int type, int size) {
+ static int getLayoutId(@Type int type, @Size int size) {
for (ButtonStyle style : values()) {
if (style.mType == type && style.mSize == size) {
return style.mLayoutId;
@@ -266,7 +292,7 @@
* <li>2: extra large</li>
* </ul>
*/
- public void setButtonStyle(int type, int size) {
+ public void setButtonStyle(@Type int type, @Size int size) {
int layoutId = ButtonStyle.getLayoutId(type, size);
setLayoutResource(layoutId);
notifyChanged();
diff --git a/packages/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 0000000..6b534aa
--- /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 54860d4..deda258 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 19181dd..abc458b 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 1f8cfb5..eda281c 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 @@
GENERIC,
LOW,
MEDIUM,
- HIGH
+ HIGH,
+ OFF
}
var iconLevel: BannerStatus = BannerStatus.GENERIC
set(value) {
@@ -87,6 +88,7 @@
1 -> BannerStatus.LOW
2 -> BannerStatus.MEDIUM
3 -> BannerStatus.HIGH
+ 4 -> BannerStatus.OFF
else -> BannerStatus.GENERIC
}
@@ -104,7 +106,10 @@
}
(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 @@
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 @@
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 @@
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/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index 7f4bebc..3355266 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -46,6 +46,8 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
@@ -326,6 +328,15 @@
return false;
}
+ /** Returns the icon color scheme. */
+ @Nullable
+ public String getIconColorScheme(@NonNull Context context) {
+ ensureMetadataNotStale(context);
+ return mMetaData != null
+ ? mMetaData.getString(TileUtils.META_DATA_PREFERENCE_ICON_COLOR_SCHEME, null)
+ : null;
+ }
+
/** Whether the {@link Activity} should be launched in a separate task. */
public boolean isNewTask() {
if (mMetaData != null && mMetaData.containsKey(META_DATA_NEW_TASK)) {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index ac0b9b4..d62ed2f 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -135,6 +135,13 @@
public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
/**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the icon
+ * color scheme. Only available for preferences on the homepage.
+ */
+ public static final String META_DATA_PREFERENCE_ICON_COLOR_SCHEME =
+ "com.android.settings.icon_color_scheme";
+
+ /**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the icon background color. The value may or may not be used by Settings app.
*/
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 55f7317..758ad79 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 1362ffe..86559fd 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 4693377..cf81154 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -398,13 +398,6 @@
}
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 "
@@ -1443,16 +1436,6 @@
}
flag {
- name: "dozeui_scheduling_alarms_background_execution"
- namespace: "systemui"
- description: "Decide whether to execute binder calls to schedule alarms in background thread"
- bug: "330492575"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "media_lockscreen_launch_animation"
namespace : "systemui"
description : "Enable the origin launch animation for UMO when opening on top of lockscreen."
@@ -2136,3 +2119,14 @@
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 ca94482..21ec896 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 @@
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 82e5f5b..cd9fcef 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 @@
*
* @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 @@
// 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 @@
onClick,
interactionSource,
useModifierBasedImplementation,
+ defaultMinSize,
content,
)
}
@@ -182,6 +186,8 @@
*
* @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 @@
// 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 @@
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 @@
val wrappedContent =
remember(content) {
movableContentOf { expandable: Expandable ->
- WrappedContent(expandable, contentColor, content)
+ WrappedContent(expandable, contentColor, defaultMinSize = defaultMinSize, content)
}
}
@@ -306,21 +318,24 @@
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/NestedScrollController.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedScrollController.kt
index 2530a4f..54232e7 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedScrollController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedScrollController.kt
@@ -134,6 +134,7 @@
private var bounds: NestedScrollableBound,
) : DelegatingNode(), NestedScrollConnection {
private var childrenConsumedAnyScroll = false
+ private var availableOnPreScroll = Offset.Zero
init {
delegate(nestedScrollModifierNode(this, dispatcher = null))
@@ -153,12 +154,21 @@
this.bounds = bounds
}
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ availableOnPreScroll = available
+ return Offset.Zero
+ }
+
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
- if (hasConsumedScrollInBounds(consumed.x) || hasConsumedScrollInBounds(consumed.y)) {
+ val consumedIncludingPreScroll = availableOnPreScroll - available
+ if (
+ hasConsumedScrollInBounds(consumedIncludingPreScroll.x) ||
+ hasConsumedScrollInBounds(consumedIncludingPreScroll.y)
+ ) {
childrenConsumedAnyScroll = true
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
index 84370ed..6fb3679 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
@@ -43,7 +43,7 @@
val context = LocalContext.current
val colorScheme = remember(context, isDarkTheme) { platformColorScheme(isDarkTheme, context) }
- val androidColorScheme = remember(context) { AndroidColorScheme(context) }
+ val androidColorScheme = remember(context, isDarkTheme) { AndroidColorScheme(context) }
val typefaceNames = remember(context) { TypefaceNames.get(context) }
val typefaceTokens = remember(typefaceNames) { TypefaceTokens(typefaceNames) }
val typography =
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedScrollControllerTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedScrollControllerTest.kt
index 424af33..377cbe2 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedScrollControllerTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedScrollControllerTest.kt
@@ -23,9 +23,13 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performTouchInput
@@ -103,4 +107,35 @@
rule.waitForIdle()
assertThat(state.isOuterScrollAllowed).isTrue()
}
+
+ @Test
+ fun supportsPreScrolls() {
+ val state = NestedScrollControlState()
+ rule.setContent {
+ Box(
+ Modifier.fillMaxSize()
+ .nestedScrollController(state)
+ .nestedScroll(
+ remember {
+ object : NestedScrollConnection {
+ override fun onPreScroll(
+ available: Offset,
+ source: NestedScrollSource,
+ ): Offset = available
+ }
+ }
+ )
+ .scrollable(rememberScrollableState { 0f }, Orientation.Vertical)
+ )
+ }
+
+ rule.onRoot().performTouchInput {
+ down(topLeft)
+ moveBy(Offset(0f, bottom))
+ }
+ assertThat(state.isOuterScrollAllowed).isFalse()
+
+ rule.onRoot().performTouchInput { up() }
+ assertThat(state.isOuterScrollAllowed).isTrue()
+ }
}
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 3150e94..2b8fe39 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.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 @@
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 @@
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 2d03e2b..0181928 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.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 @@
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 7782705..336f9e1 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.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.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 @@
}
/** 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 @@
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 4b3ebc2..da54cb8 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.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 @@
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 @@
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 @@
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 @@
text = disabledMessage,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.labelSmall,
- modifier = Modifier.basicMarquee(),
+ modifier = Modifier.basicMarquee().clearAndSetSemantics {},
)
}
}
@@ -229,7 +232,7 @@
}
val newValue =
- (value + targetDirection * state.a11yStep).coerceIn(
+ (value + targetDirection * state.step).coerceIn(
state.valueRange.start,
state.valueRange.endInclusive,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 8591375..358635e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -80,17 +80,18 @@
testScope.backgroundScope,
UnconfinedTestDispatcher(),
)
- DisplayRepositoryImpl(
+ val displaysWithDecorRepository =
+ DisplaysWithDecorationsRepositoryImpl(
commandQueue,
windowManager,
testScope.backgroundScope,
displayRepositoryFromLib,
)
- .also {
- verify(displayManager, never()).registerDisplayListener(any(), any())
- // It needs to be called, just once, for the initial value.
- verify(displayManager).getDisplays()
- }
+ DisplayRepositoryImpl(displayRepositoryFromLib, displaysWithDecorRepository).also {
+ verify(displayManager, never()).registerDisplayListener(any(), any())
+ // It needs to be called, just once, for the initial value.
+ verify(displayManager).getDisplays()
+ }
}
@Before
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 6c7783a..bf49d92 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 @@
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
@@ -43,9 +44,10 @@
private val fakeDisplayRepository = kosmos.displayRepository
private val fakePerDisplayInstanceProviderWithTeardown =
kosmos.fakePerDisplayInstanceProviderWithTeardown
+ private val lifecycleManager = kosmos.fakeDisplayInstanceLifecycleManager
private val underTest: PerDisplayInstanceRepositoryImpl<TestPerDisplayInstance> =
- kosmos.fakePerDisplayInstanceRepository
+ kosmos.createPerDisplayInstanceRepository(overrideLifecycleManager = null)
@Before
fun addDisplays() = runBlocking {
@@ -108,6 +110,43 @@
verify(kosmos.dumpManager).registerNormalDumpable(anyString(), any())
}
+ @Test
+ fun perDisplay_afterCustomLifecycleManagerRemovesDisplay_destroyInstanceInvoked() =
+ testScope.runTest {
+ val underTest =
+ kosmos.createPerDisplayInstanceRepository(
+ overrideLifecycleManager = lifecycleManager
+ )
+ // Let's start with both
+ lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID, NON_DEFAULT_DISPLAY_ID)
+
+ val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+ lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID)
+
+ // Now that the lifecycle manager says so, let's make sure it was destroyed
+ assertThat(fakePerDisplayInstanceProviderWithTeardown.destroyed)
+ .containsExactly(instance)
+ }
+
+ @Test
+ fun perDisplay_lifecycleManagerDoesNotContainIt_displayRepositoryDoes_returnsNull() =
+ testScope.runTest {
+ val underTest =
+ kosmos.createPerDisplayInstanceRepository(
+ overrideLifecycleManager = lifecycleManager
+ )
+ // only default display, so getting for the non-default one should fail, despite the
+ // repository having both displays already
+ lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID)
+
+ assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isNull()
+
+ lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID, NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isNotNull()
+ }
+
private fun createDisplay(displayId: Int): Display =
display(type = Display.TYPE_INTERNAL, id = displayId)
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 0f63150..d9990ba 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 @@
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 @@
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 1d42424..8d50cdc 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 @@
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 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
+@EnableSceneContainer
class DetailsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private lateinit var underTest: DetailsViewModel
@@ -45,10 +50,12 @@
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 @@
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 a13b864..1018748 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 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 @@
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isBubble() {
- assertThat(underTest.isBubbleCapable).isFalse()
+ assertThat(underTest.isBubble).isFalse()
}
@Test
@@ -152,4 +153,10 @@
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 1018ded..7449064 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.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
@@ -109,7 +110,7 @@
@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()
@@ -127,7 +128,7 @@
@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)
@@ -174,7 +175,7 @@
@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()
@@ -192,7 +193,7 @@
@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()
@@ -212,7 +213,7 @@
@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()
@@ -257,7 +258,7 @@
@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, "")
@@ -283,7 +284,7 @@
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
@@ -335,11 +336,21 @@
@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 =
@@ -354,6 +365,22 @@
@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()
@@ -372,7 +399,7 @@
val notification: Notification =
Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
- .addAction(Mockito.mock(Notification.Action::class.java))
+ .addAction(mock(Notification.Action::class.java))
.build()
val entry = NotificationEntryBuilder().setNotification(notification).build()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
index 7fa157f..ba2d40b 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 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.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.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 893c179..4c099b3 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 @@
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 6926677..6192399 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.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 d306a5b..0d45335 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.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 @@
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 9fdfca1..9536656 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.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 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 @@
@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 @@
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 @@
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 ccc8be7..6c6ba93 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 789701f5..de48f40 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 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 @@
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 @@
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 41cca19..6ec1f91 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 @@
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 46430af..1f37291 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 @@
}
@Test
+ @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
fun animateToGlanceableHub_affectsAlpha() =
testScope.runTest {
try {
@@ -809,6 +810,7 @@
}
@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 c23e0e7..1cc2911 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 bafa8cf..da5622a 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 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
tracker = FakeUserTracker()
- context.orCreateTestableResources.addOverride(
- R.bool.config_userSwitchingMustGoThroughLoginScreen,
- false,
- )
+ setUserSwitchingMustGoThroughLoginScreen(false)
}
@Test
@@ -308,6 +307,117 @@
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 @@
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 @@
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 04ab988..b1a3caf 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 @@
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 9e8cde3..ffe8e92 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 @@
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.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.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 @@
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 0000000..c6e9b65
--- /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 88d3ecb..d38da7b 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 0000000..2173641
--- /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 0000000..81ef3d7
--- /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 73704f8..f17cc96 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 fd409a5..d3e9db1 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 15519ff..7c6a1b1 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 3fdb98b..b627bdf 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 98e5cea..a7ad48d 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 487d1ce..b981f98 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.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 @@
}
/**
- * 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 0894667..d017754 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -778,18 +778,26 @@
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 @@
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 bd3dfe0..8025d4b 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 @@
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 @@
} 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/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 14b13d1..24b9551 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -286,7 +286,9 @@
mLaunchSourceId);
final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS)
.putExtra(Intent.EXTRA_COMPONENT_NAME,
- ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
+ ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString())
+ .setPackage(mQSSettingsPackageRepository.getSettingsPackageName());
+
mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
mDialogTransitionAnimator.createActivityTransitionController(
dialog));
@@ -588,9 +590,7 @@
com.android.internal.R.color.materialColorOnPrimaryContainer));
}
text.setText(item.getToolName());
- Intent intent = item.getToolIntent()
- .setPackage(mQSSettingsPackageRepository.getSettingsPackageName());
-
+ Intent intent = item.getToolIntent();
view.setOnClickListener(v -> {
final String name = intent.getComponent() != null
? intent.getComponent().flattenToString()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 22d2aaf..87e9784 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.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.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 @@
// 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 @@
}
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 9db7b50..1301fb8 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 2adaec2..6792f31 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 @@
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 @@
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 @@
fun Drawable.asIcon(
contentDescription: ContentDescription? = null,
@DrawableRes res: Int? = null,
-): Icon.Loaded = Icon.Loaded(this, contentDescription, res)
+): Loaded = Loaded(this, contentDescription, res)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 756edb3..5a4b0b0 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 @@
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 39708a7..3520439 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 c201fbf..edee64e 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.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 f331695..02d9c66 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 @@
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
@@ -31,10 +33,11 @@
import com.android.systemui.display.data.repository.DisplayScopeRepositoryImpl
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepositoryImpl
+import com.android.systemui.display.data.repository.DisplaysWithDecorationsRepository
+import com.android.systemui.display.data.repository.DisplaysWithDecorationsRepositoryImpl
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
@@ -83,8 +86,17 @@
): DisplayWindowPropertiesRepository
@Binds
+ fun displaysWithDecorationsRepository(
+ impl: DisplaysWithDecorationsRepositoryImpl
+ ): DisplaysWithDecorationsRepository
+
+ @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/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 051fe7e..01bbf2d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -16,95 +16,25 @@
package com.android.systemui.display.data.repository
-import android.annotation.SuppressLint
-import android.view.IWindowManager
import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.statusbar.CommandQueue
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.scan
-import kotlinx.coroutines.flow.stateIn
-/** Repository for providing access to display related information and events. */
-interface DisplayRepository : DisplayRepositoryFromLib {
-
- /** A [StateFlow] that maintains a set of display IDs that should have system decorations. */
- val displayIdsWithSystemDecorations: StateFlow<Set<Int>>
-}
+/**
+ * Repository for providing access to display related information and events.
+ *
+ * This is now just an interface that extends [DisplayRepositoryFromLib] to avoid changing all the
+ * imports in sysui using this interface.
+ */
+interface DisplayRepository : DisplayRepositoryFromLib, DisplaysWithDecorationsRepository
@SysUISingleton
-@SuppressLint("SharedFlowCreation")
class DisplayRepositoryImpl
@Inject
constructor(
- private val commandQueue: CommandQueue,
- private val windowManager: IWindowManager,
- @Background bgApplicationScope: CoroutineScope,
private val displayRepositoryFromLib: com.android.app.displaylib.DisplayRepository,
-) : DisplayRepositoryFromLib by displayRepositoryFromLib, DisplayRepository {
-
- private val decorationEvents: Flow<Event> = callbackFlow {
- val callback =
- object : CommandQueue.Callbacks {
- override fun onDisplayAddSystemDecorations(displayId: Int) {
- trySend(Event.Add(displayId))
- }
-
- override fun onDisplayRemoveSystemDecorations(displayId: Int) {
- trySend(Event.Remove(displayId))
- }
- }
- commandQueue.addCallback(callback)
- awaitClose { commandQueue.removeCallback(callback) }
- }
-
- private val initialDisplayIdsWithDecorations: Set<Int> =
- displayIds.value.filter { windowManager.shouldShowSystemDecors(it) }.toSet()
-
- /**
- * A [StateFlow] that maintains a set of display IDs that should have system decorations.
- *
- * Updates to the set are triggered by:
- * - Adding displays via [CommandQueue.Callbacks.onDisplayAddSystemDecorations].
- * - Removing displays via [CommandQueue.Callbacks.onDisplayRemoveSystemDecorations].
- * - Removing displays via [displayRemovalEvent] emissions.
- *
- * The set is initialized with displays that qualify for system decorations based on
- * [WindowManager.shouldShowSystemDecors].
- */
- override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> =
- merge(decorationEvents, displayRemovalEvent.map { Event.Remove(it) })
- .scan(initialDisplayIdsWithDecorations) { displayIds: Set<Int>, event: Event ->
- when (event) {
- is Event.Add -> displayIds + event.displayId
- is Event.Remove -> displayIds - event.displayId
- }
- }
- .distinctUntilChanged()
- .stateIn(
- scope = bgApplicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = initialDisplayIdsWithDecorations,
- )
-
- private sealed class Event(val displayId: Int) {
- class Add(displayId: Int) : Event(displayId)
-
- class Remove(displayId: Int) : Event(displayId)
- }
-
- private companion object {
- const val TAG = "DisplayRepository"
- }
-}
+ private val displaysWithDecorationsRepositoryImpl: DisplaysWithDecorationsRepository,
+) :
+ DisplayRepositoryFromLib by displayRepositoryFromLib,
+ DisplaysWithDecorationsRepository by displaysWithDecorationsRepositoryImpl,
+ DisplayRepository
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplaysWithDecorationsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplaysWithDecorationsRepository.kt
new file mode 100644
index 0000000..f4a2ed4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplaysWithDecorationsRepository.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.view.IWindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.CommandQueue
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.stateIn
+
+/** Provides the displays with decorations. */
+interface DisplaysWithDecorationsRepository {
+ /** A [StateFlow] that maintains a set of display IDs that should have system decorations. */
+ val displayIdsWithSystemDecorations: StateFlow<Set<Int>>
+}
+
+@SysUISingleton
+class DisplaysWithDecorationsRepositoryImpl
+@Inject
+constructor(
+ private val commandQueue: CommandQueue,
+ private val windowManager: IWindowManager,
+ @Background bgApplicationScope: CoroutineScope,
+ displayRepository: com.android.app.displaylib.DisplayRepository,
+) : DisplaysWithDecorationsRepository {
+
+ private val decorationEvents: Flow<Event> = callbackFlow {
+ val callback =
+ object : CommandQueue.Callbacks {
+ override fun onDisplayAddSystemDecorations(displayId: Int) {
+ trySend(Event.Add(displayId))
+ }
+
+ override fun onDisplayRemoveSystemDecorations(displayId: Int) {
+ trySend(Event.Remove(displayId))
+ }
+ }
+ commandQueue.addCallback(callback)
+ awaitClose { commandQueue.removeCallback(callback) }
+ }
+
+ private val initialDisplayIdsWithDecorations: Set<Int> =
+ displayRepository.displayIds.value
+ .filter { windowManager.shouldShowSystemDecors(it) }
+ .toSet()
+
+ /**
+ * A [StateFlow] that maintains a set of display IDs that should have system decorations.
+ *
+ * Updates to the set are triggered by:
+ * - Adding displays via [CommandQueue.Callbacks.onDisplayAddSystemDecorations].
+ * - Removing displays via [CommandQueue.Callbacks.onDisplayRemoveSystemDecorations].
+ * - Removing displays via [displayRemovalEvent] emissions.
+ *
+ * The set is initialized with displays that qualify for system decorations based on
+ * [WindowManager.shouldShowSystemDecors].
+ */
+ override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> =
+ merge(decorationEvents, displayRepository.displayRemovalEvent.map { Event.Remove(it) })
+ .scan(initialDisplayIdsWithDecorations) { displayIds: Set<Int>, event: Event ->
+ when (event) {
+ is Event.Add -> displayIds + event.displayId
+ is Event.Remove -> displayIds - event.displayId
+ }
+ }
+ .distinctUntilChanged()
+ .stateIn(
+ scope = bgApplicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = initialDisplayIdsWithDecorations,
+ )
+
+ private sealed class Event(val displayId: Int) {
+ class Add(displayId: Int) : Event(displayId)
+
+ class Remove(displayId: Int) : Event(displayId)
+ }
+}
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 a56710e..86c9d84c 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 212d556..efbae5d 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 7e00c60..0000000
--- 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/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 9def81a..2b16e19 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -18,7 +18,6 @@
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
-import static com.android.systemui.Flags.dozeuiSchedulingAlarmsBackgroundExecution;
import android.app.AlarmManager;
import android.content.Context;
@@ -84,13 +83,7 @@
mBgExecutor = bgExecutor;
mCanAnimateTransition = !params.getDisplayNeedsBlanking();
mDozeParameters = params;
- if (dozeuiSchedulingAlarmsBackgroundExecution()) {
- mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick",
- bgHandler);
- } else {
- mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick",
- handler);
- }
+ mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", bgHandler);
mDozeLog = dozeLog;
}
@@ -184,7 +177,7 @@
mTimeTickScheduled = true;
long time = System.currentTimeMillis();
- long delta = roundToNextMinute(time) - System.currentTimeMillis();
+ long delta = roundToNextMinute(time) - time;
boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
if (scheduled) {
mDozeLog.traceTimeTickScheduled(time, time + delta);
@@ -224,14 +217,8 @@
private void onTimeTick() {
verifyLastTimeTick();
- if (dozeuiSchedulingAlarmsBackgroundExecution()) {
- mHandler.post(mHost::dozeTimeTick);
- } else {
- mHost.dozeTimeTick();
- }
-
// Keep wakelock until a frame has been pushed.
- mHandler.post(mWakeLock.wrap(() -> {}));
+ mHandler.post(mWakeLock.wrap(mHost::dozeTimeTick));
mTimeTickScheduled = false;
scheduleTimeTick();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index c61530c..74cf7e4 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 @@
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.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 @@
bindJankViewModel()
initializeViews()
- if (lightRevealMigration()) {
+ if (ambientAod()) {
LightRevealScrimViewBinder.bind(
lightRevealScrim,
lightRevealScrimViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4755e28..6db2ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1744,6 +1744,9 @@
mJavaAdapter.alwaysCollectFlow(
mWallpaperRepository.getWallpaperSupportsAmbientMode(),
this::setWallpaperSupportsAmbientMode);
+ mJavaAdapter.alwaysCollectFlow(
+ mKeyguardInteractor.getDozeTimeTick(),
+ this::triggerTimeUpdate);
}
@Override
@@ -4056,6 +4059,10 @@
mWallpaperSupportsAmbientMode = supportsAmbientMode;
}
+ private void triggerTimeUpdate(long timeInMillis) {
+ mUpdateMonitor.triggerTimeUpdate();
+ }
+
private static class StartKeyguardExitAnimParams {
@WindowManager.TransitionOldType int mTransit;
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 0b116de..438dff9 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 @@
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 45f8f10..3cf0506 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.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 @@
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 d981eeb..ba6bda8 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.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 @@
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 b15cacf..2eb5bf9 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.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 @@
duration = 250.milliseconds,
startTime = 0.milliseconds,
onStart = {
- if (lightRevealMigration()) {
+ if (ambientAod()) {
currentAlpha = viewState.alpha()
} else {
currentAlpha = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
index c6e4db7..324a3ef 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 @@
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 71cb745..68cd807 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 @@
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 58ddbf6..39482be 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.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/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 237ec7c..6cda192 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -22,6 +22,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
+import static com.android.systemui.Flags.predictiveBackDelayWmTransition;
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED;
@@ -1182,6 +1183,9 @@
return;
} else if (dx > dy && dx > mTouchSlop) {
if (mAllowGesture) {
+ if (!predictiveBackDelayWmTransition() && mBackAnimation != null) {
+ mBackAnimation.onThresholdCrossed();
+ }
if (mBackAnimation == null) {
pilferPointers();
}
@@ -1197,7 +1201,8 @@
// forward touch
mEdgeBackPlugin.onMotionEvent(ev);
dispatchToBackAnimation(ev);
- if (mBackAnimation != null && mThresholdCrossed && !mLastFrameThresholdCrossed) {
+ if (predictiveBackDelayWmTransition() && mBackAnimation != null
+ && mThresholdCrossed && !mLastFrameThresholdCrossed) {
mBackAnimation.onThresholdCrossed();
}
}
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 d20b360..728652e 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 @@
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 d40ecc9..1176095 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.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 @@
val title = tileDetailedViewModel.title
val subTitle = tileDetailedViewModel.subTitle
+ val colors = MaterialTheme.colorScheme
Column(
modifier =
@@ -70,20 +74,33 @@
.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 @@
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 @@
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 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 50012ab..699778f 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.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 @@
sideDrawable: Drawable?,
colors: TileColors,
squishiness: () -> Float,
+ isVisible: () -> Boolean = { true },
accessibilityUiState: AccessibilityUiState? = null,
iconShape: RoundedCornerShape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
toggleClick: (() -> Unit)? = null,
@@ -158,6 +161,7 @@
secondaryLabel = secondaryLabel,
colors = colors,
accessibilityUiState = accessibilityUiState,
+ isVisible = isVisible,
)
if (sideDrawable != null) {
@@ -170,12 +174,14 @@
}
}
+@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 @@
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 @@
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 ccbd8fd..46f05d0 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.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.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 @@
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 @@
),
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 @@
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 @@
)
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun DefaultEditTileGrid(
listState: EditTileListState,
@@ -283,7 +289,9 @@
}
}
} 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 @@
}
@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 @@
}
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun AvailableTileGrid(
tiles: List<AvailableTileGridCell>,
@@ -524,7 +538,7 @@
) {
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 @@
}
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun AvailableTileGridCell(
cell: AvailableTileGridCell,
@@ -803,6 +818,7 @@
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 27e6092..984343a 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 @@
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 6bafd43..e247182 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 @@
bounceableInfo: BounceableInfo,
tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
modifier: Modifier = Modifier,
+ isVisible: () -> Boolean = { true },
detailsViewModel: DetailsViewModel?,
) {
trace(tile.traceName) {
@@ -249,6 +250,7 @@
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 3287443..60ca6e6 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.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 @@
* @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 d263965..5b97175 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 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.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.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 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/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 922afc7..9cae6c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -329,6 +329,7 @@
cookie,
component,
launchCujType = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ returnCujType = Cuj.CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP,
) {
override suspend fun createController(
forLaunch: Boolean
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 74b3f30..b94e7b2 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.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.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 @@
}
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 @@
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 @@
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 @@
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 3168a22..cb1002a 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 @@
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 fcdcc3f..fed9417 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 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 @@
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 @@
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 @@
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 @@
* 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 d06f24f..ba40010 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 @@
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 f653573..8fc6cbe 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 @@
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 1fc8efd..be17ae56c 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.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 @@
return false
}
- override fun isBubbleCapable(): Boolean {
+ override fun isBubble(): Boolean {
return false
}
@@ -113,6 +114,10 @@
return false
}
+ override fun getPeopleNotificationType(): Int {
+ return TYPE_NON_PERSON
+ }
+
override fun isPromotedOngoing(): Boolean {
return false
}
@@ -121,6 +126,11 @@
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")
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 47ffe10..3757ebf 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.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 @@
boolean canDragAndDrop();
- boolean isBubbleCapable();
+ boolean isBubble();
@Nullable String getStyle();
@@ -132,6 +133,8 @@
boolean isAmbient();
+ @PeopleNotificationIdentifier.Companion.PeopleNotificationType int getPeopleNotificationType();
+
/**
* Returns whether this row represents promoted ongoing notification.
*/
@@ -141,6 +144,8 @@
return false;
}
+ void onDragSuccess();
+
/**
* Process a click on a notification bubble icon
*/
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 f78a5dd..a23c5a3 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 @@
return false
}
- override fun isBubbleCapable(): Boolean {
+ override fun isBubble(): Boolean {
return entry.isBubble
}
@@ -137,6 +137,10 @@
return entry.ranking.isAmbient
}
+ override fun getPeopleNotificationType(): Int {
+ return peopleNotificationIdentifier.getPeopleNotificationType(entry)
+ }
+
override fun isPromotedOngoing(): Boolean {
return entry.isPromotedOngoing
}
@@ -145,6 +149,10 @@
return entry.sbn.notification.fullScreenIntent != null
}
+ override fun onDragSuccess() {
+ notificationActivityStarter.onDragSuccess(entry)
+ }
+
override fun onNotificationBubbleIconClicked() {
notificationActivityStarter.onNotificationBubbleIconClicked(entry)
}
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 5cea821..fe2bd34 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 @@
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 2f0701f..3747aba 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 @@
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 1be415d..20169ef 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.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 @@
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 147a5af..619d48f 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 @@
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 d35c3b61..7e19ff1 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.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_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 @@
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 @@
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 @@
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 4689347..5f9678a 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 @@
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 0c2859f..57b0720 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 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 @@
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 1a76d5d..2a3b266 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.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 @@
}
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() {
@@ -2515,7 +2520,11 @@
*/
public void dragAndDropSuccess() {
if (mOnDragSuccessListener != null) {
- mOnDragSuccessListener.onDragSuccess(getEntry());
+ if (NotificationBundleUi.isEnabled()) {
+ mOnDragSuccessListener.onDragSuccess(getEntryAdapter());
+ } else {
+ mOnDragSuccessListener.onDragSuccess(getEntryLegacy());
+ }
}
}
@@ -4002,7 +4011,8 @@
}
} 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;
@@ -4416,6 +4426,12 @@
* @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 292f74a..f36a0cf 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 @@
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.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 @@
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 @@
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 fe3a856..a9ca635 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 @@
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 @@
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 @@
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 @@
// 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 @@
// Create a rotation matrix for the spiral effect
val matrix = Matrix()
matrix.setRotate(rotationAngle, boundsF.centerX(), boundsF.centerY())
- gradient.setLocalMatrix(matrix)
+ gradientShader.setLocalMatrix(matrix)
- outlinePaint.shader = gradient
+ // 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)
+
+ canvas.restore()
}
override fun onBoundsChange(bounds: Rect) {
@@ -149,13 +169,15 @@
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 26d318b..488aa44 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 @@
}
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 @@
@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 e89a76f..c03dc27 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.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 @@
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 @@
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 08c1d71..03990bf 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 @@
// 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 aa69517..48cff74 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 @@
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 @@
*/
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 @@
/** 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 @@
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 5a23f7c..6e8b222 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 @@
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 @@
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 @@
sectionsManager: NotificationSectionsManager,
) {
if (currentState == State.IDLE) {
+ translationOffset = 0f
updateMagneticAndRoundableTargets(swipingRow, stackScrollLayout, sectionsManager)
currentState = State.TARGETS_SET
} else {
@@ -121,36 +135,36 @@
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 @@
)
}
+ 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 @@
}
}
+ 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 @@
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 5959ef1..344dab4 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 @@
/** 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 f3d8ee2..612c19f 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 @@
}
@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 c5a846e..5105e55 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 @@
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 @@
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 @@
// 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 a4ee4ad..9d9f01b 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 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 @@
@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 @@
return;
}
- if (lightRevealMigration()) {
+ if (ambientAod()) {
return;
}
@@ -3103,7 +3103,7 @@
@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 36193bd..3c14462 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.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 @@
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 @@
&& !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 0d43789..8890db3 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.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.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 @@
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 @@
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 1a8ca95..f4afc24 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.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 @@
@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 @@
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 @@
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 e468159..e6c2921 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 @@
private val mobileRepoFactory: Lazy<ConnectionRepoFactory>,
) : MobileConnectionsRepositoryKairos, Dumpable, KairosBuilder by kairosBuilder() {
+ private var dumpCache: DumpCache? = null
+
init {
dumpManager.registerNormalDumpable("MobileConnectionsRepositoryKairos", this)
}
@@ -253,6 +255,7 @@
.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 @@
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 @@
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 0eabb4ec..af4e61a 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 @@
* 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 @@
* 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 @@
* 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 87877b3..6b9c537 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.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.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 @@
context: Context,
mobileMappingsProxy: MobileMappingsProxy,
private val userSetupRepo: UserSetupRepository,
+ private val logFactory: TableLogBufferFactory,
) : MobileIconsInteractor, KairosBuilder by kairosBuilder() {
private val interactorsBySubIdK = buildIncremental {
@@ -158,7 +170,37 @@
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 9a81992..7f778bb 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 @@
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 05b2e0d..b33eafc 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 @@
}
}
+ 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/util/AlarmTimeout.java b/packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java
index a791376..38ac5e6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java
+++ b/packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java
@@ -26,7 +26,6 @@
*/
public class AlarmTimeout implements AlarmManager.OnAlarmListener {
- public static final int MODE_CRASH_IF_SCHEDULED = 0;
public static final int MODE_IGNORE_IF_SCHEDULED = 1;
public static final int MODE_RESCHEDULE_IF_SCHEDULED = 2;
@@ -48,17 +47,11 @@
* Schedules an alarm in {@code timeout} milliseconds in the future.
*
* @param timeout How long to wait from now.
- * @param mode {@link #MODE_CRASH_IF_SCHEDULED}, {@link #MODE_IGNORE_IF_SCHEDULED} or
- * {@link #MODE_RESCHEDULE_IF_SCHEDULED}.
+ * @param mode {@link #MODE_IGNORE_IF_SCHEDULED} or {@link #MODE_RESCHEDULE_IF_SCHEDULED}.
* @return {@code true} when scheduled successfully, {@code false} otherwise.
*/
public boolean schedule(long timeout, int mode) {
switch (mode) {
- case MODE_CRASH_IF_SCHEDULED:
- if (mScheduled) {
- throw new IllegalStateException(mTag + " timeout is already scheduled");
- }
- break;
case MODE_IGNORE_IF_SCHEDULED:
if (mScheduled) {
return false;
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 43d1ef4..32f784f 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.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.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 @@
},
)
},
- accessibilityParams = AccessibilityParams(label = sliderStateModel.label),
+ accessibilityParams = AccessibilityParams(contentDescription = sliderStateModel.label),
modifier =
modifier.pointerInput(Unit) {
coroutineScope {
@@ -172,7 +171,7 @@
@Composable
private fun BoxScope.VolumeIcon(
- drawable: Drawable,
+ icon: Icon.Loaded,
isVisible: Boolean,
modifier: Modifier = Modifier,
) {
@@ -182,6 +181,6 @@
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 ef147c7..3712276 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 @@
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 @@
} 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 @@
} 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 @@
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 @@
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 88a061f..ed59598 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 @@
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 f6aa189..faf0abd 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 @@
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 3d98eba..f645267 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 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.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 @@
) : 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 @@
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 @@
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 @@
override val isEnabled: Boolean
get() = true
- override val a11yStep: Float
+ override val step: Float
get() = 1f
override val disabledMessage: String?
@@ -125,6 +138,9 @@
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 9d32285..9fe0ad4 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 @@
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.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 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 @@
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 @@
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 @@
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 @@
?: 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 @@
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 @@
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 a6c8091..01810f9 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 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 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 @@
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 @@
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 4bc237b..b1d18340 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 @@
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 @@
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 f6582a0..502b311 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.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 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 @@
}
}
val semantics =
- accessibilityParams.createSemantics(
+ createSemantics(
+ accessibilityParams,
animatable.targetValue,
valueRange,
valueChange,
@@ -167,24 +166,18 @@
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 @@
}
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 146488b..108f3ae 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 @@
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/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 8105ae0..206654a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -101,7 +101,6 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.SystemPropertiesHelper;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
@@ -207,7 +206,6 @@
private @Mock ShadeWindowLogger mShadeWindowLogger;
private @Mock SelectedUserInteractor mSelectedUserInteractor;
private @Mock UserTracker.Callback mUserTrackerCallback;
- private @Mock KeyguardInteractor mKeyguardInteractor;
private @Mock KeyguardTransitionBootInteractor mKeyguardTransitionBootInteractor;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallback;
@@ -1499,7 +1497,7 @@
mSystemPropertiesHelper,
() -> mock(WindowManagerLockscreenVisibilityManager.class),
mSelectedUserInteractor,
- mKeyguardInteractor,
+ mKosmos.getKeyguardInteractor(),
mKeyguardTransitionBootInteractor,
mKosmos::getCommunalSceneInteractor,
mock(WindowManagerOcclusionManager.class));
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 cf8278e..82082cc 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.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.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 @@
@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 @@
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 @@
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 @@
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 @@
@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 @@
// 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 @@
@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 @@
// 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 @@
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 @@
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 @@
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 @@
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 8fb2a24..6a9a485 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 @@
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 @@
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 @@
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 @@
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 @@
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 a3616d2..a7f3fdc 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_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 @@
@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 15cb95a..846db63 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 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 @@
@Before
public void SysuiSetup() throws Exception {
+ assertTempFilesAreCreatable();
ProtoLog.REQUIRE_PROTOLOGTOOL = false;
mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver());
mDependency = mSysuiDependency.install();
@@ -211,6 +215,28 @@
}
}
+ 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 338e4be..122e6a5 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.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 @@
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 5ab3b3d..161e062 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,10 @@
package com.android.systemui.display.data.repository
+import com.android.app.displaylib.DisplayInstanceLifecycleManager
+import com.android.app.displaylib.FakeDisplayInstanceLifecycleManager
+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
@@ -67,13 +71,25 @@
Kosmos.Fixture { FakePerDisplayInstanceProviderWithTeardown() }
val Kosmos.perDisplayDumpHelper by Kosmos.Fixture { PerDisplayRepoDumpHelper(dumpManager) }
+val Kosmos.fakeDisplayInstanceLifecycleManager by
+ Kosmos.Fixture { FakeDisplayInstanceLifecycleManager() }
+
val Kosmos.fakePerDisplayInstanceRepository by
Kosmos.Fixture {
- PerDisplayInstanceRepositoryImpl(
- debugName = "fakePerDisplayInstanceRepository",
- instanceProvider = fakePerDisplayInstanceProviderWithTeardown,
- testScope.backgroundScope,
- displayRepository,
- perDisplayDumpHelper,
- )
+ { lifecycleManager: DisplayInstanceLifecycleManager? ->
+ PerDisplayInstanceRepositoryImpl(
+ debugName = "fakePerDisplayInstanceRepository",
+ instanceProvider = fakePerDisplayInstanceProviderWithTeardown,
+ lifecycleManager,
+ testScope.backgroundScope,
+ displayRepository,
+ perDisplayDumpHelper,
+ )
+ }
}
+
+fun Kosmos.createPerDisplayInstanceRepository(
+ overrideLifecycleManager: DisplayInstanceLifecycleManager? = null
+): PerDisplayInstanceRepositoryImpl<TestPerDisplayInstance> {
+ return fakePerDisplayInstanceRepository(overrideLifecycleManager)
+}
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 623989e..c80d738 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.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 @@
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 dc22905..56fd270 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 @@
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/NotificationEntryBuilderKosmos.kt
deleted file mode 100644
index 59f5ecd..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt
+++ /dev/null
@@ -1,90 +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.statusbar.notification
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.app.Person
-import android.content.Intent
-import android.content.applicationContext
-import android.graphics.drawable.Icon
-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.statusbar.notification.icon.IconPack
-import com.android.systemui.statusbar.notification.promoted.setPromotedContent
-import org.mockito.kotlin.mock
-
-fun Kosmos.setIconPackWithMockIconViews(entry: NotificationEntry) {
- entry.icons =
- IconPack.buildPack(
- /* statusBarIcon = */ mock(),
- /* statusBarChipIcon = */ mock(),
- /* shelfIcon = */ mock(),
- /* aodIcon = */ mock(),
- /* source = */ null,
- )
-}
-
-fun Kosmos.buildOngoingCallEntry(
- promoted: Boolean = false,
- block: NotificationEntryBuilder.() -> Unit = {},
-): NotificationEntry =
- buildNotificationEntry(
- tag = "call",
- promoted = promoted,
- style = makeOngoingCallStyle(),
- 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,
- style: Notification.Style? = null,
- block: NotificationEntryBuilder.() -> Unit = {},
-): NotificationEntry =
- NotificationEntryBuilder()
- .apply {
- setTag(tag)
- setFlag(applicationContext, Notification.FLAG_PROMOTED_ONGOING, promoted)
- modifyNotification(applicationContext)
- .setSmallIcon(Icon.createWithContentUri("content://null"))
- .setStyle(style)
- }
- .apply(block)
- .build()
- .also {
- setIconPackWithMockIconViews(it)
- if (promoted) setPromotedContent(it)
- }
-
-private fun Kosmos.makeOngoingCallStyle(): Notification.CallStyle {
- val pendingIntent =
- PendingIntent.getBroadcast(
- applicationContext,
- 0,
- Intent("action"),
- PendingIntent.FLAG_IMMUTABLE,
- )
- val person = Person.Builder().setName("person").build()
- return Notification.CallStyle.forOngoingCall(person, pendingIntent)
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt
new file mode 100644
index 0000000..00c6c94
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt
@@ -0,0 +1,143 @@
+/*
+ * 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 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.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
+
+fun Kosmos.setIconPackWithMockIconViews(entry: NotificationEntry) {
+ entry.icons =
+ IconPack.buildPack(
+ /* statusBarIcon = */ mock(),
+ /* statusBarChipIcon = */ mock(),
+ /* shelfIcon = */ mock(),
+ /* aodIcon = */ mock(),
+ /* source = */ null,
+ )
+}
+
+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 = {},
+): NotificationEntry =
+ buildNotificationEntry(
+ tag = "call",
+ promoted = promoted,
+ style = makeOngoingCallStyle(),
+ block = block,
+ )
+
+fun Kosmos.buildNotificationEntry(
+ tag: String? = null,
+ promoted: Boolean = false,
+ style: Notification.Style? = null,
+ block: NotificationEntryBuilder.() -> Unit = {},
+): NotificationEntry =
+ NotificationEntryBuilder()
+ .apply {
+ setTag(tag)
+ setFlag(applicationContext, Notification.FLAG_PROMOTED_ONGOING, promoted)
+ modifyNotification(applicationContext)
+ .setSmallIcon(Icon.createWithContentUri("content://null"))
+ .setStyle(style)
+ }
+ .apply(block)
+ .build()
+ .also {
+ setIconPackWithMockIconViews(it)
+ if (promoted) setPromotedContent(it)
+ }
+
+private fun Kosmos.makeOngoingCallStyle(): Notification.CallStyle {
+ val pendingIntent =
+ PendingIntent.getBroadcast(
+ applicationContext,
+ 0,
+ Intent("action"),
+ PendingIntent.FLAG_IMMUTABLE,
+ )
+ 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 0000000..e127a70
--- /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 09f9f1c..44d7a22 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 @@
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 @@
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 8c8d024..6e43d79 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 @@
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 88c716e..47016e5 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 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 @@
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 6875619..ed51e05 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 @@
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 @@
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 b956e44..90a9271 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 @@
},
)
}
- 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 c11eb12..81f3702 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 @@
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 @@
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 @@
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 cb2c6e5..faef6a2 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 @@
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 @@
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/packages/WallpaperBackup/Android.bp b/packages/WallpaperBackup/Android.bp
index b8e0d42..b0c0c3a 100644
--- a/packages/WallpaperBackup/Android.bp
+++ b/packages/WallpaperBackup/Android.bp
@@ -48,7 +48,9 @@
static_libs: [
"androidx.test.core",
"androidx.test.rules",
+ "flag-junit",
"mockito-target-minus-junit4",
+ "platform-test-annotations",
"truth",
],
resource_dirs: ["test/res"],
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 44ea9a2..a80a64d 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -16,6 +16,7 @@
package com.android.wallpaperbackup;
+import static android.app.Flags.liveWallpaperContentHandling;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
@@ -27,6 +28,7 @@
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
import static com.android.window.flags.Flags.multiCrop;
+import android.annotation.Nullable;
import android.app.AppGlobals;
import android.app.WallpaperManager;
import android.app.backup.BackupAgent;
@@ -35,6 +37,7 @@
import android.app.backup.BackupManager;
import android.app.backup.BackupRestoreEventLogger.BackupRestoreError;
import android.app.backup.FullBackupDataOutput;
+import android.app.wallpaper.WallpaperDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
@@ -493,11 +496,13 @@
// First parse the live component name so that we know for logging if we care about
// logging errors with the image restore.
- ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
- mSystemHasLiveComponent = wpService != null;
+ Pair<ComponentName, WallpaperDescription> wpService = parseWallpaperComponent(infoStage,
+ "wp");
+ mSystemHasLiveComponent = wpService.first != null;
- ComponentName kwpService = parseWallpaperComponent(infoStage, "kwp");
- mLockHasLiveComponent = kwpService != null;
+ Pair<ComponentName, WallpaperDescription> kwpService = parseWallpaperComponent(
+ infoStage, "kwp");
+ mLockHasLiveComponent = kwpService.first != null;
boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists();
// if there's no separate lock wallpaper, apply the system wallpaper to both screens.
@@ -586,25 +591,39 @@
}
@VisibleForTesting
- void updateWallpaperComponent(ComponentName wpService, int which)
+ void updateWallpaperComponent(Pair<ComponentName, WallpaperDescription> wpService, int which)
throws IOException {
- if (servicePackageExists(wpService)) {
- Slog.i(TAG, "Using wallpaper service " + wpService);
- mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
- if ((which & FLAG_LOCK) != 0) {
- mEventLogger.onLockLiveWallpaperRestored(wpService);
- }
- if ((which & FLAG_SYSTEM) != 0) {
- mEventLogger.onSystemLiveWallpaperRestored(wpService);
+ WallpaperDescription description = wpService.second;
+ boolean hasDescription = (liveWallpaperContentHandling() && description != null);
+ ComponentName component = hasDescription ? description.getComponent() : wpService.first;
+ if (servicePackageExists(component)) {
+ if (hasDescription) {
+ Slog.i(TAG, "Using wallpaper description " + description);
+ mWallpaperManager.setWallpaperComponentWithDescription(description, which);
+ if ((which & FLAG_LOCK) != 0) {
+ mEventLogger.onLockLiveWallpaperRestoredWithDescription(description);
+ }
+ if ((which & FLAG_SYSTEM) != 0) {
+ mEventLogger.onSystemLiveWallpaperRestoredWithDescription(description);
+ }
+ } else {
+ Slog.i(TAG, "Using wallpaper service " + component);
+ mWallpaperManager.setWallpaperComponentWithFlags(component, which);
+ if ((which & FLAG_LOCK) != 0) {
+ mEventLogger.onLockLiveWallpaperRestored(component);
+ }
+ if ((which & FLAG_SYSTEM) != 0) {
+ mEventLogger.onSystemLiveWallpaperRestored(component);
+ }
}
} else {
// If we've restored a live wallpaper, but the component doesn't exist,
// we should log it as an error so we can easily identify the problem
// in reports from users
- if (wpService != null) {
+ if (component != null) {
// TODO(b/268471749): Handle delayed case
- applyComponentAtInstall(wpService, which);
- Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
+ applyComponentAtInstall(component, description, which);
+ Slog.w(TAG, "Wallpaper service " + component + " isn't available. "
+ " Will try to apply later");
}
}
@@ -697,7 +716,6 @@
* (thereby preserving the center point). Then finally, adding any leftover image real-estate
* (i.e. space left over on the horizontal axis) to add parallax effect. Parallax is only added
* if was present in the old device's settings.
- *
*/
private Rect findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl,
Point newDisplaySize, Point bitmapSize, boolean newRtl) {
@@ -976,10 +994,12 @@
return cropHints;
}
- private ComponentName parseWallpaperComponent(File wallpaperInfo, String sectionTag) {
+ private Pair<ComponentName, WallpaperDescription> parseWallpaperComponent(File wallpaperInfo,
+ String sectionTag) {
ComponentName name = null;
+ WallpaperDescription description = null;
try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
- final XmlPullParser parser = Xml.resolvePullParser(stream);
+ final TypedXmlPullParser parser = Xml.resolvePullParser(stream);
int type;
do {
@@ -991,6 +1011,7 @@
name = (parsedName != null)
? ComponentName.unflattenFromString(parsedName)
: null;
+ description = parseWallpaperDescription(parser, name);
break;
}
}
@@ -998,9 +1019,30 @@
} catch (Exception e) {
// Whoops; can't process the info file at all. Report failure.
Slog.w(TAG, "Failed to parse restored component: " + e.getMessage());
- return null;
+ return new Pair<>(null, null);
}
- return name;
+ return new Pair<>(name, description);
+ }
+
+ // Copied from com.android.server.wallpaper.WallpaperDataParser
+ private WallpaperDescription parseWallpaperDescription(TypedXmlPullParser parser,
+ ComponentName component) throws XmlPullParserException, IOException {
+
+ WallpaperDescription description = null;
+ int type = parser.next();
+ if (type == XmlPullParser.START_TAG && "description".equals(parser.getName())) {
+ // Always read the description if it's there - there may be one from a previous save
+ // with content handling enabled even if it's enabled now
+ description = WallpaperDescription.restoreFromXml(parser);
+ if (liveWallpaperContentHandling()) {
+ // null component means that wallpaper was last saved without content handling, so
+ // populate description from saved component
+ if (description.getComponent() == null) {
+ description = description.toBuilder().setComponent(component).build();
+ }
+ }
+ }
+ return description;
}
private int getAttributeInt(XmlPullParser parser, String name, int defValue) {
@@ -1037,14 +1079,16 @@
// Intentionally blank
}
- private void applyComponentAtInstall(ComponentName componentName, int which) {
- PackageMonitor packageMonitor = getWallpaperPackageMonitor(
- componentName, which);
+ private void applyComponentAtInstall(ComponentName componentName,
+ @Nullable WallpaperDescription description, int which) {
+ PackageMonitor packageMonitor = getWallpaperPackageMonitor(componentName, description,
+ which);
packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
}
@VisibleForTesting
- PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) {
+ PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
+ @Nullable WallpaperDescription description, int which) {
return new PackageMonitor() {
@Override
public void onPackageAdded(String packageName, int uid) {
@@ -1068,7 +1112,33 @@
return;
}
- if (componentName.getPackageName().equals(packageName)) {
+ boolean useDescription = (liveWallpaperContentHandling() && description != null
+ && description.getComponent() != null);
+ if (useDescription && description.getComponent().getPackageName().equals(
+ packageName)) {
+ Slog.d(TAG, "Applying description " + description);
+ boolean success = mWallpaperManager.setWallpaperComponentWithDescription(
+ description, which);
+ WallpaperEventLogger logger = new WallpaperEventLogger(
+ mBackupManager.getDelayedRestoreLogger());
+ if (success) {
+ if ((which & FLAG_SYSTEM) != 0) {
+ logger.onSystemLiveWallpaperRestoredWithDescription(description);
+ }
+ if ((which & FLAG_LOCK) != 0) {
+ logger.onLockLiveWallpaperRestoredWithDescription(description);
+ }
+ } else {
+ if ((which & FLAG_SYSTEM) != 0) {
+ logger.onSystemLiveWallpaperRestoreFailed(
+ WallpaperEventLogger.ERROR_SET_DESCRIPTION_EXCEPTION);
+ }
+ if ((which & FLAG_LOCK) != 0) {
+ logger.onLockLiveWallpaperRestoreFailed(
+ WallpaperEventLogger.ERROR_SET_DESCRIPTION_EXCEPTION);
+ }
+ }
+ } else if (componentName.getPackageName().equals(packageName)) {
Slog.d(TAG, "Applying component " + componentName);
boolean success = mWallpaperManager.setWallpaperComponentWithFlags(
componentName, which);
@@ -1191,4 +1261,4 @@
void setBackupManagerForTesting(BackupManager backupManager) {
mBackupManager = backupManager;
}
-}
\ No newline at end of file
+}
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
index b25f95a..69469e4 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
@@ -16,12 +16,14 @@
package com.android.wallpaperbackup;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WallpaperInfo;
import android.app.backup.BackupManager;
import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
import android.app.backup.BackupRestoreEventLogger.BackupRestoreError;
+import android.app.wallpaper.WallpaperDescription;
import android.content.ComponentName;
import com.android.internal.annotations.VisibleForTesting;
@@ -53,6 +55,16 @@
@VisibleForTesting
static final String WALLPAPER_LIVE_LOCK = "wlp_live_lock";
+ /* Live component used as system (or home) screen wallpaper */
+ @BackupRestoreDataType
+ @VisibleForTesting
+ static final String WALLPAPER_DESCRIPTION_SYSTEM = "wlp_description_system";
+
+ /* Live component used as lock screen wallpaper */
+ @BackupRestoreDataType
+ @VisibleForTesting
+ static final String WALLPAPER_DESCRIPTION_LOCK = "wlp_description_lock";
+
@BackupRestoreError
static final String ERROR_INELIGIBLE = "ineligible";
@BackupRestoreError
@@ -64,6 +76,8 @@
@BackupRestoreError
static final String ERROR_SET_COMPONENT_EXCEPTION = "exception_in_set_component";
@BackupRestoreError
+ static final String ERROR_SET_DESCRIPTION_EXCEPTION = "exception_in_set_description";
+ @BackupRestoreError
static final String ERROR_LIVE_PACKAGE_NOT_INSTALLED = "live_pkg_not_installed_in_restore";
private final BackupRestoreEventLogger mLogger;
@@ -115,11 +129,11 @@
}
void onSystemImageWallpaperRestored() {
- logRestoreSuccessInternal(WALLPAPER_IMG_SYSTEM, /* liveComponentWallpaperInfo */ null);
+ logRestoreSuccessInternal(WALLPAPER_IMG_SYSTEM, (ComponentName) null);
}
void onLockImageWallpaperRestored() {
- logRestoreSuccessInternal(WALLPAPER_IMG_LOCK, /* liveComponentWallpaperInfo */ null);
+ logRestoreSuccessInternal(WALLPAPER_IMG_LOCK, (ComponentName) null);
}
void onSystemLiveWallpaperRestored(ComponentName wpService) {
@@ -146,6 +160,13 @@
logRestoreFailureInternal(WALLPAPER_LIVE_LOCK, error);
}
+ void onSystemLiveWallpaperRestoredWithDescription(@NonNull WallpaperDescription description) {
+ logRestoreSuccessInternal(WALLPAPER_DESCRIPTION_SYSTEM, description);
+ }
+
+ void onLockLiveWallpaperRestoredWithDescription(@NonNull WallpaperDescription description) {
+ logRestoreSuccessInternal(WALLPAPER_DESCRIPTION_LOCK, description);
+ }
/**
@@ -168,15 +189,17 @@
*/
void onRestoreException(Exception exception) {
String error = exception.getClass().getName();
- if (!mProcessedDataTypes.contains(WALLPAPER_IMG_SYSTEM) && !mProcessedDataTypes.contains(
- WALLPAPER_LIVE_SYSTEM)) {
+ if (!(mProcessedDataTypes.contains(WALLPAPER_IMG_SYSTEM) || mProcessedDataTypes.contains(
+ WALLPAPER_LIVE_SYSTEM) || mProcessedDataTypes.contains(
+ WALLPAPER_DESCRIPTION_SYSTEM))) {
mLogger.logItemsRestoreFailed(WALLPAPER_IMG_SYSTEM, /* count */ 1, error);
}
- if (!mProcessedDataTypes.contains(WALLPAPER_IMG_LOCK) && !mProcessedDataTypes.contains(
- WALLPAPER_LIVE_LOCK)) {
+ if (!(mProcessedDataTypes.contains(WALLPAPER_IMG_LOCK) || mProcessedDataTypes.contains(
+ WALLPAPER_LIVE_LOCK) || mProcessedDataTypes.contains(WALLPAPER_DESCRIPTION_LOCK))) {
mLogger.logItemsRestoreFailed(WALLPAPER_IMG_LOCK, /* count */ 1, error);
}
}
+
private void logBackupSuccessInternal(@BackupRestoreDataType String which,
@Nullable WallpaperInfo liveComponentWallpaperInfo) {
mLogger.logItemsBackedUp(which, /* count */ 1);
@@ -197,6 +220,13 @@
mProcessedDataTypes.add(which);
}
+ private void logRestoreSuccessInternal(@BackupRestoreDataType String which,
+ @NonNull WallpaperDescription description) {
+ mLogger.logItemsRestored(which, /* count */ 1);
+ logRestoredLiveWallpaperDescription(which, description);
+ mProcessedDataTypes.add(which);
+ }
+
private void logRestoreFailureInternal(@BackupRestoreDataType String which,
@BackupRestoreError String error) {
mLogger.logItemsRestoreFailed(which, /* count */ 1, error);
@@ -216,4 +246,11 @@
mLogger.logRestoreMetadata(wallpaperType, wpService.getClassName());
}
}
+
+ private void logRestoredLiveWallpaperDescription(@BackupRestoreDataType String wallpaperType,
+ WallpaperDescription description) {
+ if (description != null) {
+ mLogger.logRestoreMetadata(wallpaperType, description.toString());
+ }
+ }
}
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index f5fb644..c9f1b58 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -16,6 +16,7 @@
package com.android.wallpaperbackup;
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
@@ -27,6 +28,8 @@
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_DESCRIPTION_LOCK;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_DESCRIPTION_SYSTEM;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOCK;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
@@ -54,6 +57,7 @@
import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.FullBackupDataOutput;
+import android.app.wallpaper.WallpaperDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -63,7 +67,10 @@
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.wallpaper.WallpaperService;
+import android.util.Pair;
import android.util.SparseArray;
import android.util.Xml;
@@ -79,6 +86,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
@@ -113,12 +121,15 @@
@Mock
private BackupManager mBackupManager;
- @Rule
- public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
private ContextWithServiceOverrides mContext;
private IsolatedWallpaperBackupAgent mWallpaperBackupAgent;
private ComponentName mWallpaperComponent;
+ private WallpaperDescription mWallpaperDescription;
+
+ private final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Rule
+ public RuleChain mRuleChain = RuleChain.outerRule(new SetFlagsRule()).around(mTemporaryFolder);
@Before
public void setUp() {
@@ -135,6 +146,8 @@
BackupAnnotations.OperationType.BACKUP);
mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, "");
+ mWallpaperDescription = new WallpaperDescription.Builder().setComponent(
+ mWallpaperComponent).setId("id").build();
}
@After
@@ -366,11 +379,128 @@
}
@Test
- public void testUpdateWallpaperComponent_systemAndLock() throws IOException {
- mWallpaperBackupAgent.mIsDeviceInRestore = true;
- mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ public void testUpdateWallpaperComponent_immediate_systemAndLock() throws IOException {
+ mWallpaperBackupAgent.mPackageExists = true;
+
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
/* which */ FLAG_LOCK | FLAG_SYSTEM);
+ assertThat(mWallpaperBackupAgent.mGetPackageMonitorCallCount).isEqualTo(0);
+ verify(mWallpaperManager, times(1))
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+ verify(mWallpaperManager, never()).clear(anyInt());
+ }
+
+ @Test
+ public void testUpdateWallpaperComponent_immediate_systemOnly()
+ throws IOException {
+ mWallpaperBackupAgent.mPackageExists = true;
+
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
+ /* which */ FLAG_SYSTEM);
+
+ assertThat(mWallpaperBackupAgent.mGetPackageMonitorCallCount).isEqualTo(0);
+ verify(mWallpaperManager, times(1))
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+ verify(mWallpaperManager, never()).clear(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ public void testUpdateWallpaperDescription_immediate_systemAndLock()
+ throws IOException {
+ mWallpaperBackupAgent.mPackageExists = true;
+
+ mWallpaperBackupAgent.updateWallpaperComponent(
+ new Pair<>(mWallpaperComponent, mWallpaperDescription), /* which */
+ FLAG_LOCK | FLAG_SYSTEM);
+
+ verify(mWallpaperManager, times(1))
+ .setWallpaperComponentWithDescription(mWallpaperDescription,
+ FLAG_LOCK | FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_LOCK);
+ verify(mWallpaperManager, never()).clear(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ public void testUpdateWallpaperDescription_immediate_systemOnly() throws IOException {
+ mWallpaperBackupAgent.mPackageExists = true;
+
+ mWallpaperBackupAgent.updateWallpaperComponent(
+ new Pair<>(mWallpaperComponent, mWallpaperDescription), /* which */ FLAG_SYSTEM);
+
+ verify(mWallpaperManager, times(1))
+ .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_LOCK);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithDescription(mWallpaperDescription,
+ FLAG_LOCK | FLAG_SYSTEM);
+ verify(mWallpaperManager, never()).clear(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ public void testUpdateWallpaperDescription_delayed_systemAndLock()
+ throws IOException {
+ mWallpaperBackupAgent.mIsDeviceInRestore = true;
+ mWallpaperBackupAgent.updateWallpaperComponent(
+ new Pair<>(mWallpaperComponent, mWallpaperDescription), /* which */
+ FLAG_LOCK | FLAG_SYSTEM);
+
+ // Imitate wallpaper component installation.
+ mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+ /* uid */0);
+ verify(mWallpaperManager, times(1))
+ .setWallpaperComponentWithDescription(mWallpaperDescription,
+ FLAG_LOCK | FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_LOCK);
+ verify(mWallpaperManager, never()).clear(anyInt());
+ }
+
+ @Test
+ @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ public void testUpdateWallpaperDescription_delayed_systemOnly() throws IOException {
+ mWallpaperBackupAgent.mIsDeviceInRestore = true;
+
+ mWallpaperBackupAgent.updateWallpaperComponent(
+ new Pair<>(mWallpaperComponent, mWallpaperDescription), /* which */ FLAG_SYSTEM);
+
+ // Imitate wallpaper component installation.
+ mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+ /* uid */0);
+
+ verify(mWallpaperManager, times(1))
+ .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_LOCK);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithDescription(mWallpaperDescription,
+ FLAG_LOCK | FLAG_SYSTEM);
+ verify(mWallpaperManager, never()).clear(anyInt());
+ }
+
+ @Test
+ public void testUpdateWallpaperComponent_delayed_systemAndLock() throws IOException {
+ mWallpaperBackupAgent.mIsDeviceInRestore = true;
+
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
+ /* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
/* uid */0);
@@ -384,13 +514,12 @@
}
@Test
- public void testUpdateWallpaperComponent_systemOnly()
+ public void testUpdateWallpaperComponent_delayed_systemOnly()
throws IOException {
mWallpaperBackupAgent.mIsDeviceInRestore = true;
- mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
/* which */ FLAG_SYSTEM);
-
// Imitate wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
/* uid */0);
@@ -405,11 +534,11 @@
}
@Test
- public void testUpdateWallpaperComponent_deviceNotInRestore_doesNotApply()
+ public void testUpdateWallpaperComponent_delayed_deviceNotInRestore_doesNotApply()
throws IOException {
mWallpaperBackupAgent.mIsDeviceInRestore = false;
- mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
/* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate wallpaper component installation.
@@ -421,11 +550,11 @@
}
@Test
- public void testUpdateWallpaperComponent_differentPackageInstalled_doesNotApply()
+ public void testUpdateWallpaperComponent_delayed_differentPackageInstalled_doesNotApply()
throws IOException {
mWallpaperBackupAgent.mIsDeviceInRestore = false;
- mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
/* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate "wrong" wallpaper component installation.
@@ -745,9 +874,8 @@
}
@Test
- public void testUpdateWallpaperComponent_delayedRestore_logsSuccess() throws Exception {
+ public void testUpdateWallpaperComponent_delayed_succeeds_logsSuccess() throws Exception {
mWallpaperBackupAgent.mIsDeviceInRestore = true;
- when(mWallpaperManager.setWallpaperComponent(any())).thenReturn(true);
when(mWallpaperManager.setWallpaperComponentWithFlags(any(), eq(FLAG_LOCK | FLAG_SYSTEM)))
.thenReturn(true);
BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
@@ -755,7 +883,7 @@
when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
- mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
/* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -771,15 +899,41 @@
@Test
- public void testUpdateWallpaperComponent_delayedRestoreFails_logsFailure() throws Exception {
+ @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ public void testUpdateWallpaperDescription_delayed_succeeds_logsSuccess() throws Exception {
mWallpaperBackupAgent.mIsDeviceInRestore = true;
- when(mWallpaperManager.setWallpaperComponent(any())).thenReturn(false);
+ when(mWallpaperManager.setWallpaperComponentWithDescription(any(),
+ eq(FLAG_LOCK | FLAG_SYSTEM))).thenReturn(true);
BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
BackupAnnotations.OperationType.RESTORE);
when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
- mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(null, mWallpaperDescription),
+ /* which */ FLAG_LOCK | FLAG_SYSTEM);
+ // Imitate wallpaper component installation.
+ mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+ /* uid */0);
+
+ DataTypeResult system = getLoggingResult(WALLPAPER_DESCRIPTION_SYSTEM,
+ logger.getLoggingResults());
+ DataTypeResult lock = getLoggingResult(WALLPAPER_DESCRIPTION_LOCK,
+ logger.getLoggingResults());
+ assertThat(system).isNotNull();
+ assertThat(system.getSuccessCount()).isEqualTo(1);
+ assertThat(lock).isNotNull();
+ assertThat(lock.getSuccessCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testUpdateWallpaperComponent_delayed_fails_logsFailure() throws Exception {
+ mWallpaperBackupAgent.mIsDeviceInRestore = true;
+ BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
+ BackupAnnotations.OperationType.RESTORE);
+ when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
+ mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
+
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
/* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -793,7 +947,29 @@
}
@Test
- public void testUpdateWallpaperComponent_delayedRestore_packageNotInstalled_logsFailure()
+ @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ public void testUpdateWallpaperDescription_delayed_fails_logsFailure() throws Exception {
+ mWallpaperBackupAgent.mIsDeviceInRestore = true;
+ BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
+ BackupAnnotations.OperationType.RESTORE);
+ when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
+ mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
+
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(null, mWallpaperDescription),
+ /* which */ FLAG_LOCK | FLAG_SYSTEM);
+ // Imitate wallpaper component installation.
+ mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+ /* uid */0);
+
+ DataTypeResult system = getLoggingResult(WALLPAPER_LIVE_SYSTEM, logger.getLoggingResults());
+ assertThat(system).isNotNull();
+ assertThat(system.getFailCount()).isEqualTo(1);
+ assertThat(system.getErrors()).containsKey(
+ WallpaperEventLogger.ERROR_SET_DESCRIPTION_EXCEPTION);
+ }
+
+ @Test
+ public void testUpdateWallpaperComponent_delayed_packageNotInstalled_logsFailure()
throws Exception {
mWallpaperBackupAgent.mIsDeviceInRestore = false;
BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
@@ -801,7 +977,7 @@
when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
- mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+ mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
/* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate wallpaper component installation.
@@ -990,6 +1166,8 @@
List<File> mBackedUpFiles = new ArrayList<>();
PackageMonitor mWallpaperPackageMonitor;
boolean mIsDeviceInRestore = false;
+ boolean mPackageExists = false;
+ int mGetPackageMonitorCallCount = 0;
@Override
protected void backupFile(File file, FullBackupDataOutput data) {
@@ -998,7 +1176,7 @@
@Override
boolean servicePackageExists(ComponentName comp) {
- return false;
+ return mPackageExists;
}
@Override
@@ -1007,8 +1185,11 @@
}
@Override
- PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) {
- mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, which);
+ PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
+ WallpaperDescription description, int which) {
+ mGetPackageMonitorCallCount++;
+ mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, description,
+ which);
return mWallpaperPackageMonitor;
}
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
index 383bf2f..09aa23e 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
@@ -16,6 +16,10 @@
package com.android.wallpaperbackup;
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
+
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_DESCRIPTION_LOCK;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_DESCRIPTION_SYSTEM;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOCK;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
@@ -31,16 +35,20 @@
import android.app.backup.BackupAnnotations;
import android.app.backup.BackupManager;
import android.app.backup.BackupRestoreEventLogger;
+import android.app.wallpaper.WallpaperDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.wallpaper.WallpaperService;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -63,6 +71,10 @@
private WallpaperEventLogger mWallpaperEventLogger;
private WallpaperInfo mWallpaperInfo;
+ private WallpaperDescription mWallpaperDescription;
+
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() throws Exception {
@@ -73,6 +85,8 @@
mWallpaperInfo = getWallpaperInfo();
mWallpaperEventLogger = new WallpaperEventLogger(mMockBackupManager, mMockBackupAgent);
+ mWallpaperDescription = new WallpaperDescription.Builder().setComponent(
+ mWallpaperInfo.getComponent()).build();
}
@Test
@@ -263,6 +277,19 @@
}
@Test
+ @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ public void onSystemLiveWallpaperRestoredWithDescription_logsSuccess() {
+ setUpLoggerForRestore();
+
+ mWallpaperEventLogger.onSystemLiveWallpaperRestoredWithDescription(mWallpaperDescription);
+ BackupRestoreEventLogger.DataTypeResult result = getLogsForType(
+ WALLPAPER_DESCRIPTION_SYSTEM);
+
+ assertThat(result).isNotNull();
+ assertThat(result.getSuccessCount()).isEqualTo(1);
+ }
+
+ @Test
public void onLockLiveWallpaperRestored_logsSuccess() {
setUpLoggerForRestore();
@@ -274,6 +301,17 @@
}
@Test
+ public void onLockLiveWallpaperRestoredWithDescription_logsSuccess() {
+ setUpLoggerForRestore();
+
+ mWallpaperEventLogger.onLockLiveWallpaperRestoredWithDescription(mWallpaperDescription);
+ BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_DESCRIPTION_LOCK);
+
+ assertThat(result).isNotNull();
+ assertThat(result.getSuccessCount()).isEqualTo(1);
+ }
+
+ @Test
public void onImgWallpaperRestored_nullInfo_doesNotLogMetadata() {
setUpLoggerForRestore();
@@ -286,7 +324,7 @@
@Test
- public void onLiveWallpaperRestored_logsMetadata() {
+ public void onSystemLiveWallpaperRestored_logsMetadata() {
setUpLoggerForRestore();
mWallpaperEventLogger.onSystemLiveWallpaperRestored(mWallpaperInfo.getComponent());
@@ -296,6 +334,19 @@
assertThat(result.getMetadataHash()).isNotNull();
}
+ @Test
+ @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ public void onSystemLiveWallpaperRestoredDescription_logsMetadata() {
+ setUpLoggerForRestore();
+
+ mWallpaperEventLogger.onSystemLiveWallpaperRestoredWithDescription(mWallpaperDescription);
+ BackupRestoreEventLogger.DataTypeResult result = getLogsForType(
+ WALLPAPER_DESCRIPTION_SYSTEM);
+
+ assertThat(result).isNotNull();
+ assertThat(result.getMetadataHash()).isNotNull();
+ }
+
@Test
public void onSystemImgWallpaperRestoreFailed_logsFail() {
@@ -373,7 +424,7 @@
}
@Test
- public void onWallpaperRestoreException_liveTypeProcessed_doesNotLogForSameImgType() {
+ public void onSystemWallpaperRestoreException_liveTypeProcessed_doesNotLogForSameImgType() {
setUpLoggerForRestore();
mWallpaperEventLogger.onSystemLiveWallpaperRestored(mWallpaperInfo.getComponent());
@@ -383,6 +434,41 @@
assertThat(result).isNull();
}
+ @Test
+ @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ public void onSystemWallpaperRestoreException_descriptionProcessed_doesNotLogForSameImgType() {
+ setUpLoggerForRestore();
+ mWallpaperEventLogger.onSystemLiveWallpaperRestoredWithDescription(mWallpaperDescription);
+
+ mWallpaperEventLogger.onRestoreException(new Exception());
+ BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void onLockWallpaperRestoreException_liveTypeProcessed_doesNotLogForSameImgType() {
+ setUpLoggerForRestore();
+ mWallpaperEventLogger.onLockLiveWallpaperRestored(mWallpaperInfo.getComponent());
+
+ mWallpaperEventLogger.onRestoreException(new Exception());
+ BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ public void onLockWallpaperRestoreException_descriptionProcessed_doesNotLogForSameImgType() {
+ setUpLoggerForRestore();
+ mWallpaperEventLogger.onLockLiveWallpaperRestoredWithDescription(mWallpaperDescription);
+
+ mWallpaperEventLogger.onRestoreException(new Exception());
+ BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK);
+
+ assertThat(result).isNull();
+ }
+
private BackupRestoreEventLogger.DataTypeResult getLogsForType(String dataType) {
for (BackupRestoreEventLogger.DataTypeResult result : mEventLogger.getLoggingResults()) {
if ((result.getDataType()).equals(dataType)) {
diff --git a/ravenwood/tools/hoststubgen/Android.bp b/ravenwood/tools/hoststubgen/Android.bp
index 004834e..e605318 100644
--- a/ravenwood/tools/hoststubgen/Android.bp
+++ b/ravenwood/tools/hoststubgen/Android.bp
@@ -99,6 +99,7 @@
"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 b2af782..a62f66d 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 @@
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 e2647eb..40d343a 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 @@
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 @@
* 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 @@
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 @@
}
// 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 @@
}
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 3335405..2edcb2a 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.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 @@
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 @@
val processor = HostStubGenClassProcessor(options, allClasses, errors, stats)
// Transform the jar.
- convert(
+ inJar.convert(
options.inJar.get,
options.outJar.get,
processor,
@@ -88,7 +87,7 @@
/**
* 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 @@
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 ->
+ val entries = entries.toList()
- numItems = inZip.size()
- val shardStart = numItems * shard / numShards
- val shardNextStart = numItems * (shard + 1) / numShards
+ numItems = entries.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++
+ 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 @@
*/
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 @@
}
/**
- * 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 @@
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 04e3bda2..8e36323 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 @@
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 @@
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 @@
log.i(stats.toString())
}
- private fun process(
+ private fun ZipFile.process(
inJar: String,
outJar: String,
enableValidation: Boolean,
@@ -138,40 +139,34 @@
}
stats.totalProcessTime = log.vTime("$executableName processing $inJar") {
- ZipFile(inJar).use { inZip ->
- val inEntries = inZip.entries()
+ 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."
+ )
+ }
- stats.totalEntries = inZip.size()
+ if (stripMockito && entry.name.isMockitoFile()) {
+ // Skip this entry
+ return@forEach
+ }
- ZipOutputStream(BufferedOutputStream(FileOutputStream(outJar))).use { outZip ->
- while (inEntries.hasMoreElements()) {
- val entry = inEntries.nextElement()
+ val className = zipEntryNameToClassName(entry.name)
- 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 (className != null) {
+ stats.totalClasses += 1
+ }
- if (stripMockito && entry.name.isMockitoFile()) {
- // Skip this entry
- continue
- }
-
- val className = zipEntryNameToClassName(entry.name)
-
- 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 @@
}
}
- /**
- * 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 @@
}
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 94cef41..edcf574 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 @@
}
if (state == TelephonyManager.CALL_STATE_IDLE) {
- if (mIsCommDeviceChangedRegistered) {
- mIsCommDeviceChangedRegistered = false;
- mAudioManager.removeOnCommunicationDeviceChangedListener(
- mCommDeviceChangedListener);
- }
+ removeOnCommunicationDeviceChangedListenerIfNeeded(mCommDeviceChangedListener);
dismissNotificationIfNeeded();
if (mHearingDevice != null) {
@@ -172,10 +169,8 @@
if (mHearingDevice != null) {
showNotificationIfNeeded();
} else {
- mAudioManager.addOnCommunicationDeviceChangedListener(
- mCommDeviceChangedExecutor,
+ addOnCommunicationDeviceChangedListenerIfNeeded(mCommDeviceChangedExecutor,
mCommDeviceChangedListener);
- mIsCommDeviceChangedRegistered = true;
}
} else {
mHearingDevice = getSupportedInputHearingDeviceInfo(
@@ -187,6 +182,27 @@
}
}
+ 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 658ea4c..193d827 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 @@
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 @@
}
}
- 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 @@
* 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 @@
// 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 @@
}
}
- 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 @@
// === 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 @@
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 @@
setAdbConnectionInfo(null);
mContext.unregisterReceiver(mBroadcastReceiver);
- if (mThread != null) {
- mThread.sendResponse(MSG_DISABLE_ADBDWIFI);
- }
onAdbdWifiServerDisconnected(-1);
stopAdbDebuggingThread();
break;
@@ -1138,8 +1121,7 @@
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 @@
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 40f7c87..d12a0a2 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.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.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.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 @@
*/
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 @@
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 @@
} 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/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 517279b..8b3eb48 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -98,6 +98,9 @@
@VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
+ @VisibleForTesting
+ static final long APP_START_INFO_HISTORY_LENGTH_MS = TimeUnit.DAYS.toMillis(14);
+
/**
* The max number of records that can be present in {@link mInProgressRecords}.
*
@@ -120,9 +123,13 @@
* Monotonic clock which does not reset on reboot.
*
* Time for offset is persisted along with records, see {@link #persistProcessStartInfo}.
- * This does not follow the recommendation of {@link MonotonicClock} to persist on shutdown as
- * it's ok in this case to lose any time change past the last persist as records added since
- * then will be lost as well and the purpose of this clock is to keep records in order.
+ * This does not currently follow the recommendation of {@link MonotonicClock} to persist on
+ * shutdown as it's ok in this case to lose any time change past the last persist as records
+ * added since then will be lost as well. Since this time is used for cleanup as well, the
+ * potential old offset may result in the cleanup window being extended slightly beyond the
+ * targeted 14 days.
+ *
+ * TODO: b/402794215 - Persist on shutdown once persist performance is sufficiently improved.
*/
@VisibleForTesting MonotonicClock mMonotonicClock = null;
@@ -296,7 +303,7 @@
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
+ ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.setIntent(intent);
start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
@@ -454,7 +461,7 @@
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
+ ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
addBaseFieldsFromProcessRecord(start, app);
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
@@ -484,7 +491,7 @@
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
+ ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
addBaseFieldsFromProcessRecord(start, app);
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
@@ -511,7 +518,7 @@
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
+ ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
addBaseFieldsFromProcessRecord(start, app);
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
@@ -533,7 +540,7 @@
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
+ ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
addBaseFieldsFromProcessRecord(start, app);
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
@@ -721,8 +728,8 @@
Collections.sort(
list, (a, b) ->
- Long.compare(b.getMonoticCreationTimeMs(),
- a.getMonoticCreationTimeMs()));
+ Long.compare(b.getMonotonicCreationTimeMs(),
+ a.getMonotonicCreationTimeMs()));
int size = list.size();
if (maxNum > 0) {
size = Math.min(size, maxNum);
@@ -1098,7 +1105,7 @@
mLastAppStartInfoPersistTimestamp = now;
}
}
- proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTime());
+ proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTimeMs());
if (succeeded) {
proto.flush();
af.finishWrite(out);
@@ -1219,7 +1226,11 @@
}
}
- private long getMonotonicTime() {
+ /**
+ * Monotonic time that doesn't change with reboot or device time change for ordering records.
+ */
+ @VisibleForTesting
+ public long getMonotonicTimeMs() {
if (mMonotonicClock == null) {
// This should never happen. Return 0 to not interfere with past or future records.
return 0;
@@ -1229,7 +1240,7 @@
/** A container class of (@link android.app.ApplicationStartInfo) */
final class AppStartInfoContainer {
- private ArrayList<ApplicationStartInfo> mInfos; // Always kept sorted by first timestamp.
+ private ArrayList<ApplicationStartInfo> mInfos; // Always kept sorted by monotonic time.
private int mMaxCapacity;
private int mUid;
private boolean mMonitoringModeEnabled = false;
@@ -1260,9 +1271,12 @@
return;
}
- // Sort records so we can remove the least recent ones.
- Collections.sort(mInfos, (a, b) ->
- Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
+ if (!android.app.Flags.appStartInfoKeepRecordsSorted()) {
+ // Sort records so we can remove the least recent ones.
+ Collections.sort(mInfos, (a, b) ->
+ Long.compare(b.getMonotonicCreationTimeMs(),
+ a.getMonotonicCreationTimeMs()));
+ }
// Remove records and trim list object back to size.
mInfos.subList(0, mInfos.size() - getMaxCapacity()).clear();
@@ -1277,25 +1291,34 @@
@GuardedBy("mLock")
void addStartInfoLocked(ApplicationStartInfo info) {
- int size = mInfos.size();
- if (size >= getMaxCapacity()) {
- // Remove oldest record if size is over max capacity.
- int oldestIndex = -1;
- long oldestTimeStamp = Long.MAX_VALUE;
- for (int i = 0; i < size; i++) {
- ApplicationStartInfo startInfo = mInfos.get(i);
- if (startInfo.getMonoticCreationTimeMs() < oldestTimeStamp) {
- oldestTimeStamp = startInfo.getMonoticCreationTimeMs();
- oldestIndex = i;
+ if (android.app.Flags.appStartInfoKeepRecordsSorted()) {
+ while (mInfos.size() >= getMaxCapacity()) {
+ // Expected to execute at most once.
+ mInfos.removeLast();
+ }
+ mInfos.addFirst(info);
+ } else {
+ int size = mInfos.size();
+ if (size >= getMaxCapacity()) {
+ // Remove oldest record if size is over max capacity.
+ int oldestIndex = -1;
+ long oldestTimeStamp = Long.MAX_VALUE;
+ for (int i = 0; i < size; i++) {
+ ApplicationStartInfo startInfo = mInfos.get(i);
+ if (startInfo.getMonotonicCreationTimeMs() < oldestTimeStamp) {
+ oldestTimeStamp = startInfo.getMonotonicCreationTimeMs();
+ oldestIndex = i;
+ }
+ }
+ if (oldestIndex >= 0) {
+ mInfos.remove(oldestIndex);
}
}
- if (oldestIndex >= 0) {
- mInfos.remove(oldestIndex);
- }
+ mInfos.add(info);
+ Collections.sort(mInfos, (a, b) ->
+ Long.compare(b.getMonotonicCreationTimeMs(),
+ a.getMonotonicCreationTimeMs()));
}
- mInfos.add(info);
- Collections.sort(mInfos, (a, b) ->
- Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
}
/**
@@ -1439,9 +1462,25 @@
long token = proto.start(fieldId);
proto.write(AppsStartInfoProto.Package.User.UID, mUid);
int size = mInfos.size();
- for (int i = 0; i < size; i++) {
- mInfos.get(i).writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO,
- byteArrayOutputStream, objectOutputStream, typedXmlSerializer);
+ if (android.app.Flags.appStartInfoCleanupOldRecords()) {
+ long removeOlderThan = getMonotonicTimeMs() - APP_START_INFO_HISTORY_LENGTH_MS;
+ // Iterate backwards so we can remove old records as we go.
+ for (int i = size - 1; i >= 0; i--) {
+ if (mInfos.get(i).getMonotonicCreationTimeMs() < removeOlderThan) {
+ // Remove the record.
+ mInfos.remove(i);
+ } else {
+ mInfos.get(i).writeToProto(
+ proto, AppsStartInfoProto.Package.User.APP_START_INFO,
+ byteArrayOutputStream, objectOutputStream, typedXmlSerializer);
+ }
+ }
+ } else {
+ for (int i = 0; i < size; i++) {
+ mInfos.get(i).writeToProto(
+ proto, AppsStartInfoProto.Package.User.APP_START_INFO,
+ byteArrayOutputStream, objectOutputStream, typedXmlSerializer);
+ }
}
proto.write(AppsStartInfoProto.Package.User.MONITORING_ENABLED, mMonitoringModeEnabled);
proto.end(token);
@@ -1466,7 +1505,13 @@
info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO,
byteArrayInputStream, objectInputStream, typedXmlPullParser);
info.setPackageName(packageName);
- mInfos.add(info);
+ if (android.app.Flags.appStartInfoKeepRecordsSorted()) {
+ // Since the writes are done from oldest to newest, each additional
+ // record will be newer than the previous so use addFirst.
+ mInfos.addFirst(info);
+ } else {
+ mInfos.add(info);
+ }
break;
case (int) AppsStartInfoProto.Package.User.MONITORING_ENABLED:
mMonitoringModeEnabled = proto.readBoolean(
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
index 7502664..180ef85 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.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 @@
/** 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 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 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 bf7f194..6b3661a 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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
}
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 @@
@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 @@
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 @@
/**
* 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 @@
}
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 @@
}
}
- @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 ab86433..62c3dbd 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.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 @@
/**
* Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior(
- * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
+ * AudioDeviceAttributes, VolumeInfo, boolean, Executor, OnAudioDeviceVolumeChangedListener)}
*/
void setDeviceAbsoluteVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@@ -64,7 +65,7 @@
/**
* Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeAdjustOnlyBehavior(
- * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
+ * AudioDeviceAttributes, VolumeInfo, boolean, Executor, OnAudioDeviceVolumeChangedListener)}
*/
void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
@NonNull AudioDeviceAttributes device,
@@ -72,4 +73,16 @@
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 fd4dd51..6d01e2d 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 @@
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 10cbb00..02d8579 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 @@
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 @@
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 061e145..6627154 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 @@
}
@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 9118c46..574e484 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 @@
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 fdd0ef2..41b0b4d 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 @@
* 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 @@
}
}
}
- return getAudioManager().getDeviceVolumeBehavior(device);
+ return getAudioDeviceVolumeManager().getDeviceVolumeBehavior(device);
}
/**
@@ -4695,7 +4695,7 @@
// 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 @@
// 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 @@
// 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 @@
}
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 @@
for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
if (ABSOLUTE_VOLUME_BEHAVIORS.contains(getDeviceVolumeBehavior(device))) {
- getAudioManager().setDeviceVolumeBehavior(device,
- AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ getAudioDeviceVolumeManager().setDeviceVolumeBehavior(device,
+ AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
}
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index 6db62c8..ccb9e3e 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 @@
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 @@
// 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 @@
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 @@
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 @@
}
}
- private Optional<Byte> onEndpointSessionOpenRequestInternal(
+ /* package */ Optional<Byte> onEndpointSessionOpenRequest(
int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
if (!hasEndpointPermissions(initiator)) {
Log.e(
@@ -594,15 +586,41 @@
+ 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));
}
- mSessionMap.put(sessionId, new Session(initiator, true));
+ }
+
+ if (existingSession) {
+ if (existingSessionActive) {
+ // Existing session is already active, call onSessionOpenComplete.
+ openSessionRequestComplete(sessionId);
+ return Optional.empty();
+ }
+ // 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 @@
(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 @@
}
/**
- * 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 8ab581e..e156159 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.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.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 @@
}
}
+ /** 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 @@
}
}
- @Override
- public void onEndpointSessionOpenRequest(
+ private Optional<Byte> onEndpointSessionOpenRequestInternal(
int sessionId,
HubEndpointInfo.HubEndpointIdentifier destination,
HubEndpointInfo.HubEndpointIdentifier initiator,
@@ -348,7 +354,7 @@
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 @@
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 @@
TAG,
"onEndpointSessionOpenRequest: unknown initiator endpoint ID: "
+ initiator.getEndpoint());
- return;
+ return Optional.of(Reason.ENDPOINT_INVALID);
}
- broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor);
+ if (!isSessionIdAllocatedForService(sessionId)) {
+ Log.e(
+ TAG,
+ "onEndpointSessionOpenRequest: invalid session ID, rejected:"
+ + " sessionId="
+ + sessionId);
+ return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+ }
+ 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 @@
}
}
+ /**
+ * 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 7b4c563..7fd400e 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 @@
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 8948bd1..78554bd 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 @@
}
@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 233b577..33a7e74 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 @@
pw.print(" Has profile owner: ");
pw.println(mIsUserManaged.get(userId));
+
pw.println(" Restrictions:");
synchronized (mRestrictionsLock) {
UserRestrictionsUtils.dumpRestrictions(
@@ -7756,6 +7757,9 @@
}
}
+ 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 6872ca9..8c3b7c6 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -17,11 +17,13 @@
package com.android.server.security.advancedprotection;
import static android.provider.Settings.Secure.ADVANCED_PROTECTION_MODE;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.Manifest;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.StatsManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Binder;
@@ -45,6 +47,7 @@
import android.security.advancedprotection.AdvancedProtectionProtoEnums;
import android.util.ArrayMap;
import android.util.Slog;
+import android.util.StatsEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
@@ -59,6 +62,7 @@
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 +135,18 @@
} catch (Exception e) {
Slog.e(TAG, "Failed to initialize UsbDataAdvancedProtection", e);
}
- }
+ }
+
+ mProviders.add(new DisallowWepAdvancedProtectionProvider());
+ }
+
+ private void initLogging() {
+ StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.ADVANCED_PROTECTION_STATE_INFO,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ new AdvancedProtectionStatePullAtomCallback());
}
// Only for tests
@@ -303,7 +318,7 @@
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 +356,7 @@
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());
});
});
@@ -396,6 +411,7 @@
Slog.i(TAG, "Advanced protection is enabled");
}
mService.initFeatures(enabled);
+ mService.initLogging();
}
}
}
@@ -497,4 +513,22 @@
}
}
}
+
+ private class AdvancedProtectionStatePullAtomCallback
+ implements StatsManager.StatsPullAtomCallback {
+
+ @Override
+ public int onPullAtom(int atomTag, List<StatsEvent> data) {
+ if (atomTag != FrameworkStatsLog.ADVANCED_PROTECTION_STATE_INFO) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ data.add(
+ FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.ADVANCED_PROTECTION_STATE_INFO,
+ /*enabled*/ isAdvancedProtectionEnabledInternal(),
+ /*hours_since_enabled*/ hoursSinceLastChange()));
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
}
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 ed451f1..6498cfc 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 @@
/** @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 0000000..1505f68
--- /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 f413fe3..58f34d0 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 @@
/** 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 e7da33d..274175a 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.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 @@
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 void onScreenTurningOn(int displayId) {
notifyScreenTurningOn(displayId);
}
-
- @Override
- public void onKeyguardGoingAway() {
- notifyKeyguardGoingAway();
- }
}
void initialize() {
@@ -2571,6 +2591,18 @@
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 @@
* Propagate a keyguard going away event to the wallpaper engine.
*/
private void notifyKeyguardGoingAway() {
+ dispatchKeyguardCommand(WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY);
+ }
+
+ /**
+ * Propagate a keyguard appearing event to the wallpaper engine.
+ */
+ private void notifyKeyguardAppearing() {
+ dispatchKeyguardCommand(WallpaperManager.COMMAND_KEYGUARD_APPEARING);
+ }
+
+ /**
+ * Propagate a keyguard-related event to the wallpaper engine.
+ *
+ * When the flag below is enabled, the event will only be dispatched in case the recipient
+ * has {@link android.Manifest.pertmission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE} permission.
+ */
+ private void dispatchKeyguardCommand(String command) {
synchronized (mLock) {
for (WallpaperData data : getActiveWallpapers()) {
+ if (notifyKeyguardEvents() && !hasPermission(
+ data, android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)) {
+ continue;
+ }
+
data.connection.forEachDisplayConnector(displayConnector -> {
if (displayConnector.mEngine != null) {
try {
displayConnector.mEngine.dispatchWallpaperCommand(
- WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY,
- -1, -1, -1, new Bundle());
+ command, -1, -1, -1, new Bundle());
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify that the keyguard is going away", e);
+ Slog.w(TAG, "Failed to dispatch wallpaper command: " + command, e);
}
}
});
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index a731bf7..70fc6ba 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -82,6 +82,7 @@
*/
@VisibleForTesting
static final int SNAPSHOT_MODE_NONE = 2;
+ static final float THEME_SNAPSHOT_MIN_Length = 128.0f;
protected final WindowManagerService mService;
protected final float mHighResSnapshotScale;
@@ -436,14 +437,21 @@
final Rect taskBounds = source.getBounds();
final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
+ final int taskWidth = taskBounds.width();
+ final int taskHeight = taskBounds.height();
+ float scale = mHighResSnapshotScale;
+ if (Flags.reduceTaskSnapshotMemoryUsage()) {
+ final int minLength = Math.min(taskWidth, taskHeight);
+ if (THEME_SNAPSHOT_MIN_Length < minLength) {
+ scale = Math.min(THEME_SNAPSHOT_MIN_Length / minLength, scale);
+ }
+ }
final SnapshotDrawerUtils.SystemBarBackgroundPainter
decorPainter = new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags,
attrs.privateFlags, attrs.insetsFlags.appearance, taskDescription,
- mHighResSnapshotScale, mainWindow.getRequestedVisibleTypes());
- final int taskWidth = taskBounds.width();
- final int taskHeight = taskBounds.height();
- final int width = (int) (taskWidth * mHighResSnapshotScale);
- final int height = (int) (taskHeight * mHighResSnapshotScale);
+ scale, mainWindow.getRequestedVisibleTypes());
+ final int width = (int) (taskWidth * scale);
+ final int height = (int) (taskHeight * scale);
final RenderNode node = RenderNode.create("SnapshotController", null);
node.setLeftTopRightBottom(0, 0, width, height);
node.setClipToBounds(false);
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 5cc186c..a19f438 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 @@
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 c243cdc..21b730e 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 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 9c96566..46d24b0 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.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 @@
private ComponentName mSysUiServiceComponent;
private PermissionPolicyInternal mPermissionPolicyInternal;
private StatusBarManagerInternal mStatusBarManagerInternal;
- private WallpaperManagerInternal mWallpaperManagerInternal;
private UserManagerInternal mUserManagerInternal;
@VisibleForTesting
final ActivityTaskManagerInternal mInternal;
@@ -3719,10 +3717,12 @@
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 @@
return mStatusBarManagerInternal;
}
- WallpaperManagerInternal getWallpaperManagerInternal() {
- if (mWallpaperManagerInternal == null) {
- mWallpaperManagerInternal = LocalServices.getService(WallpaperManagerInternal.class);
- }
- return mWallpaperManagerInternal;
- }
-
UserManagerInternal getUserManagerInternal() {
if (mUserManagerInternal == null) {
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
diff --git a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
index 9596093..cd6a014 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 @@
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/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index fee5566..d808726 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -69,11 +69,11 @@
* Calculates the final aspect ratio of an launching activity based on the task it will be
* launched in. Takes into account any min or max aspect ratio constraints.
*/
- float calculateAspectRatio(@NonNull Task task) {
+ float calculateAspectRatio(@NonNull Task task, boolean hasOrientationMismatch) {
final float maxAspectRatio = getMaxAspectRatio();
final float minAspectRatio = getMinAspectRatio(task);
float desiredAspectRatio = 0;
- desiredAspectRatio = getDesiredAspectRatio(task);
+ desiredAspectRatio = getDesiredAspectRatio(task, hasOrientationMismatch);
if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
desiredAspectRatio = maxAspectRatio;
} else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
@@ -87,13 +87,14 @@
* any min or max aspect ratio constraints.
*/
@VisibleForTesting
- float getDesiredAspectRatio(@NonNull Task task) {
+ float getDesiredAspectRatio(@NonNull Task task, boolean hasOrientationMismatch) {
final float letterboxAspectRatioOverride = getFixedOrientationLetterboxAspectRatio(task);
// Aspect ratio as suggested by the system. Apps requested mix/max aspect ratio will
// be respected in #calculateAspectRatio.
if (isDefaultMultiWindowLetterboxAspectRatioDesired(task)) {
return DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
- } else if (letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
+ } else if (hasOrientationMismatch
+ && letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
return letterboxAspectRatioOverride;
}
return AppCompatUtils.computeAspectRatio(task.getDisplayArea().getBounds());
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index d935432..83ca5f6 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.isFixedOrientation;
import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
@@ -32,8 +31,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
-import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.ActivityInfo.WindowLayout;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.Size;
@@ -152,19 +151,25 @@
}
final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
activity.mAppCompatController.getDesktopAspectRatioPolicy();
- float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(task);
+ final int stableBoundsOrientation = stableBounds.height() >= stableBounds.width()
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ int activityOrientation = getActivityConfigurationOrientation(
+ activity, task, stableBoundsOrientation);
+ // Use orientation mismatch to resolve aspect ratio to match fixed orientation letterboxing
+ // policy in {@link ActivityRecord.resolveFixedOrientationConfiguration}
+ final boolean hasOrientationMismatch = stableBoundsOrientation != activityOrientation;
+ float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(
+ task, hasOrientationMismatch);
final float tdaWidth = stableBounds.width();
final float tdaHeight = stableBounds.height();
- final int taskConfigOrientation = task.getConfiguration().orientation;
- final int activityOrientation = getActivityOrientation(activity, task);
- final Size initialSize = switch (taskConfigOrientation) {
+ final Size initialSize = switch (stableBoundsOrientation) {
case ORIENTATION_LANDSCAPE -> {
// Device in landscape orientation.
if (appAspectRatio == 0) {
appAspectRatio = 1;
}
if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, task)) {
- if (isFixedOrientationPortrait(activityOrientation)) {
+ if (hasOrientationMismatch) {
// For portrait resizeable activities, respect apps fullscreen width but
// apply ideal size height.
yield new Size((int) ((tdaHeight / appAspectRatio) + 0.5f),
@@ -183,7 +188,7 @@
final int customPortraitWidthForLandscapeApp = screenBounds.width()
- (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, task)) {
- if (isFixedOrientationLandscape(activityOrientation)) {
+ if (hasOrientationMismatch) {
if (appAspectRatio == 0) {
appAspectRatio = tdaWidth / (tdaWidth - 1);
}
@@ -198,7 +203,7 @@
if (appAspectRatio == 0) {
appAspectRatio = 1;
}
- if (isFixedOrientationLandscape(activityOrientation)) {
+ if (hasOrientationMismatch) {
// For landscape unresizeable activities, apply custom app width to ideal size
// and calculate maximum size with this area while maintaining original aspect
// ratio.
@@ -230,19 +235,23 @@
&& !desktopAppCompatAspectRatioPolicy.hasMinAspectRatioOverride(task);
}
- private static @ScreenOrientation int getActivityOrientation(
- @NonNull ActivityRecord activity, @NonNull Task task) {
+ private static @Configuration.Orientation int getActivityConfigurationOrientation(
+ @NonNull ActivityRecord activity, @NonNull Task task,
+ @Configuration.Orientation int stableBoundsOrientation) {
final int activityOrientation = activity.getOverrideOrientation();
final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
activity.mAppCompatController.getDesktopAspectRatioPolicy();
- if (desktopAppCompatAspectRatioPolicy.shouldApplyUserMinAspectRatioOverride(task)
+ if ((desktopAppCompatAspectRatioPolicy.shouldApplyUserMinAspectRatioOverride(task)
&& (!isFixedOrientation(activityOrientation)
- || activityOrientation == SCREEN_ORIENTATION_LOCKED)) {
+ || activityOrientation == SCREEN_ORIENTATION_LOCKED))
+ || isFixedOrientationPortrait(activityOrientation)) {
// If a user aspect ratio override should be applied, treat the activity as portrait if
// it has not specified a fix orientation.
- return SCREEN_ORIENTATION_PORTRAIT;
+ return ORIENTATION_PORTRAIT;
}
- return activityOrientation;
+ // If activity orientation is undefined inherit task orientation.
+ return isFixedOrientationLandscape(activityOrientation)
+ ? ORIENTATION_LANDSCAPE : stableBoundsOrientation;
}
/**
@@ -252,7 +261,7 @@
// TODO(b/400617906): Merge duplicate initial bounds calculations to shared class.
@NonNull
private static Size maximizeSizeGivenAspectRatio(
- @ScreenOrientation int orientation,
+ @Configuration.Orientation int orientation,
@NonNull Size targetArea,
float aspectRatio,
int captionHeight
@@ -261,7 +270,7 @@
final int targetWidth = targetArea.getWidth();
final int finalHeight;
final int finalWidth;
- if (isFixedOrientationPortrait(orientation)) {
+ if (orientation == ORIENTATION_PORTRAIT) {
// Portrait activity.
// Calculate required width given ideal height and aspect ratio.
int tempWidth = (int) (targetHeight / aspectRatio);
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 2798e84..ab87459 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 @@
*/
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/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index eafc8be..016ceba 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -24,6 +24,10 @@
import android.annotation.NonNull;
import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.ImageReader;
import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
@@ -33,10 +37,12 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.TransitionAnimation;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
+import com.android.window.flags.Flags;
import java.io.File;
import java.io.FileOutputStream;
@@ -400,23 +406,20 @@
Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mId);
return false;
}
- final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
- mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace());
- if (bitmap == null) {
- Slog.e(TAG, "Invalid task snapshot hw bitmap");
- return false;
- }
- final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */);
+ final HardwareBuffer hwBuffer = mSnapshot.getHardwareBuffer();
+ final int width = hwBuffer.getWidth();
+ final int height = hwBuffer.getHeight();
+ final int pixelFormat = hwBuffer.getFormat();
+ final Bitmap swBitmap = !Flags.reduceTaskSnapshotMemoryUsage()
+ || (pixelFormat != PixelFormat.RGB_565 && pixelFormat != PixelFormat.RGBA_8888)
+ || !mSnapshot.isRealSnapshot()
+ || TransitionAnimation.hasProtectedContent(hwBuffer)
+ ? copyToSwBitmapReadBack()
+ : copyToSwBitmapDirect(width, height, pixelFormat);
if (swBitmap == null) {
- Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() + ", isMutable="
- + bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed.");
return false;
}
- final int width = bitmap.getWidth();
- final int height = bitmap.getHeight();
- bitmap.recycle();
-
final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
try (FileOutputStream fos = new FileOutputStream(file)) {
swBitmap.compress(JPEG, COMPRESS_QUALITY, fos);
@@ -448,6 +451,58 @@
return true;
}
+ private Bitmap copyToSwBitmapReadBack() {
+ final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
+ mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace());
+ if (bitmap == null) {
+ Slog.e(TAG, "Invalid task snapshot hw bitmap");
+ return null;
+ }
+
+ final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */);
+ if (swBitmap == null) {
+ Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig()
+ + ", isMutable=" + bitmap.isMutable()
+ + ") to (config=ARGB_8888, isMutable=false) failed.");
+ return null;
+ }
+ bitmap.recycle();
+ return swBitmap;
+ }
+
+ /**
+ * Use ImageReader to create the software bitmap, so SkImage won't create an extra texture.
+ */
+ private Bitmap copyToSwBitmapDirect(int width, int height, int pixelFormat) {
+ try (ImageReader ir = ImageReader.newInstance(width, height,
+ pixelFormat, 1 /* maxImages */)) {
+ ir.getSurface().attachAndQueueBufferWithColorSpace(mSnapshot.getHardwareBuffer(),
+ mSnapshot.getColorSpace());
+ try (Image image = ir.acquireLatestImage()) {
+ if (image == null || image.getPlaneCount() < 1) {
+ Slog.e(TAG, "Image reader cannot acquire image");
+ return null;
+ }
+
+ final Image.Plane[] planes = image.getPlanes();
+ if (planes.length != 1) {
+ Slog.e(TAG, "Image reader cannot get plane");
+ return null;
+ }
+ final Image.Plane plane = planes[0];
+ final int rowPadding = plane.getRowStride() - plane.getPixelStride()
+ * image.getWidth();
+ final Bitmap swBitmap = Bitmap.createBitmap(
+ image.getWidth() + rowPadding / plane.getPixelStride() /* width */,
+ image.getHeight() /* height */,
+ pixelFormat == PixelFormat.RGB_565
+ ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888);
+ swBitmap.copyPixelsFromBuffer(plane.getBuffer());
+ return swBitmap;
+ }
+ }
+ }
+
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8587b5a..0531828 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1832,6 +1832,17 @@
&& supportsMultiWindowInDisplayArea(tda);
}
+ /** Returns true if the task bounds should persist across power cycles. */
+ private static boolean persistTaskBounds(@NonNull WindowConfiguration configuration) {
+ return configuration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ }
+
+ /** Returns true if the nested task is allowed to have independent bounds from its parent. */
+ private static boolean allowIndependentBoundsFromParent(
+ @NonNull WindowConfiguration configuration) {
+ return configuration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ }
+
/**
* Check whether this task can be launched on the specified display.
*
@@ -1996,11 +2007,11 @@
private void onConfigurationChangedInner(Configuration newParentConfig) {
// Check if the new configuration supports persistent bounds (eg. is Freeform) and if so
// restore the last recorded non-fullscreen bounds.
- final boolean prevPersistTaskBounds = getWindowConfiguration().persistTaskBounds();
- boolean nextPersistTaskBounds =
- getRequestedOverrideConfiguration().windowConfiguration.persistTaskBounds();
+ final boolean prevPersistTaskBounds = persistTaskBounds(getWindowConfiguration());
+ boolean nextPersistTaskBounds = persistTaskBounds(
+ getRequestedOverrideConfiguration().windowConfiguration);
if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_UNDEFINED) {
- nextPersistTaskBounds = newParentConfig.windowConfiguration.persistTaskBounds();
+ nextPersistTaskBounds = persistTaskBounds(newParentConfig.windowConfiguration);
}
// Only restore to the last non-fullscreen bounds when the requested override bounds
// have not been explicitly set already.
@@ -2042,7 +2053,7 @@
// If the configuration supports persistent bounds (eg. Freeform), keep track of the
// current (non-fullscreen) bounds for persistence.
- if (getWindowConfiguration().persistTaskBounds()) {
+ if (persistTaskBounds(getWindowConfiguration())) {
final Rect currentBounds = getRequestedOverrideBounds();
if (!currentBounds.isEmpty()) {
setLastNonFullscreenBounds(currentBounds);
@@ -2383,33 +2394,48 @@
}
void updateOverrideConfigurationFromLaunchBounds() {
- // If the task is controlled by another organized task, do not set override
- // configurations and let its parent (organized task) to control it;
final Task rootTask = getRootTask();
- boolean shouldInheritBounds = rootTask != this && rootTask.isOrganized();
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
- // Only inherit from organized parent when this task is not organized.
- shouldInheritBounds &= !isOrganized();
- }
- final Rect bounds = shouldInheritBounds ? null : getLaunchBounds();
- setBounds(bounds);
- }
-
- /** Returns the bounds that should be used to launch this task. */
- Rect getLaunchBounds() {
- final Task rootTask = getRootTask();
- if (rootTask == null) {
- return null;
- }
-
+ final boolean hasParentTask = rootTask != this;
final int windowingMode = getWindowingMode();
- if (!isActivityTypeStandardOrUndefined()
- || windowingMode == WINDOWING_MODE_FULLSCREEN) {
- return isResizeable() ? rootTask.getRequestedOverrideBounds() : null;
- } else if (!getWindowConfiguration().persistTaskBounds()) {
- return rootTask.getRequestedOverrideBounds();
+ final boolean isNonStandardOrFullscreen = !isActivityTypeStandardOrUndefined()
+ || windowingMode == WINDOWING_MODE_FULLSCREEN;
+ if (!Flags.nestedTasksWithIndependentBounds()
+ && !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
+ final Rect bounds;
+ if (hasParentTask && rootTask.isOrganized()) {
+ bounds = null;
+ } else if (isNonStandardOrFullscreen) {
+ bounds = isResizeable() ? rootTask.getRequestedOverrideBounds() : null;
+ } else if (!persistTaskBounds(getWindowConfiguration())) {
+ bounds = rootTask.getRequestedOverrideBounds();
+ } else {
+ bounds = mLastNonFullscreenBounds;
+ }
+ setBounds(bounds);
+ return;
}
- return mLastNonFullscreenBounds;
+
+ // Non-standard/fullscreen unresizable tasks should always inherit.
+ boolean shouldInheritBounds = isNonStandardOrFullscreen && !isResizeable();
+ // Task itself is not organized (e.g. Home), just inherit from its organized parent.
+ shouldInheritBounds |= hasParentTask && rootTask.isOrganized() && !isOrganized();
+ // Nested tasks should inherit when they're not allowed to have independent bounds, such as
+ // in multi-window split-screen.
+ shouldInheritBounds |= hasParentTask
+ && !(allowIndependentBoundsFromParent(getWindowConfiguration())
+ && persistTaskBounds(getWindowConfiguration()));
+ if (shouldInheritBounds) {
+ setBounds(null);
+ return;
+ }
+ if (!hasParentTask && !persistTaskBounds(getWindowConfiguration())) {
+ // Non-nested, non-persistable tasks such as PIP or multi-window floating windows.
+ return;
+ }
+ // Non-nested, persisted tasks (e.g. top-level freeform) or nested persisted tasks that
+ // allow independent bounds from parent (e.g. nested freeform) should use launch-params
+ // bounds set to |mLastNonFullscreenBounds|.
+ setBounds(mLastNonFullscreenBounds);
}
void setRootProcess(WindowProcessController proc) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2c10af4..65001f4 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 @@
* 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 bdd1372..d356128 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 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 @@
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 @@
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 @@
// 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 @@
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 @@
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 f6b107b..d60807c 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 @@
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 51ed6bb..f055feb 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.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 @@
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 @@
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();
}
- return admin == null ? null : admin.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 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 543e32f..9ff6eb6 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.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 @@
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 0000000..bc075b02
--- /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/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 987b9c6..3289d70b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -680,6 +680,67 @@
ApplicationStartInfo.START_TIMESTAMP_FORK));
}
+ /**
+ * Test that cleanup old records works as expected, removing records that are older than the max
+ * retention length.
+ */
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_APP_START_INFO_CLEANUP_OLD_RECORDS)
+ public void testOldRecordsCleanup() throws Exception {
+ // Use a different start timestamp for each record so we can identify which was removed.
+ // This timestamp is not used for ordering and has no impact on removal.
+ final long startTimeRecord1 = 123L;
+ final long startTimeRecord2 = 456L;
+ final long startTimeRecord3 = 789L;
+
+ // Create a process record to use with all starts.
+ ProcessRecord app = makeProcessRecord(
+ APP_1_PID_1, // pid
+ APP_1_UID, // uid
+ APP_1_UID, // packageUid
+ null, // definingUid
+ APP_1_PROCESS_NAME, // processName
+ APP_1_PACKAGE_NAME); // packageName
+
+ // Set monotonic time to 1, and then trigger a start info record.
+ doReturn(1L).when(mAppStartInfoTracker).getMonotonicTimeMs();
+ mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord1, app,
+ buildIntent(COMPONENT), false /* isAlarm */);
+
+ // Set monotonic time to 2, and then trigger another start info record.
+ doReturn(2L).when(mAppStartInfoTracker).getMonotonicTimeMs();
+ mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord2, app,
+ buildIntent(COMPONENT), false /* isAlarm */);
+
+ // Set monotonic time to 3, then trigger another start info record.
+ doReturn(3L).when(mAppStartInfoTracker).getMonotonicTimeMs();
+ mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord3, app,
+ buildIntent(COMPONENT), false /* isAlarm */);
+
+ // Verify that all 3 records were added successfully.
+ ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
+ mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
+ assertEquals(3, list.size());
+ assertEquals(startTimeRecord3, list.get(0).getStartupTimestamps().get(0).longValue());
+ assertEquals(startTimeRecord2, list.get(1).getStartupTimestamps().get(0).longValue());
+ assertEquals(startTimeRecord1, list.get(2).getStartupTimestamps().get(0).longValue());
+
+ // Set monotonic time to max history length + 3 so that the older 2 records will be removed
+ // but that newest 1 will remain.
+ doReturn(AppStartInfoTracker.APP_START_INFO_HISTORY_LENGTH_MS + 3L)
+ .when(mAppStartInfoTracker).getMonotonicTimeMs();
+
+ // Trigger a persist which will trigger the cleanup of old records.
+ mAppStartInfoTracker.persistProcessStartInfo();
+
+ // Now verify that the records older than max were removed, and that the records not older
+ // remain.
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
+ assertEquals(1, list.size());
+ assertEquals(startTimeRecord3, list.get(0).getStartupTimestamps().get(0).longValue());
+ }
+
private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
try {
Field field = clazz.getDeclaredField(fieldName);
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 8c09f26..fdf78ad 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.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.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.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.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 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
- @Rule
- public TestRule compatChangeRule = new PlatformCompatChangeRule();
-
private ChargingPolicyChangeListener mChargingPolicyChangeListener;
private int mSourceUid;
@@ -166,8 +161,10 @@
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 @@
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 @@
*/
@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 @@
*/
@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 @@
*/
@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 2d84887..924fe95 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.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 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.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 @@
private PowerAllowlistInternal mPowerAllowlistInternal;
@Mock
private UsageStatsManagerInternal mUsageStatsManager;
+ @Mock
+ private PlatformCompat mPlatformCompat;
@Rule
public final CheckFlagsRule mCheckFlagsRule =
@@ -182,6 +184,7 @@
.strictness(Strictness.LENIENT)
.spyStatic(DeviceConfig.class)
.mockStatic(LocalServices.class)
+ .mockStatic(ServiceManager.class)
.startMocking();
// Called in StateController constructor.
@@ -198,6 +201,7 @@
}
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 @@
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 @@
}
@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 fc864dd..3ed4a52 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 @@
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 @@
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 @@
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 3565244..33529c3 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.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 @@
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 b2d48a7..2349120 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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 4c12e436..7c7e220 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.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 @@
*/
@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 f44517a..7a43598 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 @@
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 @@
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 @@
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 @@
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 @@
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 a4c71bd..2227eeb 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 @@
}
@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 90f94cb..e07f4f9 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.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 @@
// 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 @@
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 @@
@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 @@
* 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 43ab804..ffc1c62 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 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 @@
// 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 @@
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 9b343e3..0926180 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 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 @@
// 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 @@
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 4d2dcf6..43b1ec3 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 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 @@
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 @@
}
@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 d1b2e8e..1fb8411 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 @@
/** 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 c7a06b8..339bac4 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 @@
AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
@Override
- public List<AdvancedProtectionFeature> getFeatures() {
+ public List<AdvancedProtectionFeature> getFeatures(Context context) {
return List.of(feature2);
}
};
@@ -291,7 +291,7 @@
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 bc8b7be..902171d 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 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 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/DesktopAppCompatAspectRatioPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
index fa7dcc8..81d753b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
@@ -35,6 +35,7 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.pm.ActivityInfo;
@@ -413,7 +414,7 @@
void setDesiredAspectRatio(float aspectRatio) {
doReturn(aspectRatio).when(getDesktopAppCompatAspectRatioPolicy())
- .getDesiredAspectRatio(any());
+ .getDesiredAspectRatio(any(), anyBoolean());
}
DesktopAppCompatAspectRatioPolicy getDesktopAppCompatAspectRatioPolicy() {
@@ -422,7 +423,7 @@
float calculateAspectRatio() {
return getDesktopAppCompatAspectRatioPolicy().calculateAspectRatio(
- getTopActivity().getTask());
+ getTopActivity().getTask(), /* hasOrientationMismatch */ true);
}
ActivityRecord getTopActivity() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 00b617e..f587d6e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -52,6 +52,7 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -437,7 +438,7 @@
spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
- .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
+ .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean());
final int desiredWidth =
(int) ((LANDSCAPE_DISPLAY_BOUNDS.height() / LETTERBOX_ASPECT_RATIO) + 0.5f);
@@ -933,7 +934,7 @@
spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
- .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
+ .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean());
final int desiredHeight =
(int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
@@ -1060,7 +1061,7 @@
spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
- .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
+ .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean());
final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
- (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
@@ -1115,7 +1116,7 @@
spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
- .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
+ .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean());
final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
- (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
@@ -1569,7 +1570,7 @@
activity.mAppCompatController.getDesktopAspectRatioPolicy();
spyOn(desktopAppCompatAspectRatioPolicy);
doReturn(aspectRatio).when(desktopAppCompatAspectRatioPolicy)
- .getDesiredAspectRatio(any());
+ .getDesiredAspectRatio(any(), anyBoolean());
}
private void applyUserMinAspectRatioOverride(ActivityRecord activity, int overrideCode,
@@ -1579,7 +1580,7 @@
activity.mAppCompatController.getDesktopAspectRatioPolicy();
spyOn(desktopAppCompatAspectRatioPolicy);
doReturn(1f).when(desktopAppCompatAspectRatioPolicy)
- .getDesiredAspectRatio(any());
+ .getDesiredAspectRatio(any(), anyBoolean());
// Enable user aspect ratio settings
final AppCompatConfiguration appCompatConfiguration =
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 9dc7026..5347f9a 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 @@
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 d9bb7db..5419d94 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 @@
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 @@
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 @@
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 @@
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);