Merge "Adds a flag for getSupportedRefreshRates api" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 790fc6b..909a9b3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -618,6 +618,26 @@
return mRunningJob;
}
+ @VisibleForTesting
+ void setRunningJobLockedForTest(JobStatus job) {
+ mRunningJob = job;
+ }
+
+ @VisibleForTesting
+ void setJobParamsLockedForTest(JobParameters params) {
+ mParams = params;
+ }
+
+ @VisibleForTesting
+ void setRunningCallbackLockedForTest(JobCallback callback) {
+ mRunningCallback = callback;
+ }
+
+ @VisibleForTesting
+ void setPendingStopReasonLockedForTest(int stopReason) {
+ mPendingStopReason = stopReason;
+ }
+
@JobConcurrencyManager.WorkType
int getRunningJobWorkType() {
return mRunningJobWorkType;
@@ -786,6 +806,7 @@
executing = getRunningJobLocked();
}
if (executing != null && jobId == executing.getJobId()) {
+ executing.setAbandoned(true);
final StringBuilder stateSuffix = new StringBuilder();
stateSuffix.append(TRACE_ABANDONED_JOB);
stateSuffix.append(executing.getBatteryName());
@@ -1364,8 +1385,9 @@
}
/** Process MSG_TIMEOUT here. */
+ @VisibleForTesting
@GuardedBy("mLock")
- private void handleOpTimeoutLocked() {
+ void handleOpTimeoutLocked() {
switch (mVerb) {
case VERB_BINDING:
// The system may have been too busy. Don't drop the job or trigger an ANR.
@@ -1427,9 +1449,25 @@
// Not an error - client ran out of time.
Slog.i(TAG, "Client timed out while executing (no jobFinished received)."
+ " Sending onStop: " + getRunningJobNameLocked());
- mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
- JobParameters.INTERNAL_STOP_REASON_TIMEOUT, "client timed out");
- sendStopMessageLocked("timeout while executing");
+
+ final JobStatus executing = getRunningJobLocked();
+ int stopReason = JobParameters.STOP_REASON_TIMEOUT;
+ int internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+ final StringBuilder stopMessage = new StringBuilder("timeout while executing");
+ final StringBuilder debugStopReason = new StringBuilder("client timed out");
+
+ if (android.app.job.Flags.handleAbandonedJobs()
+ && executing != null && executing.isAbandoned()) {
+ final String abandonedMessage = " and maybe abandoned";
+ stopReason = JobParameters.STOP_REASON_TIMEOUT_ABANDONED;
+ internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED;
+ stopMessage.append(abandonedMessage);
+ debugStopReason.append(abandonedMessage);
+ }
+
+ mParams.setStopReason(stopReason,
+ internalStopReason, debugStopReason.toString());
+ sendStopMessageLocked(stopMessage.toString());
} else if (nowElapsed >= earliestStopTimeElapsed) {
// We've given the app the minimum execution time. See if we should stop it or
// let it continue running
@@ -1479,8 +1517,9 @@
* Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
* VERB_STOPPING.
*/
+ @VisibleForTesting
@GuardedBy("mLock")
- private void sendStopMessageLocked(@Nullable String reason) {
+ void sendStopMessageLocked(@Nullable String reason) {
removeOpTimeOutLocked();
if (mVerb != VERB_EXECUTING) {
Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index e3af1d8..1dc5a71 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -576,6 +576,13 @@
private String mSystemTraceTag;
/**
+ * Job maybe abandoned by not calling
+ * {@link android.app.job.JobService#jobFinished(JobParameters, boolean)} while
+ * the strong reference to {@link android.app.job.JobParameters} is lost
+ */
+ private boolean mIsAbandoned;
+
+ /**
* Core constructor for JobStatus instances. All other ctors funnel down to this one.
*
* @param job The actual requested parameters for the job
@@ -725,6 +732,8 @@
updateNetworkBytesLocked();
updateMediaBackupExemptionStatus();
+
+ mIsAbandoned = false;
}
/** Copy constructor: used specifically when cloning JobStatus objects for persistence,
@@ -1061,6 +1070,16 @@
return job.getTraceTag();
}
+ /** Returns if the job maybe abandoned */
+ public boolean isAbandoned() {
+ return mIsAbandoned;
+ }
+
+ /** Set the job maybe abandoned state*/
+ public void setAbandoned(boolean abandoned) {
+ mIsAbandoned = abandoned;
+ }
+
/** Returns a trace tag using debug information provided by job scheduler service. */
@NonNull
public String computeSystemTraceTag() {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e4d5589..b108fc5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -703,6 +703,7 @@
field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
+ field @FlaggedApi("android.permission.flags.platform_skin_temperature_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 56538d9..2b0e86c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1618,9 +1618,12 @@
/** @hide Access to read heart rate sensor. */
public static final int OP_READ_HEART_RATE = AppProtoEnums.APP_OP_READ_HEART_RATE;
+ /** @hide Access to read skin temperature. */
+ public static final int OP_READ_SKIN_TEMPERATURE = AppProtoEnums.APP_OP_READ_SKIN_TEMPERATURE;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 150;
+ public static final int _NUM_OP = 151;
/**
* All app ops represented as strings.
@@ -1774,6 +1777,7 @@
OPSTR_EMERGENCY_LOCATION,
OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS,
OPSTR_READ_HEART_RATE,
+ OPSTR_READ_SKIN_TEMPERATURE,
})
public @interface AppOpString {}
@@ -2516,6 +2520,11 @@
@FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
public static final String OPSTR_READ_HEART_RATE = "android:read_heart_rate";
+ /** @hide Access to read skin temperature. */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED)
+ public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2591,6 +2600,7 @@
OP_POST_NOTIFICATION,
// Health
Flags.replaceBodySensorPermissionEnabled() ? OP_READ_HEART_RATE : OP_NONE,
+ Flags.platformSkinTemperatureEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE,
};
/**
@@ -3103,6 +3113,11 @@
.setPermission(Flags.replaceBodySensorPermissionEnabled() ?
HealthPermissions.READ_HEART_RATE : null)
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_SKIN_TEMPERATURE, OPSTR_READ_SKIN_TEMPERATURE,
+ "READ_SKIN_TEMPERATURE").setPermission(
+ Flags.platformSkinTemperatureEnabled()
+ ? HealthPermissions.READ_SKIN_TEMPERATURE : null)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index 740f593..730bb73 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -41,5 +41,6 @@
void unlockedByBiometricForUser(int userId, in BiometricSourceType source);
void clearAllBiometricRecognized(in BiometricSourceType target, int unlockedUser);
boolean isActiveUnlockRunning(int userId);
+ @EnforcePermission("ACCESS_FINE_LOCATION")
boolean isInSignificantPlace();
}
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 88d4d69..1ef83cd 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -305,6 +305,7 @@
*
* @hide
*/
+ @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
public boolean isInSignificantPlace() {
try {
return mService.isInSignificantPlace();
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index e8d7e1e..a7e7a57 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -150,6 +150,7 @@
* All values are in micro-Tesla (uT) and measure the ambient magnetic field
* in the X, Y and Z axis.
*
+ *
* <h4>{@link android.hardware.Sensor#TYPE_GYROSCOPE Sensor.TYPE_GYROSCOPE}:
* </h4> All values are in radians/second and measure the rate of rotation
* around the device's local X, Y and Z axis. The coordinate system is the
@@ -406,10 +407,9 @@
* </h4>
*
* <ul>
- * <li> values[0]: ambient (room) temperature in degree Celsius.</li>
+ * <li> values[0]: Ambient (room) temperature in degrees Celsius</li>
* </ul>
*
- *
* <h4>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD_UNCALIBRATED
* Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED}:</h4>
* Similar to {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD},
@@ -549,8 +549,20 @@
* <li> values[0]: 1.0 </li>
* </ul>
*
- * <h4>{@link android.hardware.Sensor#TYPE_HEART_BEAT
- * Sensor.TYPE_HEART_BEAT}:</h4>
+ * <h4>{@link android.hardware.Sensor#TYPE_STEP_COUNTER Sensor.TYPE_STEP_COUNTER}:</h4>
+ *
+ * <ul>
+ * <li>values[0]: Number of steps taken by the user since the last reboot while the sensor is
+ * activated</li>
+ * </ul>
+ *
+ * <h4>{@link android.hardware.Sensor#TYPE_STEP_DETECTOR Sensor.TYPE_STEP_DETECTOR}:</h4>
+ *
+ * <ul>
+ * <li>values[0]: Always set to 1.0, representing a single step detected event</li>
+ * </ul>
+ *
+ * <h4>{@link android.hardware.Sensor#TYPE_HEART_BEAT Sensor.TYPE_HEART_BEAT}:</h4>
*
* A sensor of this type returns an event everytime a heart beat peak is
* detected.
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 2d29900..7a23033 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -61,3 +61,13 @@
description: "Feature flag for biometric prompt improvements in private space"
bug: "365554098"
}
+
+flag {
+ name: "effective_user_bp"
+ namespace: "biometrics_framework"
+ description: "Feature flag for using effective user in biometric prompt"
+ bug: "365094949"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 75ffcc3..399184c 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -491,10 +491,16 @@
public static final int POLICY_DIM = 2;
// Policy: Make the screen bright as usual.
public static final int POLICY_BRIGHT = 3;
+ // The maximum policy constant. Useful for iterating through all constants in tests.
+ public static final int POLICY_MAX = POLICY_BRIGHT;
// The basic overall policy to apply: off, doze, dim or bright.
public int policy;
+ // The reason behind the current policy.
+ @Display.StateReason
+ public int policyReason;
+
// If true, the proximity sensor overrides the screen state when an object is
// nearby, turning it off temporarily until the object is moved away.
public boolean useProximitySensor;
@@ -541,6 +547,7 @@
public DisplayPowerRequest() {
policy = POLICY_BRIGHT;
+ policyReason = Display.STATE_REASON_DEFAULT_POLICY;
useProximitySensor = false;
screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
screenAutoBrightnessAdjustmentOverride = Float.NaN;
@@ -561,6 +568,7 @@
public void copyFrom(DisplayPowerRequest other) {
policy = other.policy;
+ policyReason = other.policyReason;
useProximitySensor = other.useProximitySensor;
screenBrightnessOverride = other.screenBrightnessOverride;
screenBrightnessOverrideTag = other.screenBrightnessOverrideTag;
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index e6982b9..71c91e9 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -1,7 +1,10 @@
package: "com.android.hardware.input"
container: "system"
-# Project link: https://gantry.corp.google.com/projects/android_platform_input_native/changes
+# Project link: https://gantry.corp.google.com/projects/android_platform_input/changes
+
+# NOTE: the input_native namespace is deprecated. New flags should be added to the input namespace
+# instead.
flag {
namespace: "input_native"
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 48eb968..f7dc790 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -5,13 +5,6 @@
# Flags used for module APIs must be in aconfig files under each modules
flag {
- name: "ipsec_transform_state"
- namespace: "core_networking_ipsec"
- description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
- bug: "308011229"
-}
-
-flag {
name: "powered_off_finding_platform"
namespace: "nearby"
description: "Controls whether the Powered Off Finding feature is enabled"
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 32db3be..9d4ac29 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -517,9 +517,15 @@
public static final int GO_TO_SLEEP_REASON_DEVICE_FOLD = 13;
/**
+ * Go to sleep reason code: reason unknown.
* @hide
*/
- public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_DEVICE_FOLD;
+ public static final int GO_TO_SLEEP_REASON_UNKNOWN = 14;
+
+ /**
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_UNKNOWN;
/**
* @hide
@@ -540,6 +546,7 @@
case GO_TO_SLEEP_REASON_QUIESCENT: return "quiescent";
case GO_TO_SLEEP_REASON_SLEEP_BUTTON: return "sleep_button";
case GO_TO_SLEEP_REASON_TIMEOUT: return "timeout";
+ case GO_TO_SLEEP_REASON_UNKNOWN: return "unknown";
default: return Integer.toString(sleepReason);
}
}
@@ -635,6 +642,7 @@
GO_TO_SLEEP_REASON_QUIESCENT,
GO_TO_SLEEP_REASON_SLEEP_BUTTON,
GO_TO_SLEEP_REASON_TIMEOUT,
+ GO_TO_SLEEP_REASON_UNKNOWN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface GoToSleepReason{}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 34abf31..3fe63ab 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -1867,27 +1867,33 @@
switch (type) {
case HIERARCHY_OP_TYPE_REPARENT: return "reparent";
case HIERARCHY_OP_TYPE_REORDER: return "reorder";
- case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "ChildrenTasksReparent";
- case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "SetLaunchRoot";
- case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "SetAdjacentRoot";
- case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "LaunchTask";
- case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "SetAdjacentFlagRoot";
+ case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "childrenTasksReparent";
+ case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "setLaunchRoot";
+ case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "setAdjacentRoot";
+ case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "launchTask";
+ case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "setAdjacentFlagRoot";
case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT:
- return "SetDisableLaunchAdjacent";
- case HIERARCHY_OP_TYPE_PENDING_INTENT: return "PendingIntent";
- case HIERARCHY_OP_TYPE_START_SHORTCUT: return "StartShortcut";
+ return "setDisableLaunchAdjacent";
+ case HIERARCHY_OP_TYPE_PENDING_INTENT: return "pendingIntent";
+ case HIERARCHY_OP_TYPE_START_SHORTCUT: return "startShortcut";
+ case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: return "restoreTransientOrder";
case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: return "addInsetsFrameProvider";
case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
return "removeInsetsFrameProvider";
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
- case HIERARCHY_OP_TYPE_REMOVE_TASK: return "RemoveTask";
+ case HIERARCHY_OP_TYPE_REMOVE_TASK: return "removeTask";
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
- case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "ClearAdjacentRoot";
+ case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "clearAdjacentRoot";
case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
return "setReparentLeafTaskIfRelaunch";
case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
return "addTaskFragmentOperation";
+ case HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK:
+ return "movePipActivityToPinnedTask";
+ case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE: return "setIsTrimmable";
+ case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: return "restoreBackNav";
case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes";
+ case HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE: return "setKeyguardState";
default: return "HOP(" + type + ")";
}
}
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 82b3f68..71baed8 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -37,6 +37,7 @@
import android.util.TypedValue;
import android.view.View;
+import com.android.graphics.hwui.flags.Flags;
import com.android.internal.R;
import dalvik.annotation.optimization.FastNative;
@@ -525,6 +526,35 @@
}
}
+ @Override
+ public void setFilterBitmap(boolean filterBitmap) {
+ if (!Flags.animatedImageDrawableFilterBitmap()) {
+ super.setFilterBitmap(filterBitmap);
+ return;
+ }
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException(
+ "called setFilterBitmap on empty AnimatedImageDrawable"
+ );
+ }
+ if (nSetFilterBitmap(mState.mNativePtr, filterBitmap)) {
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public boolean isFilterBitmap() {
+ if (!Flags.animatedImageDrawableFilterBitmap()) {
+ return super.isFilterBitmap();
+ }
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException(
+ "called isFilterBitmap on empty AnimatedImageDrawable"
+ );
+ }
+ return nGetFilterBitmap(mState.mNativePtr);
+ }
+
private void postOnAnimationStart() {
if (mAnimationCallbacks == null) {
return;
@@ -618,4 +648,8 @@
private static native void nSetMirrored(long nativePtr, boolean mirror);
@FastNative
private static native void nSetBounds(long nativePtr, Rect rect);
+ @FastNative
+ private static native boolean nSetFilterBitmap(long nativePtr, boolean filterBitmap);
+ @FastNative
+ private static native boolean nGetFilterBitmap(long nativePtr);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 9aa5066..28b91c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -30,7 +30,6 @@
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
@@ -45,6 +44,7 @@
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
import android.annotation.IntDef;
import android.app.ActivityManager;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index affa6aa..d3ae411 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -23,7 +23,6 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.transitTypeToString;
import static com.android.wm.shell.common.pip.PipMenuController.ALPHA_NO_CHANGE;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
@@ -35,6 +34,7 @@
import static com.android.wm.shell.pip.PipTransitionState.UNDEFINED;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
import android.animation.AnimationHandler;
import android.animation.Animator;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 839bb4e..cc0e1df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -30,7 +30,6 @@
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -73,6 +72,7 @@
import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index a27c14bd..4feb475 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -24,7 +24,6 @@
import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -34,6 +33,7 @@
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
import android.annotation.ColorInt;
import android.annotation.NonNull;
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 7c9cd08..1d456ae 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
@@ -29,7 +29,6 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
-import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -725,7 +724,7 @@
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
- info.getDebugId(), transitionToken, info);
+ info.getDebugId(), transitionToken, info.toString(" " /* prefix */));
int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
final ActiveTransition existing = mKnownTransitions.get(transitionToken);
@@ -1847,6 +1846,40 @@
}
}
+ /**
+ * Like WindowManager#transitTypeToString(), but also covers known custom transition types as
+ * well.
+ */
+ public static String transitTypeToString(int transitType) {
+ if (transitType < TRANSIT_FIRST_CUSTOM) {
+ return WindowManager.transitTypeToString(transitType);
+ }
+
+ String typeStr = switch (transitType) {
+ case TRANSIT_EXIT_PIP -> "EXIT_PIP";
+ case TRANSIT_EXIT_PIP_TO_SPLIT -> "EXIT_PIP_TO_SPLIT";
+ case TRANSIT_REMOVE_PIP -> "REMOVE_PIP";
+ case TRANSIT_SPLIT_SCREEN_PAIR_OPEN -> "SPLIT_SCREEN_PAIR_OPEN";
+ case TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE -> "SPLIT_SCREEN_OPEN_TO_SIDE";
+ case TRANSIT_SPLIT_DISMISS_SNAP -> "SPLIT_DISMISS_SNAP";
+ case TRANSIT_SPLIT_DISMISS -> "SPLIT_DISMISS";
+ case TRANSIT_MAXIMIZE -> "MAXIMIZE";
+ case TRANSIT_RESTORE_FROM_MAXIMIZE -> "RESTORE_FROM_MAXIMIZE";
+ case TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP -> "DESKTOP_MODE_START_DRAG_TO_DESKTOP";
+ case TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> "DESKTOP_MODE_END_DRAG_TO_DESKTOP";
+ case TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP ->
+ "DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP";
+ case TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE -> "DESKTOP_MODE_TOGGLE_RESIZE";
+ case TRANSIT_RESIZE_PIP -> "RESIZE_PIP";
+ case TRANSIT_TASK_FRAGMENT_DRAG_RESIZE -> "TASK_FRAGMENT_DRAG_RESIZE";
+ case TRANSIT_SPLIT_PASSTHROUGH -> "SPLIT_PASSTHROUGH";
+ case TRANSIT_CLEANUP_PIP_EXIT -> "CLEANUP_PIP_EXIT";
+ case TRANSIT_MINIMIZE -> "MINIMIZE";
+ default -> "";
+ };
+ return typeStr + "(FIRST_CUSTOM+" + (transitType - TRANSIT_FIRST_CUSTOM) + ")";
+ }
+
private static boolean getShellTransitEnabled() {
try {
if (AppGlobals.getPackageManager().hasSystemFeature(
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index f255967..5ad788c 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -146,3 +146,11 @@
description: "Whether to have more information in ashmem filenames for bitmaps"
bug: "369619160"
}
+
+flag {
+ name: "animated_image_drawable_filter_bitmap"
+ is_exported: true
+ namespace: "core_graphics"
+ description: "API's that enable animated image drawables to use nearest sampling when scaling."
+ bug: "370523334"
+}
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 69613c7..5e379aa 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -347,4 +347,26 @@
return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration());
}
+bool AnimatedImageDrawable::getFilterBitmap() const {
+ const SkFilterMode kFilterBitmap = mSkAnimatedImage->getFilterMode();
+ if (kFilterBitmap == SkFilterMode::kLinear) {
+ return true;
+ }
+ return false;
+}
+
+bool AnimatedImageDrawable::setFilterBitmap(bool filterBitmap) {
+ if (filterBitmap) {
+ if (mSkAnimatedImage->getFilterMode() == SkFilterMode::kLinear) {
+ return false;
+ }
+ mSkAnimatedImage->setFilterMode(SkFilterMode::kLinear);
+ } else {
+ if (mSkAnimatedImage->getFilterMode() == SkFilterMode::kNearest) {
+ return false;
+ }
+ mSkAnimatedImage->setFilterMode(SkFilterMode::kNearest);
+ }
+ return true;
+}
} // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 1e965ab..2212324 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -87,6 +87,11 @@
bool isRunning();
int getRepetitionCount() const { return mSkAnimatedImage->getRepetitionCount(); }
void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); }
+ // Returns true if the filter mode is set to linear sampling; false if it is
+ // set to nearest neighbor sampling.
+ bool getFilterBitmap() const;
+ // Returns true if the filter mode was changed; false otherwise.
+ bool setFilterBitmap(bool filterBitmap);
void setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener> listener) {
mEndListener = std::move(listener);
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index b01e38d..2c8530d 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -276,6 +276,18 @@
drawable->setStagingBounds(rect);
}
+static jboolean AnimatedImageDrawable_nSetFilterBitmap(JNIEnv* env, jobject /*clazz*/,
+ jlong nativePtr, jboolean filterBitmap) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ return drawable->setFilterBitmap(filterBitmap);
+}
+
+static jboolean AnimatedImageDrawable_nGetFilterBitmap(JNIEnv* env, jobject /*clazz*/,
+ jlong nativePtr) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ return drawable->getFilterBitmap();
+}
+
static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
{"nCreate", "(JLandroid/graphics/ImageDecoder;IIJZLandroid/graphics/Rect;)J",
(void*)AnimatedImageDrawable_nCreate},
@@ -294,6 +306,8 @@
{"nNativeByteSize", "(J)J", (void*)AnimatedImageDrawable_nNativeByteSize},
{"nSetMirrored", "(JZ)V", (void*)AnimatedImageDrawable_nSetMirrored},
{"nSetBounds", "(JLandroid/graphics/Rect;)V", (void*)AnimatedImageDrawable_nSetBounds},
+ {"nSetFilterBitmap", "(JZ)Z", (void*)AnimatedImageDrawable_nSetFilterBitmap},
+ {"nGetFilterBitmap", "(J)Z", (void*)AnimatedImageDrawable_nGetFilterBitmap},
};
int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 520ba89..474ff8c 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -82,6 +82,13 @@
private boolean mRfDiscoveryStarted = false;
/**
+ * Broadcast Action: Sent on NFC stack initialization when NFC OEM extensions are enabled.
+ * <p> OEM extension modules should use this intent to start their extension service </p>
+ * @hide
+ */
+ public static final String ACTION_OEM_EXTENSION_INIT = "android.nfc.action.OEM_EXTENSION_INIT";
+
+ /**
* Mode Type for {@link #setControllerAlwaysOnMode(int)}.
* Enables the controller in default mode when NFC is disabled (existing API behavior).
* works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
index 5cb45e5..94c18cd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
@@ -16,11 +16,13 @@
package com.android.systemui.notifications.ui.composable
+import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.util.fastCoerceAtLeast
import androidx.compose.ui.util.fastCoerceAtMost
+import com.android.compose.nestedscroll.OnStopScope
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import com.android.compose.nestedscroll.ScrollController
@@ -43,6 +45,7 @@
isCurrentGestureOverscroll: () -> Boolean,
onStart: (Float) -> Unit = {},
onStop: (Float) -> Unit = {},
+ flingBehavior: FlingBehavior,
): PriorityNestedScrollConnection {
return PriorityNestedScrollConnection(
orientation = Orientation.Vertical,
@@ -77,8 +80,9 @@
return amountConsumed
}
- override suspend fun onStop(initialVelocity: Float): Float {
- onStop(initialVelocity)
+ override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
+ val consumedByScroll = flingToScroll(initialVelocity, flingBehavior)
+ onStop(initialVelocity - consumedByScroll)
if (scrimOffset() < minScrimOffset()) {
animateScrimOffset(minScrimOffset())
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index e1b74a9..d8abfd7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -18,7 +18,9 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@@ -30,6 +32,7 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastCoerceAtLeast
+import com.android.compose.nestedscroll.OnStopScope
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import com.android.compose.nestedscroll.ScrollController
import kotlin.math.max
@@ -46,32 +49,35 @@
val screenHeight =
with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
val overscrollOffset = remember { Animatable(0f) }
- val stackNestedScrollConnection = remember {
- NotificationStackNestedScrollConnection(
- stackOffset = { overscrollOffset.value },
- canScrollForward = canScrollForward,
- onScroll = { offsetAvailable ->
- coroutineScope.launch {
- val maxProgress = screenHeight * 0.2f
- val tilt = 3f
- var offset =
- overscrollOffset.value +
- maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt))
- offset = max(offset, -1f * maxProgress)
- overscrollOffset.snapTo(offset)
- }
- },
- onStop = { velocityAvailable ->
- coroutineScope.launch {
- overscrollOffset.animateTo(
- targetValue = 0f,
- initialVelocity = velocityAvailable,
- animationSpec = tween(),
- )
- }
- },
- )
- }
+ val flingBehavior = ScrollableDefaults.flingBehavior()
+ val stackNestedScrollConnection =
+ remember(flingBehavior) {
+ NotificationStackNestedScrollConnection(
+ stackOffset = { overscrollOffset.value },
+ canScrollForward = canScrollForward,
+ onScroll = { offsetAvailable ->
+ coroutineScope.launch {
+ val maxProgress = screenHeight * 0.2f
+ val tilt = 3f
+ var offset =
+ overscrollOffset.value +
+ maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt))
+ offset = max(offset, -1f * maxProgress)
+ overscrollOffset.snapTo(offset)
+ }
+ },
+ onStop = { velocityAvailable ->
+ coroutineScope.launch {
+ overscrollOffset.animateTo(
+ targetValue = 0f,
+ initialVelocity = velocityAvailable,
+ animationSpec = tween(),
+ )
+ }
+ },
+ flingBehavior = flingBehavior,
+ )
+ }
return this.then(
Modifier.nestedScroll(stackNestedScrollConnection).offset {
@@ -86,6 +92,7 @@
onStart: (Float) -> Unit = {},
onScroll: (Float) -> Unit,
onStop: (Float) -> Unit = {},
+ flingBehavior: FlingBehavior,
): PriorityNestedScrollConnection {
return PriorityNestedScrollConnection(
orientation = Orientation.Vertical,
@@ -106,8 +113,9 @@
return consumed
}
- override suspend fun onStop(initialVelocity: Float): Float {
- onStop(initialVelocity)
+ override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
+ val consumedByScroll = flingToScroll(initialVelocity, flingBehavior)
+ onStop(initialVelocity - consumedByScroll)
return initialVelocity
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 4fa1984..ae273d8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -25,6 +25,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollBy
@@ -451,12 +452,14 @@
}
}
+ val flingBehavior = ScrollableDefaults.flingBehavior()
val scrimNestedScrollConnection =
shadeSession.rememberSession(
scrimOffset,
maxScrimTop,
minScrimTop,
isCurrentGestureOverscroll,
+ flingBehavior,
) {
NotificationScrimNestedScrollConnection(
scrimOffset = { scrimOffset.value },
@@ -469,6 +472,7 @@
contentHeight = { stackHeight.intValue.toFloat() },
minVisibleScrimHeight = minVisibleScrimHeight,
isCurrentGestureOverscroll = { isCurrentGestureOverscroll.value },
+ flingBehavior = flingBehavior,
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 7c7202a..7872ffa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -27,6 +27,7 @@
import com.android.compose.animation.scene.content.Content
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
+import com.android.compose.nestedscroll.OnStopScope
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import com.android.compose.nestedscroll.ScrollController
import kotlin.math.absoluteValue
@@ -749,7 +750,7 @@
return dragController.onDrag(delta = deltaScroll)
}
- override suspend fun onStop(initialVelocity: Float): Float {
+ override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
return dragController
.onStop(velocity = initialVelocity, canChangeContent = canChangeScene)
.invoke()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index 255da31..a5be4dc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -16,6 +16,7 @@
package com.android.compose.nestedscroll
+import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -41,6 +42,7 @@
onHeightChanged: (Float) -> Unit,
minHeight: () -> Float,
maxHeight: () -> Float,
+ flingBehavior: FlingBehavior,
): PriorityNestedScrollConnection {
return PriorityNestedScrollConnection(
orientation = Orientation.Vertical,
@@ -55,7 +57,15 @@
offsetAvailable > 0 && height() < maxHeight()
},
canStartPostFling = { false },
- onStart = { LargeTopAppBarScrollController(height, maxHeight, minHeight, onHeightChanged) },
+ onStart = {
+ LargeTopAppBarScrollController(
+ height = height,
+ maxHeight = maxHeight,
+ minHeight = minHeight,
+ onHeightChanged = onHeightChanged,
+ flingBehavior = flingBehavior,
+ )
+ },
)
}
@@ -64,6 +74,7 @@
val maxHeight: () -> Float,
val minHeight: () -> Float,
val onHeightChanged: (Float) -> Unit,
+ val flingBehavior: FlingBehavior,
) : ScrollController {
override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
val currentHeight = height()
@@ -79,9 +90,8 @@
return amountConsumed
}
- override suspend fun onStop(initialVelocity: Float): Float {
- // Don't consume the velocity on pre/post fling
- return 0f
+ override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
+ return flingToScroll(initialVelocity, flingBehavior)
}
override fun onCancel() {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index ca44a5c..e924ebf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -16,7 +16,9 @@
package com.android.compose.nestedscroll
+import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -83,7 +85,16 @@
* @param initialVelocity The initial velocity of the scroll when stopping.
* @return The consumed [initialVelocity] when the animation completes.
*/
- suspend fun onStop(initialVelocity: Float): Float
+ suspend fun OnStopScope.onStop(initialVelocity: Float): Float
+}
+
+interface OnStopScope {
+ /**
+ * Emits scroll events by using the [initialVelocity] and the [FlingBehavior].
+ *
+ * @return consumed velocity
+ */
+ suspend fun flingToScroll(initialVelocity: Float, flingBehavior: FlingBehavior): Float
}
/**
@@ -307,7 +318,11 @@
val controller = requireController(isStopping = false)
return coroutineScope {
try {
- async { controller.onStop(velocity) }
+ async {
+ with(controller) {
+ OnStopScopeImpl(controller = controller).onStop(velocity)
+ }
+ }
// Allows others to interrupt the job.
.also { stoppingJob = it }
// Note: this can be cancelled by [interruptStopping]
@@ -336,3 +351,19 @@
offsetScrolledBeforePriorityMode = 0f
}
}
+
+private class OnStopScopeImpl(private val controller: ScrollController) : OnStopScope {
+ override suspend fun flingToScroll(
+ initialVelocity: Float,
+ flingBehavior: FlingBehavior,
+ ): Float {
+ return with(flingBehavior) {
+ object : ScrollScope {
+ override fun scrollBy(pixels: Float): Float {
+ return controller.onScroll(pixels, NestedScrollSource.SideEffect)
+ }
+ }
+ .performFling(initialVelocity)
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index a406e13..e27f9b5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -16,6 +16,8 @@
package com.android.compose.nestedscroll
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -29,6 +31,13 @@
val scrollSource = testCase.scrollSource
private var height = 0f
+ private val customFlingBehavior =
+ object : FlingBehavior {
+ override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+ scrollBy(initialVelocity)
+ return initialVelocity / 2f
+ }
+ }
private fun buildScrollConnection(heightRange: ClosedFloatingPointRange<Float>) =
LargeTopAppBarNestedScrollConnection(
@@ -36,6 +45,7 @@
onHeightChanged = { height = it },
minHeight = { heightRange.start },
maxHeight = { heightRange.endInclusive },
+ flingBehavior = customFlingBehavior,
)
private fun NestedScrollConnection.scroll(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 0364cdc..5442840 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -18,12 +18,15 @@
package com.android.compose.nestedscroll
+import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -41,7 +44,16 @@
private var consumeScroll = true
private var lastStop: Float? = null
private var isCancelled: Boolean = false
- private var consumeStop = true
+ private var onStopConsumeFlingToScroll = false
+ private var onStopConsumeAll = true
+
+ private val customFlingBehavior =
+ object : FlingBehavior {
+ override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+ scrollBy(initialVelocity)
+ return initialVelocity / 2f
+ }
+ }
private val scrollConnection =
PriorityNestedScrollConnection(
@@ -57,9 +69,16 @@
return if (consumeScroll) deltaScroll else 0f
}
- override suspend fun onStop(initialVelocity: Float): Float {
+ override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
lastStop = initialVelocity
- return if (consumeStop) initialVelocity else 0f
+ var velocityConsumed = 0f
+ if (onStopConsumeFlingToScroll) {
+ velocityConsumed = flingToScroll(initialVelocity, customFlingBehavior)
+ }
+ if (onStopConsumeAll) {
+ velocityConsumed = initialVelocity
+ }
+ return velocityConsumed
}
override fun onCancel() {
@@ -178,6 +197,22 @@
}
@Test
+ fun onStopScrollUsingFlingToScroll() = runMonotonicClockTest {
+ startPriorityModePostScroll()
+ onStopConsumeFlingToScroll = true
+ onStopConsumeAll = false
+ lastScroll = Float.NaN
+
+ val consumed = scrollConnection.onPreFling(available = Velocity(2f, 2f))
+
+ assertThat(lastStop).isEqualTo(2f)
+ // flingToScroll should try to scroll the content, customFlingBehavior uses the velocity.
+ assertThat(lastScroll).isEqualTo(2f)
+ // customFlingBehavior returns half of the vertical velocity.
+ assertThat(consumed).isEqualTo(Velocity(0f, 1f))
+ }
+
+ @Test
fun ifCannotStopOnPreFling_shouldStopOnPostFling() = runTest {
startPriorityModePostScroll()
canStopOnPreFling = false
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
index 7816d3b..bea1010 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
@@ -367,6 +367,39 @@
}
@Test
+ fun testMaxSpansLessThanCurrentSpan() =
+ testScope.runTest {
+ // heightPerSpan =
+ // (viewportHeightPx - verticalContentPaddingPx - (totalSpans - 1)
+ // * verticalItemSpacingPx) / totalSpans
+ // = 145.3333
+ // maxSpans = (maxHeightPx + verticalItemSpacing) /
+ // (heightPerSpanPx + verticalItemSpacingPx)
+ // = 4.72
+ // This is invalid because the max span calculation comes out to be less than
+ // the current span. Ensure we handle this case correctly.
+ val layout =
+ GridLayout(
+ verticalItemSpacingPx = 100f,
+ currentRow = 0,
+ minHeightPx = 480,
+ maxHeightPx = 1060,
+ currentSpan = 6,
+ resizeMultiple = 3,
+ totalSpans = 6,
+ viewportHeightPx = 1600,
+ verticalContentPaddingPx = 228f,
+ )
+ updateGridLayout(layout)
+
+ val topState = underTest.topDragState
+ val bottomState = underTest.bottomDragState
+
+ assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+ assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, -3 to -736f)
+ }
+
+ @Test
fun testCanExpand_atTopPosition_withMultipleAnchors_returnsTrue() =
testScope.runTest {
val twoRowGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
index 75479ad..60b95ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -35,6 +37,13 @@
private var scrimOffset = 0f
private var contentHeight = 0f
private var isCurrentGestureOverscroll = false
+ private val customFlingBehavior =
+ object : FlingBehavior {
+ override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+ scrollBy(initialVelocity)
+ return initialVelocity / 2f
+ }
+ }
private val scrollConnection =
NotificationScrimNestedScrollConnection(
@@ -51,6 +60,7 @@
wasStarted = true
isStarted = false
},
+ flingBehavior = customFlingBehavior,
)
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 5052a00..198821f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -44,6 +44,8 @@
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -59,6 +61,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -88,6 +91,8 @@
@Mock private ConfigurationController mConfigurationController;
@Mock private UserTracker mUserTracker;
@Mock private DozeInteractor mDozeInteractor;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback;
/**
@@ -134,6 +139,7 @@
mStatusBarStateController,
mUserTracker,
mDozeInteractor,
+ mKeyguardTransitionInteractor,
secureSettings
);
@@ -286,6 +292,18 @@
assertTrue(mDozeParameters.shouldControlScreenOff());
}
+ @Test
+ public void shouldDelayDisplayDozeTransition_True_WhenTransitioningToAod() {
+ setShouldControlUnlockedScreenOffForTest(false);
+ when(mScreenOffAnimationController.shouldDelayDisplayDozeTransition()).thenReturn(false);
+ when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo())
+ .thenReturn(KeyguardState.LOCKSCREEN);
+ assertFalse(mDozeParameters.shouldDelayDisplayDozeTransition());
+
+ when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo())
+ .thenReturn(KeyguardState.AOD);
+ assertTrue(mDozeParameters.shouldDelayDisplayDozeTransition());
+ }
@Test
public void keyguardVisibility_changesControlScreenOffAnimation() {
diff --git a/packages/SystemUI/res/drawable/volume_dialog_background_small_radius.xml b/packages/SystemUI/res/drawable/volume_dialog_background_small_radius.xml
new file mode 100644
index 0000000..7d79496
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_dialog_background_small_radius.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/volume_dialog_background_square_corner_radius" />
+ <solid android:color="?androidprv:attr/materialColorSurface" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
index df8521a..131201e 100644
--- a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
+++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
@@ -17,8 +17,8 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:paddingMode="stack" >
<size
- android:height="@dimen/volume_ringer_item_size"
- android:width="@dimen/volume_ringer_item_size" />
+ android:height="@dimen/volume_dialog_ringer_drawer_button_size"
+ android:width="@dimen/volume_dialog_ringer_drawer_button_size" />
<solid android:color="?androidprv:attr/materialColorPrimary" />
- <corners android:radius="360dp" />
+ <corners android:radius="@dimen/volume_dialog_ringer_selected_button_background_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml b/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
index 7ddd57b7..a8c9818 100644
--- a/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
+++ b/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
@@ -16,7 +16,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle" >
- <size android:width="@dimen/volume_ringer_item_size" android:height="@dimen/volume_ringer_item_size"/>
+ <size android:width="@dimen/volume_dialog_ringer_drawer_button_size" android:height="@dimen/volume_dialog_ringer_drawer_button_size"/>
<solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
- <corners android:radius="@dimen/volume_ringer_item_radius" />
+ <corners android:radius="@dimen/volume_dialog_background_square_corner_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 8b39e5e..e90f055f 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -40,9 +40,9 @@
android:layout_height="wrap_content"
android:background="@drawable/volume_dialog_background"
android:divider="@drawable/volume_dialog_spacer"
+ android:paddingVertical="@dimen/volume_dialog_vertical_padding"
android:gravity="center_horizontal"
android:orientation="vertical"
- android:paddingVertical="@dimen/volume_dialog_vertical_padding"
android:showDividers="middle">
<include layout="@layout/volume_ringer_drawer" />
diff --git a/packages/SystemUI/res/layout/volume_ringer_button.xml b/packages/SystemUI/res/layout/volume_ringer_button.xml
new file mode 100644
index 0000000..dc6780a
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_ringer_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" >
+
+ <ImageButton
+ android:id="@+id/volume_drawer_button"
+ android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
+ android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
+ android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
+ android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
+ android:contentDescription="@string/volume_ringer_mode"
+ android:gravity="center"
+ android:src="@drawable/volume_ringer_item_bg"
+ android:background="@drawable/volume_ringer_item_bg"/>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 94b55b1..9b3af52 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -20,9 +20,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
- android:paddingLeft="@dimen/volume_dialog_ringer_container_padding"
- android:paddingTop="@dimen/volume_dialog_ringer_container_padding"
- android:paddingRight="@dimen/volume_dialog_ringer_container_padding"
+ android:paddingLeft="@dimen/volume_dialog_ringer_horizontal_padding"
+ android:paddingRight="@dimen/volume_dialog_ringer_horizontal_padding"
android:layoutDirection="ltr"
android:clipToPadding="false"
android:clipChildren="false"
@@ -31,7 +30,6 @@
<!-- Drawer view, invisible by default. -->
<FrameLayout
android:id="@+id/volume_drawer_container"
- android:alpha="0.0"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
@@ -40,9 +38,8 @@
<FrameLayout
android:id="@+id/volume_drawer_selection_background"
android:alpha="0.0"
- android:layout_width="@dimen/volume_ringer_item_size"
- android:layout_marginBottom="8dp"
- android:layout_height="@dimen/volume_ringer_item_size"
+ android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
+ android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
android:layout_gravity="bottom|right"
android:background="@drawable/volume_drawer_selection_bg" />
@@ -52,64 +49,7 @@
android:layout_height="wrap_content"
android:orientation="vertical">
- <FrameLayout
- android:id="@+id/volume_drawer_vibrate"
- android:layout_width="@dimen/volume_ringer_item_size"
- android:layout_height="@dimen/volume_ringer_item_size"
- android:layout_marginBottom="8dp"
- android:contentDescription="@string/volume_ringer_hint_vibrate"
- android:gravity="center"
- android:background="@drawable/volume_ringer_item_bg">
-
- <ImageView
- android:id="@+id/volume_drawer_vibrate_icon"
- android:layout_width="@dimen/volume_ringer_icon_size"
- android:layout_height="@dimen/volume_ringer_icon_size"
- android:layout_gravity="center"
- android:src="@drawable/ic_volume_ringer_vibrate"
- android:tint="?androidprv:attr/materialColorOnSurface" />
-
- </FrameLayout>
-
- <FrameLayout
- android:id="@+id/volume_drawer_mute"
- android:layout_width="@dimen/volume_ringer_item_size"
- android:layout_height="@dimen/volume_ringer_item_size"
- android:layout_marginBottom="8dp"
- android:accessibilityTraversalAfter="@id/volume_drawer_vibrate"
- android:contentDescription="@string/volume_ringer_hint_mute"
- android:gravity="center"
- android:background="@drawable/volume_ringer_item_bg">
-
- <ImageView
- android:id="@+id/volume_drawer_mute_icon"
- android:layout_width="@dimen/volume_ringer_icon_size"
- android:layout_height="@dimen/volume_ringer_icon_size"
- android:layout_gravity="center"
- android:src="@drawable/ic_speaker_mute"
- android:tint="?androidprv:attr/materialColorOnSurface" />
-
- </FrameLayout>
-
- <FrameLayout
- android:id="@+id/volume_drawer_normal"
- android:layout_width="@dimen/volume_ringer_item_size"
- android:layout_height="@dimen/volume_ringer_item_size"
- android:layout_marginBottom="8dp"
- android:accessibilityTraversalAfter="@id/volume_drawer_mute"
- android:contentDescription="@string/volume_ringer_hint_unmute"
- android:gravity="center"
- android:background="@drawable/volume_ringer_item_bg">
-
- <ImageView
- android:id="@+id/volume_drawer_normal_icon"
- android:layout_width="@dimen/volume_ringer_icon_size"
- android:layout_height="@dimen/volume_ringer_icon_size"
- android:layout_gravity="center"
- android:src="@drawable/ic_speaker_on"
- android:tint="?androidprv:attr/materialColorOnSurface" />
-
- </FrameLayout>
+ <!-- add ringer buttons here -->
</LinearLayout>
@@ -117,23 +57,16 @@
<!-- The current ringer selection. When the drawer is opened, this animates to the corresponding
position in the drawer. When the drawer is closed, it animates back. -->
- <FrameLayout
- android:id="@+id/volume_new_ringer_active_icon_container"
- android:layout_width="@dimen/volume_ringer_item_size"
- android:layout_height="@dimen/volume_ringer_item_size"
- android:layout_marginBottom="8dp"
- android:layout_gravity="bottom|right"
+ <ImageButton
+ android:id="@+id/volume_new_ringer_active_button"
+ android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
+ android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
+ android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
+ android:background="@drawable/volume_drawer_selection_bg"
android:contentDescription="@string/volume_ringer_change"
- android:background="@drawable/volume_drawer_selection_bg">
-
- <ImageView
- android:id="@+id/volume_new_ringer_active_icon"
- android:layout_width="@dimen/volume_ringer_icon_size"
- android:layout_height="@dimen/volume_ringer_icon_size"
- android:layout_gravity="center"
- android:tint="?androidprv:attr/materialColorOnPrimary"
- android:src="@drawable/ic_volume_media" />
-
- </FrameLayout>
+ android:gravity="center"
+ android:padding="10dp"
+ android:src="@drawable/ic_volume_media"
+ android:tint="?androidprv:attr/materialColorOnPrimary" />
</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c5e205c..7fa2879 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -2064,7 +2064,7 @@
<!-- Volume start -->
<dimen name="volume_dialog_background_corner_radius">30dp</dimen>
<dimen name="volume_dialog_width">60dp</dimen>
- <dimen name="volume_dialog_vertical_padding">6dp</dimen>
+ <dimen name="volume_dialog_vertical_padding">10dp</dimen>
<dimen name="volume_dialog_components_spacing">8dp</dimen>
<dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen>
<dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
@@ -2078,10 +2078,10 @@
<dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
<dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
- <dimen name="volume_dialog_ringer_container_padding">10dp</dimen>
- <dimen name="volume_ringer_item_size">40dp</dimen>
- <dimen name="volume_ringer_icon_size">20dp</dimen>
- <dimen name="volume_ringer_item_radius">12dp</dimen>
- <dimen name="volume_dialog_ringer_item_margin_bottom">8dp</dimen>
+ <dimen name="volume_dialog_ringer_horizontal_padding">10dp</dimen>
+ <dimen name="volume_dialog_ringer_drawer_button_size">40dp</dimen>
+ <dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen>
+ <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
+ <dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen>
<!-- Volume end -->
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ab64ae5..1766cdf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1797,6 +1797,7 @@
<string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
<string name="volume_ringer_change">Tap to change ringer mode</string>
+ <string name="volume_ringer_mode">ringer mode</string>
<!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
<string name="volume_ringer_hint_mute">mute</string>
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
index bde5d0f..113375f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
@@ -76,10 +76,10 @@
floor(spans.toDouble() / resizeMultiple).toInt() * resizeMultiple
val maxSpans: Int
- get() = roundDownToMultiple(getSpansForPx(maxHeightPx))
+ get() = roundDownToMultiple(getSpansForPx(maxHeightPx)).coerceAtLeast(currentSpan)
val minSpans: Int
- get() = roundDownToMultiple(getSpansForPx(minHeightPx))
+ get() = roundDownToMultiple(getSpansForPx(minHeightPx)).coerceAtMost(currentSpan)
}
/** Check if widget can expanded based on current drag states */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 30f564f..3a24ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -61,15 +61,6 @@
return row.getEntry().getSbn().getNotification();
}
};
- private static final ResultApplicator GREY_APPLICATOR = new ResultApplicator() {
- @Override
- public void apply(View parent, View view, boolean apply, boolean reset) {
- CachingIconView icon = view.findViewById(com.android.internal.R.id.icon);
- if (icon != null) {
- icon.setGrayedOut(apply);
- }
- }
- };
private final ExpandableNotificationRow mRow;
private final ArrayList<Processor> mProcessors = new ArrayList<>();
@@ -78,14 +69,18 @@
public NotificationGroupingUtil(ExpandableNotificationRow row) {
mRow = row;
- final IconComparator iconVisibilityComparator = new IconComparator(mRow) {
+ final IconComparator iconVisibilityComparator = new IconComparator() {
public boolean compare(View parent, View child, Object parentData,
Object childData) {
+ if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
+ // Icon is always the same when we're showing the app icon.
+ return true;
+ }
return hasSameIcon(parentData, childData)
&& hasSameColor(parentData, childData);
}
};
- final IconComparator greyComparator = new IconComparator(mRow) {
+ final IconComparator greyComparator = new IconComparator() {
public boolean compare(View parent, View child, Object parentData,
Object childData) {
if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
@@ -95,6 +90,19 @@
|| hasSameColor(parentData, childData);
}
};
+ final ResultApplicator greyApplicator = new ResultApplicator() {
+ @Override
+ public void apply(View parent, View view, boolean apply, boolean reset) {
+ if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
+ // Do nothing.
+ return;
+ }
+ CachingIconView icon = view.findViewById(com.android.internal.R.id.icon);
+ if (icon != null) {
+ icon.setGrayedOut(apply);
+ }
+ }
+ };
// To hide the icons if they are the same and the color is the same
mProcessors.add(new Processor(mRow,
@@ -107,7 +115,7 @@
com.android.internal.R.id.status_bar_latest_event_content,
ICON_EXTRACTOR,
greyComparator,
- GREY_APPLICATOR));
+ greyApplicator));
// To show the large icon on the left side instead if all the small icons are the same
mProcessors.add(new Processor(mRow,
com.android.internal.R.id.status_bar_latest_event_content,
@@ -319,13 +327,14 @@
private interface ViewComparator {
/**
- * @param parent the view with the given id in the group header
- * @param child the view with the given id in the child notification
+ * @param parent the view with the given id in the group header
+ * @param child the view with the given id in the child notification
* @param parentData optional data for the parent
- * @param childData optional data for the child
+ * @param childData optional data for the child
* @return whether to views are the same
*/
boolean compare(View parent, View child, Object parentData, Object childData);
+
boolean isEmpty(View view);
}
@@ -368,21 +377,12 @@
}
private abstract static class IconComparator implements ViewComparator {
- private final ExpandableNotificationRow mRow;
-
- IconComparator(ExpandableNotificationRow row) {
- mRow = row;
- }
-
@Override
public boolean compare(View parent, View child, Object parentData, Object childData) {
return false;
}
protected boolean hasSameIcon(Object parentData, Object childData) {
- if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
- return true;
- }
Icon parentIcon = getIcon((Notification) parentData);
Icon childIcon = getIcon((Notification) childData);
return parentIcon.sameAs(childIcon);
@@ -420,9 +420,9 @@
private interface ResultApplicator {
/**
* @param parent the root view of the child notification
- * @param view the view with the given id in the child notification
- * @param apply whether the state should be applied or removed
- * @param reset if [de]application is the result of a reset
+ * @param view the view with the given id in the child notification
+ * @param apply whether the state should be applied or removed
+ * @param reset if [de]application is the result of a reset
*/
void apply(View parent, View view, boolean apply, boolean reset);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 98869be..4915b84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -45,6 +45,8 @@
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
@@ -85,6 +87,7 @@
private final BatteryController mBatteryController;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final DozeInteractor mDozeInteractor;
+ private final KeyguardTransitionInteractor mTransitionInteractor;
private final FoldAodAnimationController mFoldAodAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private final UserTracker mUserTracker;
@@ -134,6 +137,7 @@
StatusBarStateController statusBarStateController,
UserTracker userTracker,
DozeInteractor dozeInteractor,
+ KeyguardTransitionInteractor transitionInteractor,
SecureSettings secureSettings) {
mResources = resources;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
@@ -148,6 +152,7 @@
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mUserTracker = userTracker;
mDozeInteractor = dozeInteractor;
+ mTransitionInteractor = transitionInteractor;
mSecureSettings = secureSettings;
keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
@@ -353,6 +358,9 @@
* delayed for a few seconds. This might be useful to play animations without reducing FPS.
*/
public boolean shouldDelayDisplayDozeTransition() {
+ if (mTransitionInteractor.getTransitionState().getValue().getTo() == KeyguardState.AOD) {
+ return true;
+ }
return willAnimateFromLockScreenToAod()
|| mScreenOffAnimationController.shouldDelayDisplayDozeTransition();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index c05a0b2..6816d35 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -16,15 +16,26 @@
package com.android.systemui.volume.dialog.ringer.ui.binder
+import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import androidx.annotation.LayoutRes
+import androidx.compose.ui.util.fastForEachIndexed
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.setSnapshotBinding
import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.res.R
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonViewModel
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerDrawerState
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModel
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModelState
import com.android.systemui.volume.dialog.ringer.ui.viewmodel.VolumeDialogRingerDrawerViewModel
import javax.inject.Inject
-import kotlinx.coroutines.awaitCancellation
+import kotlin.math.abs
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
@VolumeDialogScope
class VolumeDialogRingerViewBinder
@@ -33,16 +44,124 @@
fun bind(view: View) {
with(view) {
+ val drawerAndRingerContainer =
+ requireViewById<View>(R.id.volume_ringer_and_drawer_container)
+ val drawerContainer = requireViewById<View>(R.id.volume_drawer_container)
+ val selectedButtonView =
+ requireViewById<ImageButton>(R.id.volume_new_ringer_active_button)
+ val volumeDialogView = requireViewById<ViewGroup>(R.id.volume_dialog)
repeatWhenAttached {
viewModel(
traceName = "VolumeDialogRingerViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
factory = { viewModelFactory.create() },
) { viewModel ->
- setSnapshotBinding {}
- awaitCancellation()
+ viewModel.ringerViewModel
+ .onEach { ringerState ->
+ when (ringerState) {
+ is RingerViewModelState.Available -> {
+ val uiModel = ringerState.uiModel
+
+ bindSelectedButton(viewModel, uiModel, selectedButtonView)
+ bindDrawerButtons(viewModel, uiModel.availableButtons)
+
+ // Set up views background and visibility
+ drawerAndRingerContainer.visibility = View.VISIBLE
+ when (uiModel.drawerState) {
+ is RingerDrawerState.Initial -> {
+ drawerContainer.visibility = View.GONE
+ selectedButtonView.visibility = View.VISIBLE
+ volumeDialogView.setBackgroundResource(
+ R.drawable.volume_dialog_background
+ )
+ }
+
+ is RingerDrawerState.Closed -> {
+ drawerContainer.visibility = View.GONE
+ selectedButtonView.visibility = View.VISIBLE
+ volumeDialogView.setBackgroundResource(
+ R.drawable.volume_dialog_background
+ )
+ }
+
+ is RingerDrawerState.Open -> {
+ drawerContainer.visibility = View.VISIBLE
+ selectedButtonView.visibility = View.GONE
+ if (
+ uiModel.currentButtonIndex !=
+ uiModel.availableButtons.size - 1
+ ) {
+ volumeDialogView.setBackgroundResource(
+ R.drawable.volume_dialog_background_small_radius
+ )
+ }
+ }
+ }
+ }
+
+ is RingerViewModelState.Unavailable -> {
+ drawerAndRingerContainer.visibility = View.GONE
+ volumeDialogView.setBackgroundResource(
+ R.drawable.volume_dialog_background
+ )
+ }
+ }
+ }
+ .launchIn(this)
}
}
}
}
+
+ private fun View.bindDrawerButtons(
+ viewModel: VolumeDialogRingerDrawerViewModel,
+ availableButtons: List<RingerButtonViewModel?>,
+ ) {
+ val drawerOptions = requireViewById<ViewGroup>(R.id.volume_drawer_options)
+ val count = availableButtons.size
+ drawerOptions.ensureChildCount(R.layout.volume_ringer_button, count)
+
+ availableButtons.fastForEachIndexed { index, ringerButton ->
+ ringerButton?.let {
+ drawerOptions.getChildAt(count - index - 1).bindDrawerButton(it, viewModel)
+ }
+ }
+ }
+
+ private fun View.bindDrawerButton(
+ buttonViewModel: RingerButtonViewModel,
+ viewModel: VolumeDialogRingerDrawerViewModel,
+ ) {
+ with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
+ setImageResource(buttonViewModel.imageResId)
+ contentDescription = context.getString(buttonViewModel.contentDescriptionResId)
+ setOnClickListener { viewModel.onRingerButtonClicked(buttonViewModel.ringerMode) }
+ }
+ }
+
+ private fun ViewGroup.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int) {
+ val childCountDelta = childCount - count
+ when {
+ childCountDelta > 0 -> {
+ removeViews(0, childCountDelta)
+ }
+ childCountDelta < 0 -> {
+ val inflater = LayoutInflater.from(context)
+ repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) }
+ }
+ }
+ }
+
+ private fun bindSelectedButton(
+ viewModel: VolumeDialogRingerDrawerViewModel,
+ uiModel: RingerViewModel,
+ selectedButtonView: ImageButton,
+ ) {
+ with(uiModel) {
+ selectedButtonView.setImageResource(selectedButton.imageResId)
+ selectedButtonView.setOnClickListener {
+ viewModel.onRingerButtonClicked(selectedButton.ringerMode)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
index a09bfeb..96d4f62 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
@@ -22,6 +22,8 @@
val availableButtons: List<RingerButtonViewModel?>,
/** The index of the currently selected button */
val currentButtonIndex: Int,
+ /** Currently selected button. */
+ val selectedButton: RingerButtonViewModel,
/** For open and close animations */
val drawerState: RingerDrawerState,
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
index 5b73107..d4da226 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -41,8 +41,6 @@
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
-private const val TAG = "VolumeDialogRingerDrawerViewModel"
-
class VolumeDialogRingerDrawerViewModel
@AssistedInject
constructor(
@@ -111,31 +109,45 @@
return if (currentIndex == -1 || isSingleVolume) {
RingerViewModelState.Unavailable
} else {
- RingerViewModelState.Available(
- RingerViewModel(
- availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
- currentButtonIndex = currentIndex,
- drawerState = drawerState,
+ toButtonViewModel(currentRingerMode, isSelectedButton = true)?.let {
+ RingerViewModelState.Available(
+ RingerViewModel(
+ availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
+ currentButtonIndex = currentIndex,
+ selectedButton = it,
+ drawerState = drawerState,
+ )
)
- )
+ } ?: RingerViewModelState.Unavailable
}
}
private fun VolumeDialogRingerModel.toButtonViewModel(
- ringerMode: RingerMode
+ ringerMode: RingerMode,
+ isSelectedButton: Boolean = false,
): RingerButtonViewModel? {
return when (ringerMode.value) {
RINGER_MODE_SILENT ->
RingerButtonViewModel(
imageResId = R.drawable.ic_speaker_mute,
- contentDescriptionResId = R.string.volume_ringer_status_silent,
+ contentDescriptionResId =
+ if (isSelectedButton) {
+ R.string.volume_ringer_status_silent
+ } else {
+ R.string.volume_ringer_hint_mute
+ },
hintLabelResId = R.string.volume_ringer_hint_unmute,
ringerMode = ringerMode,
)
RINGER_MODE_VIBRATE ->
RingerButtonViewModel(
imageResId = R.drawable.ic_volume_ringer_vibrate,
- contentDescriptionResId = R.string.volume_ringer_status_vibrate,
+ contentDescriptionResId =
+ if (isSelectedButton) {
+ R.string.volume_ringer_status_vibrate
+ } else {
+ R.string.volume_ringer_hint_vibrate
+ },
hintLabelResId = R.string.volume_ringer_hint_vibrate,
ringerMode = ringerMode,
)
@@ -143,8 +155,18 @@
when {
isMuted && isEnabled ->
RingerButtonViewModel(
- imageResId = R.drawable.ic_speaker_mute,
- contentDescriptionResId = R.string.volume_ringer_status_normal,
+ imageResId =
+ if (isSelectedButton) {
+ R.drawable.ic_speaker_mute
+ } else {
+ R.drawable.ic_speaker_on
+ },
+ contentDescriptionResId =
+ if (isSelectedButton) {
+ R.string.volume_ringer_status_normal
+ } else {
+ R.string.volume_ringer_hint_unmute
+ },
hintLabelResId = R.string.volume_ringer_hint_unmute,
ringerMode = ringerMode,
)
@@ -152,7 +174,12 @@
availableModes.contains(RingerMode(RINGER_MODE_VIBRATE)) ->
RingerButtonViewModel(
imageResId = R.drawable.ic_speaker_on,
- contentDescriptionResId = R.string.volume_ringer_status_normal,
+ contentDescriptionResId =
+ if (isSelectedButton) {
+ R.string.volume_ringer_status_normal
+ } else {
+ R.string.volume_ringer_hint_unmute
+ },
hintLabelResId = R.string.volume_ringer_hint_vibrate,
ringerMode = ringerMode,
)
@@ -160,7 +187,12 @@
else ->
RingerButtonViewModel(
imageResId = R.drawable.ic_speaker_on,
- contentDescriptionResId = R.string.volume_ringer_status_normal,
+ contentDescriptionResId =
+ if (isSelectedButton) {
+ R.string.volume_ringer_status_normal
+ } else {
+ R.string.volume_ringer_hint_unmute
+ },
hintLabelResId = R.string.volume_ringer_hint_mute,
ringerMode = ringerMode,
)
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 9002e40..0f16352 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -165,6 +165,17 @@
RavenwoodSystemProperties.initialize(RAVENWOOD_BUILD_PROP);
setSystemProperties(null);
+ // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
+ // before loadFrameworkNativeCode() (which uses $ANDROID_LOG_TAGS).
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
+ try {
+ Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
+ } catch (ErrnoException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
// Make sure libandroid_runtime is loaded.
RavenwoodNativeLoader.loadFrameworkNativeCode();
@@ -175,15 +186,6 @@
Objects.requireNonNull(Build.TYPE);
Objects.requireNonNull(Build.VERSION.SDK);
- if (RAVENWOOD_VERBOSE_LOGGING) {
- RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
- try {
- Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
- } catch (ErrnoException e) {
- // Shouldn't happen.
- }
- }
-
System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
// This will let AndroidJUnit4 use the original runner.
System.setProperty("android.junit.runner",
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 3ff0848..2a3c26e 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -173,6 +173,24 @@
throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
}
+static void maybeRedirectLog() {
+ auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
+ if (ravenwoodLogOut == NULL) {
+ return;
+ }
+ ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut);
+
+ // Redirect stdin / stdout to /dev/tty.
+ int ttyFd = open(ravenwoodLogOut, O_WRONLY);
+ if (ttyFd == -1) {
+ ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut,
+ strerror(errno));
+ return;
+ }
+ dup2(ttyFd, 1);
+ dup2(ttyFd, 2);
+}
+
// ---- Registration ----
extern void register_android_system_OsConstants(JNIEnv* env);
@@ -192,6 +210,8 @@
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+ maybeRedirectLog();
+
ALOGI("%s: JNI_OnLoad", __FILE__);
JNIEnv* env = GetJNIEnvOrDie(vm);
diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
index ce0033d..4895a1a 100644
--- a/ravenwood/tests/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -43,9 +43,12 @@
// To make sure it won't cause VerifyError (b/324063814)
"platformprotosnano",
+
+ "com.android.internal.os.flags-aconfig-java",
],
srcs: [
"test/**/*.java",
+ "test/**/*.kt",
],
jni_libs: [
"libravenwoodbivalenttest_jni",
@@ -58,10 +61,12 @@
// TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
exclude_srcs: [
"test/**/ravenizer/*.java",
+ "test/**/ravenizer/*.kt",
],
static_libs: [
"junit",
"truth",
+ "flag-junit",
"ravenwood-junit",
],
test_suites: [
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
new file mode 100644
index 0000000..fd6d6fb
--- /dev/null
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.aconfig
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.os.Flags
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigSimpleReadTests {
+ @Test
+ fun testFalseFlags() {
+ assertFalse(Flags.ravenwoodFlagRo1())
+ assertFalse(Flags.ravenwoodFlagRw1())
+ }
+
+ @Test
+ @Ignore // TODO: Enable this test after rolling out the "2" flags.
+ fun testTrueFlags() {
+ assertTrue(Flags.ravenwoodFlagRo2())
+ assertTrue(Flags.ravenwoodFlagRw2())
+ }
+}
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigCheckFlagsRuleTests {
+ @Rule
+ @JvmField
+ val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_RAVENWOOD_FLAG_RO_1)
+ fun testRequireFlagsEnabledRo() {
+ fail("This test shouldn't be executed")
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_RAVENWOOD_FLAG_RW_1)
+ fun testRequireFlagsEnabledRw() {
+ fail("This test shouldn't be executed")
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RO_2)
+ @Ignore // TODO: Enable this test after rolling out the "2" flags.
+ fun testRequireFlagsDisabledRo() {
+ fail("This test shouldn't be executed")
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RW_2)
+ @Ignore // TODO: Enable this test after rolling out the "2" flags.
+ fun testRequireFlagsDisabledRw() {
+ fail("This test shouldn't be executed")
+ }
+}
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigSetFlagsRuleWithDefaultTests {
+ @Rule
+ @JvmField
+ val setFlagsRule = SetFlagsRule()
+
+ @Test
+ @EnableFlags(Flags.FLAG_RAVENWOOD_FLAG_RO_1)
+ fun testSetRoFlag() {
+ assertTrue(Flags.ravenwoodFlagRo1())
+ assertFalse(Flags.ravenwoodFlagRw1())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RAVENWOOD_FLAG_RW_1)
+ fun testSetRwFlag() {
+ assertFalse(Flags.ravenwoodFlagRo1())
+ assertTrue(Flags.ravenwoodFlagRw1())
+ }
+}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index a02082d..f47aaba 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -327,6 +327,10 @@
return (this.access and Opcodes.ACC_SYNTHETIC) != 0
}
+fun ClassNode.isAbstract(): Boolean {
+ return (this.access and Opcodes.ACC_ABSTRACT) != 0
+}
+
fun MethodNode.isSynthetic(): Boolean {
return (this.access and Opcodes.ACC_SYNTHETIC) != 0
}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index 32dcbe5..a0e5599 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -46,7 +46,7 @@
var enableValidation: SetOnce<Boolean> = SetOnce(true),
/** Whether the validation failure is fatal or not. */
- var fatalValidation: SetOnce<Boolean> = SetOnce(false),
+ var fatalValidation: SetOnce<Boolean> = SetOnce(true),
/** Whether to remove mockito and dexmaker classes. */
var stripMockito: SetOnce<Boolean> = SetOnce(false),
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
index 27092d2..8ec0932 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
@@ -16,10 +16,12 @@
package com.android.platform.test.ravenwood.ravenizer
import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.isAbstract
import com.android.hoststubgen.asm.startsWithAny
import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.log
import org.objectweb.asm.tree.ClassNode
+import java.util.regex.Pattern
fun validateClasses(classes: ClassNodes): Boolean {
var allOk = true
@@ -41,25 +43,35 @@
}
var allOk = true
+ log.i("Checking ${cn.name.toHumanReadableClassName()}")
+
// See if there's any class that extends a legacy base class.
// But ignore the base classes in android.test.
- if (!cn.name.startsWithAny("android/test/")) {
- allOk = checkSuperClass(cn, cn, classes) && allOk
+ if (!cn.isAbstract() && !cn.name.startsWith("android/test/")
+ && !isAllowListedLegacyTest(cn)
+ ) {
+ allOk = checkSuperClassForJunit3(cn, cn, classes) && allOk
}
return allOk
}
-fun checkSuperClass(targetClass: ClassNode, currentClass: ClassNode, classes: ClassNodes): Boolean {
+fun checkSuperClassForJunit3(
+ targetClass: ClassNode,
+ currentClass: ClassNode,
+ classes: ClassNodes,
+): Boolean {
if (currentClass.superName == null || currentClass.superName == "java/lang/Object") {
return true // No parent class
}
+ // Make sure the class doesn't extend a junit3 TestCase class.
if (currentClass.superName.isLegacyTestBaseClass()) {
log.e("Error: Class ${targetClass.name.toHumanReadableClassName()} extends"
- + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}.")
+ + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}"
+ + ", which is not supported on Ravenwood. Please migrate to Junit4 syntax.")
return false
}
classes.findClass(currentClass.superName)?.let {
- return checkSuperClass(targetClass, it, classes)
+ return checkSuperClassForJunit3(targetClass, it, classes)
}
// Super class not found.
// log.w("Class ${currentClass.superName} not found.")
@@ -73,9 +85,64 @@
return this.startsWithAny(
"junit/framework/TestCase",
- // In case the test doesn't statically include JUnit, we need
+ // In case the test doesn't statically include JUnit, we need the following.
"android/test/AndroidTestCase",
"android/test/InstrumentationTestCase",
"android/test/InstrumentationTestSuite",
)
}
+
+private val allowListedLegacyTests = setOf(
+// List of existing test classes that use the JUnit3 syntax. We exempt them for now, but
+// will reject any more of them.
+//
+// Note, we want internal class names, but for convenience, we use '.'s and '%'s here
+// and replace them later. (a '$' would be parsed as a string template.)
+ *"""
+android.util.proto.cts.DebuggingTest
+android.util.proto.cts.EncodedBufferTest
+android.util.proto.cts.ProtoOutputStreamBoolTest
+android.util.proto.cts.ProtoOutputStreamBytesTest
+android.util.proto.cts.ProtoOutputStreamDoubleTest
+android.util.proto.cts.ProtoOutputStreamEnumTest
+android.util.proto.cts.ProtoOutputStreamFixed32Test
+android.util.proto.cts.ProtoOutputStreamFixed64Test
+android.util.proto.cts.ProtoOutputStreamFloatTest
+android.util.proto.cts.ProtoOutputStreamInt32Test
+android.util.proto.cts.ProtoOutputStreamInt64Test
+android.util.proto.cts.ProtoOutputStreamObjectTest
+android.util.proto.cts.ProtoOutputStreamSFixed32Test
+android.util.proto.cts.ProtoOutputStreamSFixed64Test
+android.util.proto.cts.ProtoOutputStreamSInt32Test
+android.util.proto.cts.ProtoOutputStreamSInt64Test
+android.util.proto.cts.ProtoOutputStreamStringTest
+android.util.proto.cts.ProtoOutputStreamSwitchedWriteTest
+android.util.proto.cts.ProtoOutputStreamTagTest
+android.util.proto.cts.ProtoOutputStreamUInt32Test
+android.util.proto.cts.ProtoOutputStreamUInt64Test
+
+android.os.cts.BadParcelableExceptionTest
+android.os.cts.DeadObjectExceptionTest
+android.os.cts.ParcelFormatExceptionTest
+android.os.cts.PatternMatcherTest
+android.os.cts.RemoteExceptionTest
+
+android.os.storage.StorageManagerBaseTest
+android.os.storage.StorageManagerIntegrationTest
+android.util.LogTest%PerformanceTest
+
+com.android.server.power.stats.BatteryStatsCounterTest
+com.android.server.power.stats.BatteryStatsDualTimerTest
+com.android.server.power.stats.BatteryStatsDurationTimerTest
+com.android.server.power.stats.BatteryStatsSamplingTimerTest
+com.android.server.power.stats.BatteryStatsStopwatchTimerTest
+com.android.server.power.stats.BatteryStatsTimeBaseTest
+com.android.server.power.stats.BatteryStatsTimerTest
+
+ """.trim().replace('%', '$').replace('.', '/')
+ .split(Pattern.compile("""\s+""")).toTypedArray()
+)
+
+private fun isAllowListedLegacyTest(targetClass: ClassNode): Boolean {
+ return allowListedLegacyTests.contains(targetClass.name)
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 61079fc..c1d5597 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -49,6 +49,7 @@
# Broadcasts
per-file Broadcast* = file:/BROADCASTS_OWNERS
+per-file broadcasts_flags.aconfig = file:/BROADCASTS_OWNERS
# Permissions & Packages
per-file *Permission* = patb@google.com
diff --git a/services/core/java/com/android/server/am/broadcasts_flags.aconfig b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
new file mode 100644
index 0000000..b1185d5
--- /dev/null
+++ b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.server.am"
+container: "system"
+
+flag {
+ name: "restrict_priority_values"
+ namespace: "backstage_power"
+ description: "Restrict priority values defined by non-system apps"
+ is_fixed_read_only: true
+ bug: "369487976"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 8834820..56cfdfb 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -235,14 +235,6 @@
}
flag {
- name: "restrict_priority_values"
- namespace: "backstage_power"
- description: "Restrict priority values defined by non-system apps"
- is_fixed_read_only: true
- bug: "369487976"
-}
-
-flag {
name: "unfreeze_bind_policy_fix"
namespace: "backstage_power"
description: "Make sure shouldNotFreeze state change correctly triggers updates."
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index a9ada29..e8fa417 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -146,7 +146,7 @@
final List<Pair<BiometricSensor, Integer>> ineligibleSensors = new ArrayList<>();
final int effectiveUserId;
- if (Flags.privateSpaceBp()) {
+ if (Flags.effectiveUserBp()) {
effectiveUserId = userManager.getCredentialOwnerProfile(userId);
} else {
effectiveUserId = userId;
diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java
index dba6874..0b46e0f 100644
--- a/services/core/java/com/android/server/display/state/DisplayStateController.java
+++ b/services/core/java/com/android/server/display/state/DisplayStateController.java
@@ -61,7 +61,7 @@
// We might override this below based on other factors.
// Initialise brightness as invalid.
int state;
- int reason = Display.STATE_REASON_DEFAULT_POLICY;
+ int reason = displayPowerRequest.policyReason;
switch (displayPowerRequest.policy) {
case DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF:
state = Display.STATE_OFF;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 45885f0..c314ab0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -347,6 +347,8 @@
private final StorageManagerInternal mStorageManagerInternal;
+ private final Object mGcWorkToken = new Object();
+
// This class manages life cycle events for encrypted users on File Based Encryption (FBE)
// devices. The most basic of these is to show/hide notifications about missing features until
// the user unlocks the account and credential-encrypted storage is available.
@@ -3632,11 +3634,19 @@
* release references to the argument.
*/
private void scheduleGc() {
+ // Cancel any existing GC request first, so that GC requests don't pile up if lockscreen
+ // credential operations are happening very quickly, e.g. as sometimes happens during tests.
+ //
+ // This delays the already-requested GC, but that is fine in practice where lockscreen
+ // operations don't happen very quickly. And the precise time that the sanitization happens
+ // isn't very important; doing it within a minute can be fine, for example.
+ mHandler.removeCallbacksAndMessages(mGcWorkToken);
+
mHandler.postDelayed(() -> {
System.gc();
System.runFinalization();
System.gc();
- }, 2000);
+ }, mGcWorkToken, 2000);
}
private class DeviceProvisionedObserver extends ContentObserver {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 849f236..2c45fc8 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -9363,10 +9363,15 @@
// a group summary or children (complete a group)
mHandler.postDelayed(() -> {
synchronized (mNotificationLock) {
- mGroupHelper.onNotificationPostedWithDelay(
- r, mNotificationList, mSummaryByGroupKey);
+ NotificationRecord record =
+ mNotificationsByKey.get(key);
+ if (record != null) {
+ mGroupHelper.onNotificationPostedWithDelay(
+ record, mNotificationList,
+ mSummaryByGroupKey);
+ }
}
- }, r.getKey(), DELAY_FORCE_REGROUP_TIME);
+ }, key, DELAY_FORCE_REGROUP_TIME);
}
}
@@ -9412,10 +9417,15 @@
if (notificationForceGrouping()) {
mHandler.postDelayed(() -> {
synchronized (mNotificationLock) {
- mGroupHelper.onNotificationPostedWithDelay(r,
- mNotificationList, mSummaryByGroupKey);
+ NotificationRecord record =
+ mNotificationsByKey.get(key);
+ if (record != null) {
+ mGroupHelper.onNotificationPostedWithDelay(
+ record, mNotificationList,
+ mSummaryByGroupKey);
+ }
}
- }, r.getKey(), DELAY_FORCE_REGROUP_TIME);
+ }, key, DELAY_FORCE_REGROUP_TIME);
}
}
}
@@ -10395,7 +10405,7 @@
}
mListeners.notifyRemovedLocked(r, reason, r.getStats());
if (notificationForceGrouping()) {
- mHandler.removeCallbacksAndMessages(r.getKey());
+ mHandler.removeCallbacksAndEqualMessages(r.getKey());
mHandler.post(() -> {
synchronized (NotificationManagerService.this.mNotificationLock) {
mGroupHelper.onNotificationRemoved(r, mNotificationList);
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 2e8a0c6..a928814 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -42,6 +42,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
+import com.android.server.power.feature.PowerManagerFlags;
/**
* Used to store power related requests to every display in a
@@ -62,6 +63,8 @@
private final DisplayManagerInternal mDisplayManagerInternal;
private final boolean mSupportsSandman;
private final int mGroupId;
+ private final PowerManagerFlags mFeatureFlags;
+
/** True if DisplayManagerService has applied all the latest display states that were requested
* for this group. */
private boolean mReady;
@@ -82,10 +85,15 @@
private long mLastWakeTime;
/** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
private long mLastSleepTime;
+ /** The last reason that woke the power group. */
+ private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN;
+ /** The last reason that put the power group to sleep. */
+ private @PowerManager.GoToSleepReason int mLastSleepReason =
+ PowerManager.GO_TO_SLEEP_REASON_UNKNOWN;
PowerGroup(int groupId, PowerGroupListener wakefulnessListener, Notifier notifier,
DisplayManagerInternal displayManagerInternal, int wakefulness, boolean ready,
- boolean supportsSandman, long eventTime) {
+ boolean supportsSandman, long eventTime, PowerManagerFlags featureFlags) {
mGroupId = groupId;
mWakefulnessListener = wakefulnessListener;
mNotifier = notifier;
@@ -95,10 +103,12 @@
mSupportsSandman = supportsSandman;
mLastWakeTime = eventTime;
mLastSleepTime = eventTime;
+ mFeatureFlags = featureFlags;
}
PowerGroup(int wakefulness, PowerGroupListener wakefulnessListener, Notifier notifier,
- DisplayManagerInternal displayManagerInternal, long eventTime) {
+ DisplayManagerInternal displayManagerInternal, long eventTime,
+ PowerManagerFlags featureFlags) {
mGroupId = Display.DEFAULT_DISPLAY_GROUP;
mWakefulnessListener = wakefulnessListener;
mNotifier = notifier;
@@ -108,6 +118,7 @@
mSupportsSandman = true;
mLastWakeTime = eventTime;
mLastSleepTime = eventTime;
+ mFeatureFlags = featureFlags;
}
long getLastWakeTimeLocked() {
@@ -138,8 +149,14 @@
setLastPowerOnTimeLocked(eventTime);
setIsPoweringOnLocked(true);
mLastWakeTime = eventTime;
+ if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+ mLastWakeReason = reason;
+ }
} else if (isInteractive(mWakefulness) && !isInteractive(newWakefulness)) {
mLastSleepTime = eventTime;
+ if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+ mLastSleepReason = reason;
+ }
}
mWakefulness = newWakefulness;
mWakefulnessListener.onWakefulnessChangedLocked(mGroupId, mWakefulness, eventTime,
@@ -393,37 +410,51 @@
return false;
}
- @VisibleForTesting
- int getDesiredScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
+ // TODO: create and use more specific policy reasons, beyond the ones that correlate to
+ // interactivity state
+ private void updateScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
boolean bootCompleted, boolean screenBrightnessBoostInProgress,
boolean brightWhenDozing) {
final int wakefulness = getWakefulnessLocked();
final int wakeLockSummary = getWakeLockSummaryLocked();
- if (wakefulness == WAKEFULNESS_ASLEEP || quiescent) {
- return DisplayPowerRequest.POLICY_OFF;
+ int policyReason = Display.STATE_REASON_DEFAULT_POLICY;
+ int policy = Integer.MAX_VALUE; // do not set to real policy to start with.
+ if (quiescent) {
+ policy = DisplayPowerRequest.POLICY_OFF;
+ } else if (wakefulness == WAKEFULNESS_ASLEEP) {
+ policy = DisplayPowerRequest.POLICY_OFF;
+ policyReason = sleepReasonToDisplayStateReason(mLastSleepReason);
} else if (wakefulness == WAKEFULNESS_DOZING) {
if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
- return DisplayPowerRequest.POLICY_DOZE;
- }
- if (dozeAfterScreenOff) {
- return DisplayPowerRequest.POLICY_OFF;
- }
- if (brightWhenDozing) {
- return DisplayPowerRequest.POLICY_BRIGHT;
+ policy = DisplayPowerRequest.POLICY_DOZE;
+ } else if (dozeAfterScreenOff) {
+ policy = DisplayPowerRequest.POLICY_OFF;
+ } else if (brightWhenDozing) {
+ policy = DisplayPowerRequest.POLICY_BRIGHT;
}
// Fall through and preserve the current screen policy if not configured to
// bright when dozing or doze after screen off. This causes the screen off transition
// to be skipped.
}
- if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
- || !bootCompleted
- || (getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
- || screenBrightnessBoostInProgress) {
- return DisplayPowerRequest.POLICY_BRIGHT;
+ if (policy == Integer.MAX_VALUE) { // policy is not set yet.
+ if (isInteractive(wakefulness)) {
+ policyReason = wakeReasonToDisplayStateReason(mLastWakeReason);
+ }
+ if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
+ || !bootCompleted
+ || (getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
+ || screenBrightnessBoostInProgress) {
+ policy = DisplayPowerRequest.POLICY_BRIGHT;
+ } else {
+ policy = DisplayPowerRequest.POLICY_DIM;
+ }
}
- return DisplayPowerRequest.POLICY_DIM;
+ if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+ mDisplayPowerRequest.policyReason = policyReason;
+ }
+ mDisplayPowerRequest.policy = policy;
}
int getPolicyLocked() {
@@ -439,7 +470,7 @@
boolean dozeAfterScreenOff, boolean bootCompleted,
boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity,
boolean brightWhenDozing) {
- mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
+ updateScreenPolicyLocked(quiescent, dozeAfterScreenOff,
bootCompleted, screenBrightnessBoostInProgress, brightWhenDozing);
mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
mDisplayPowerRequest.screenBrightnessOverrideTag = overrideTag;
@@ -478,6 +509,33 @@
return ready;
}
+ /** Determines the respective display state reason for a given PowerManager WakeReason. */
+ private static int wakeReasonToDisplayStateReason(@PowerManager.WakeReason int wakeReason) {
+ switch (wakeReason) {
+ case PowerManager.WAKE_REASON_POWER_BUTTON:
+ case PowerManager.WAKE_REASON_WAKE_KEY:
+ return Display.STATE_REASON_KEY;
+ case PowerManager.WAKE_REASON_WAKE_MOTION:
+ return Display.STATE_REASON_MOTION;
+ case PowerManager.WAKE_REASON_TILT:
+ return Display.STATE_REASON_TILT;
+ default:
+ return Display.STATE_REASON_DEFAULT_POLICY;
+ }
+ }
+
+ /** Determines the respective display state reason for a given PowerManager GoToSleepReason. */
+ private static int sleepReasonToDisplayStateReason(
+ @PowerManager.GoToSleepReason int sleepReason) {
+ switch (sleepReason) {
+ case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON:
+ case PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON:
+ return Display.STATE_REASON_KEY;
+ default:
+ return Display.STATE_REASON_DEFAULT_POLICY;
+ }
+ }
+
protected interface PowerGroupListener {
/**
* Informs the recipient about a wakefulness change of a {@link PowerGroup}.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 65f2241..3a5afac 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -790,7 +790,8 @@
WAKEFULNESS_AWAKE,
/* ready= */ false,
supportsSandman,
- mClock.uptimeMillis());
+ mClock.uptimeMillis(),
+ mFeatureFlags);
mPowerGroups.append(groupId, powerGroup);
onPowerGroupEventLocked(DISPLAY_GROUP_ADDED, powerGroup);
}
@@ -1375,7 +1376,8 @@
mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP,
new PowerGroup(WAKEFULNESS_AWAKE, mPowerGroupWakefulnessChangeListener,
- mNotifier, mDisplayManagerInternal, mClock.uptimeMillis()));
+ mNotifier, mDisplayManagerInternal, mClock.uptimeMillis(),
+ mFeatureFlags));
DisplayGroupPowerChangeListener displayGroupPowerChangeListener =
new DisplayGroupPowerChangeListener();
mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
@@ -3738,14 +3740,6 @@
}
@VisibleForTesting
- @GuardedBy("mLock")
- int getDesiredScreenPolicyLocked(int groupId) {
- return mPowerGroups.get(groupId).getDesiredScreenPolicyLocked(sQuiescent,
- mDozeAfterScreenOff, mBootCompleted,
- mScreenBrightnessBoostInProgress, mBrightWhenDozingConfig);
- }
-
- @VisibleForTesting
int getDreamsBatteryLevelDrain() {
return mDreamsBatteryLevelDrain;
}
@@ -4588,7 +4582,8 @@
WAKEFULNESS_AWAKE,
/* ready= */ false,
/* supportsSandman= */ false,
- mClock.uptimeMillis());
+ mClock.uptimeMillis(),
+ mFeatureFlags);
mPowerGroups.append(displayGroupId, powerGroup);
}
mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
index db57d11..4ddf0c0 100644
--- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -50,6 +50,11 @@
private final FlagState mFrameworkWakelockInfo =
new FlagState(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO, Flags::frameworkWakelockInfo);
+ private final FlagState mPolicyReasonInDisplayPowerRequest = new FlagState(
+ Flags.FLAG_POLICY_REASON_IN_DISPLAY_POWER_REQUEST,
+ Flags::policyReasonInDisplayPowerRequest
+ );
+
/** Returns whether early-screen-timeout-detector is enabled on not. */
public boolean isEarlyScreenTimeoutDetectorEnabled() {
return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
@@ -77,6 +82,13 @@
}
/**
+ * @return Whether the wakefulness reason is populated in DisplayPowerRequest.
+ */
+ public boolean isPolicyReasonInDisplayPowerRequestEnabled() {
+ return mPolicyReasonInDisplayPowerRequest.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index 8bb69ba..e27f8bb 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -33,3 +33,11 @@
description: "Feature flag to enable statsd pulling of FrameworkWakelockInfo atoms"
bug: "352602149"
}
+
+flag {
+ name: "policy_reason_in_display_power_request"
+ namespace: "wear_frameworks"
+ description: "Whether the policy reason is populted in DisplayPowerRequest."
+ bug: "364349703"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 457196b..465ac2f 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -21,6 +21,7 @@
import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
import android.Manifest;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -1896,8 +1897,11 @@
}
}
+ @EnforcePermission(Manifest.permission.ACCESS_FINE_LOCATION)
@Override
public boolean isInSignificantPlace() {
+ super.isInSignificantPlace_enforcePermission();
+
if (android.security.Flags.significantPlaces()) {
mSignificantPlaceServiceWatcher.runOnBinder(
binder -> ISignificantPlaceProvider.Stub.asInterface(binder)
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index a492a72..6ce8685 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.net.IpSecTransformState;
import android.net.vcn.FeatureFlags;
import android.net.vcn.FeatureFlagsImpl;
import android.os.Looper;
@@ -70,19 +69,6 @@
return mIsInTestMode;
}
- public boolean isFlagIpSecTransformStateEnabled() {
- // TODO: b/328844044: Ideally this code should gate the behavior by checking the
- // android.net.platform.flags.ipsec_transform_state flag but that flag is not accessible
- // right now. We should either update the code when the flag is accessible or remove the
- // legacy behavior after VIC SDK finalization
- try {
- new IpSecTransformState.Builder();
- return true;
- } catch (Exception e) {
- return false;
- }
- }
-
@NonNull
public FeatureFlags getFeatureFlags() {
return mFeatureFlags;
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 189b608..2d3bc84 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1912,8 +1912,7 @@
// Transforms do not need to be persisted; the IkeSession will keep them alive
mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
- if (direction == IpSecManager.DIRECTION_IN
- && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+ if (direction == IpSecManager.DIRECTION_IN) {
mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
}
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 6f1e15b..16ab51e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -148,12 +148,6 @@
Objects.requireNonNull(deps, "Missing deps");
- if (!vcnContext.isFlagIpSecTransformStateEnabled()) {
- // Caller error
- logWtf("ipsecTransformState flag disabled");
- throw new IllegalAccessException("ipsecTransformState flag disabled");
- }
-
mHandler = new Handler(getVcnContext().getLooper());
mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class);
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 0b9b677..3eeeece 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -204,10 +204,8 @@
List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
mCellBringupCallbacks.clear();
- if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
- for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
- evaluator.close();
- }
+ for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+ evaluator.close();
}
mUnderlyingNetworkRecords.clear();
@@ -429,10 +427,7 @@
if (oldSnapshot
.getAllSubIdsInGroup(mSubscriptionGroup)
.equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
-
- if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
- reevaluateNetworks();
- }
+ reevaluateNetworks();
return;
}
registerOrUpdateNetworkRequests();
@@ -445,11 +440,6 @@
*/
public void updateInboundTransform(
@NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
- if (!mVcnContext.isFlagIpSecTransformStateEnabled()) {
- logWtf("#updateInboundTransform: unexpected call; flags missing");
- return;
- }
-
Objects.requireNonNull(currentNetwork, "currentNetwork is null");
Objects.requireNonNull(transform, "transform is null");
@@ -572,10 +562,7 @@
@Override
public void onLost(@NonNull Network network) {
- if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
- mUnderlyingNetworkRecords.get(network).close();
- }
-
+ mUnderlyingNetworkRecords.get(network).close();
mUnderlyingNetworkRecords.remove(network);
reevaluateNetworks();
@@ -648,11 +635,6 @@
class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
@Override
public void onEvaluationResultChanged() {
- if (!mVcnContext.isFlagIpSecTransformStateEnabled()) {
- logWtf("#onEvaluationResultChanged: unexpected call; flags missing");
- return;
- }
-
mVcnContext.ensureRunningOnLooperThread();
reevaluateNetworks();
}
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index 448a7eb..08be11e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -102,17 +102,15 @@
updatePriorityClass(
underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
- if (isIpSecPacketLossDetectorEnabled()) {
- try {
- mMetricMonitors.add(
- mDependencies.newIpSecPacketLossDetector(
- mVcnContext,
- mNetworkRecordBuilder.getNetwork(),
- carrierConfig,
- new MetricMonitorCallbackImpl()));
- } catch (IllegalAccessException e) {
- // No action. Do not add anything to mMetricMonitors
- }
+ try {
+ mMetricMonitors.add(
+ mDependencies.newIpSecPacketLossDetector(
+ mVcnContext,
+ mNetworkRecordBuilder.getNetwork(),
+ carrierConfig,
+ new MetricMonitorCallbackImpl()));
+ } catch (IllegalAccessException e) {
+ // No action. Do not add anything to mMetricMonitors
}
}
@@ -188,22 +186,12 @@
}
}
- private boolean isIpSecPacketLossDetectorEnabled() {
- return isIpSecPacketLossDetectorEnabled(mVcnContext);
- }
-
- private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) {
- return vcnContext.isFlagIpSecTransformStateEnabled();
- }
-
/** Get the comparator for UnderlyingNetworkEvaluator */
public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) {
return (left, right) -> {
- if (isIpSecPacketLossDetectorEnabled(vcnContext)) {
- if (left.mIsPenalized != right.mIsPenalized) {
- // A penalized network should have lower priority which means a larger index
- return left.mIsPenalized ? 1 : -1;
- }
+ if (left.mIsPenalized != right.mIsPenalized) {
+ // A penalized network should have lower priority which means a larger index
+ return left.mIsPenalized ? 1 : -1;
}
final int leftIndex = left.mPriorityClass;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8aa0530..8986750 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3917,8 +3917,10 @@
sb.append(affinityIntent.getComponent().flattenToShortString());
}
sb.append(" isResizeable=").append(isResizeable());
- sb.append(" minWidth=").append(mMinWidth);
- sb.append(" minHeight=").append(mMinHeight);
+ if (mMinWidth != INVALID_MIN_SIZE || mMinHeight != INVALID_MIN_SIZE) {
+ sb.append(" minWidth=").append(mMinWidth);
+ sb.append(" minHeight=").append(mMinHeight);
+ }
sb.append('}');
return stringName = sb.toString();
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index fc46600..454e431 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -237,15 +237,16 @@
private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>();
/**
- * Set of transient activities (lifecycle initially tied to this transition) and their
+ * Map of transient activities (lifecycle initially tied to this transition) to their
* restore-below tasks.
*/
private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;
/**
* The tasks that may be occluded by the transient activity. Assume the task stack is
- * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below
- * task, and [B, C] are the transient-hide tasks.
+ * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), and Home is started in a
+ * transient-launch activity, then A is the restore-below task, and [B, C] are the
+ * transient-hide tasks.
*/
private ArrayList<Task> mTransientHideTasks;
@@ -401,18 +402,26 @@
mTransientLaunches.put(activity, restoreBelow);
setTransientLaunchToChanges(activity);
- final Task transientRootTask = activity.getRootTask();
+ final int restoreBelowTaskId = restoreBelow != null ? restoreBelow.mTaskId : -1;
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
+ + "transient-launch restoreBelowTaskId=%d", mSyncId, activity, restoreBelowTaskId);
+
+ final Task transientLaunchRootTask = activity.getRootTask();
final WindowContainer<?> parent = restoreBelow != null ? restoreBelow.getParent()
- : (transientRootTask != null ? transientRootTask.getParent() : null);
+ : (transientLaunchRootTask != null ? transientLaunchRootTask.getParent() : null);
if (parent != null) {
// Collect all visible tasks which can be occluded by the transient activity to
// make sure they are in the participants so their visibilities can be updated when
// finishing transition.
+ // Note: This currently assumes that the parent is a DA containing the full set of
+ // visible tasks
parent.forAllTasks(t -> {
// Skip transient-launch task
- if (t == transientRootTask) return false;
+ if (t == transientLaunchRootTask) return false;
if (t.isVisibleRequested() && !t.isAlwaysOnTop()) {
if (t.isRootTask()) {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ " transient hide: taskId=%d", t.mTaskId);
mTransientHideTasks.add(t);
}
if (t.isLeafTask()) {
@@ -442,9 +451,6 @@
// the gesture threshold.
activity.getTask().setCanAffectSystemUiFlags(false);
}
-
- ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
- + "transient-launch", mSyncId, activity);
}
/** @return whether `wc` is a descendent of a transient-hide window. */
@@ -462,6 +468,8 @@
/** Returns {@code true} if the task should keep visible if this is a transient transition. */
boolean isTransientVisible(@NonNull Task task) {
if (mTransientLaunches == null) return false;
+
+ // Check if all the transient-launch activities are occluded
int occludedCount = 0;
final int numTransient = mTransientLaunches.size();
for (int i = numTransient - 1; i >= 0; --i) {
@@ -486,6 +494,8 @@
// Let transient-hide activities pause before transition is finished.
return false;
}
+
+ // If this task is currently transient-hide, then keep it visible
return isInTransientHide(task);
}
@@ -608,7 +618,8 @@
}
/**
- * Only set flag to the parent tasks and activity itself.
+ * Sets the FLAG_TRANSIENT_LAUNCH flag to all changes associated with the given activity
+ * container and parent tasks.
*/
private void setTransientLaunchToChanges(@NonNull WindowContainer wc) {
for (WindowContainer curr = wc; curr != null && mChanges.containsKey(curr);
@@ -774,7 +785,7 @@
// Only look at tasks, taskfragments, or activities
if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) return;
if (!isInTransientHide(wc)) return;
- info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+ info.mFlags |= ChangeInfo.FLAG_TRANSIENT_HIDE;
}
private void recordDisplay(DisplayContent dc) {
@@ -2448,6 +2459,13 @@
sb.append(" type=" + transitTypeToString(mType));
sb.append(" flags=0x" + Integer.toHexString(mFlags));
sb.append(" overrideAnimOptions=" + mOverrideOptions);
+ if (!mChanges.isEmpty()) {
+ sb.append(" c=[");
+ for (int i = 0; i < mChanges.size(); i++) {
+ sb.append("\n").append(" ").append(mChanges.valueAt(i).toString());
+ }
+ sb.append("\n]\n");
+ }
sb.append('}');
return sb.toString();
}
@@ -3396,8 +3414,14 @@
* seamless rotation. This is currently only used by DisplayContent during fixed-rotation.
*/
private static final int FLAG_SEAMLESS_ROTATION = 1;
+ /**
+ * Identifies the associated WindowContainer as a transient-launch task or activity.
+ */
private static final int FLAG_TRANSIENT_LAUNCH = 2;
- private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4;
+ /**
+ * Identifies the associated WindowContainer as a transient-hide task or activity.
+ */
+ private static final int FLAG_TRANSIENT_HIDE = 4;
/** This container explicitly requested no-animation (usually Activity level). */
private static final int FLAG_CHANGE_NO_ANIMATION = 0x8;
@@ -3429,7 +3453,7 @@
FLAG_NONE,
FLAG_SEAMLESS_ROTATION,
FLAG_TRANSIENT_LAUNCH,
- FLAG_ABOVE_TRANSIENT_LAUNCH,
+ FLAG_TRANSIENT_HIDE,
FLAG_CHANGE_NO_ANIMATION,
FLAG_CHANGE_YES_ANIMATION,
FLAG_CHANGE_MOVED_TO_TOP,
@@ -3438,7 +3462,7 @@
FLAG_BELOW_BACK_GESTURE_ANIMATION
})
@Retention(RetentionPolicy.SOURCE)
- @interface Flag {}
+ @interface ChangeInfoFlag {}
@NonNull final WindowContainer mContainer;
/**
@@ -3467,7 +3491,7 @@
@ActivityInfo.Config int mKnownConfigChanges;
/** Extra information about this change. */
- @Flag int mFlags = FLAG_NONE;
+ @ChangeInfoFlag int mFlags = FLAG_NONE;
/** Snapshot surface and luma, if relevant. */
SurfaceControl mSnapshot;
@@ -3502,14 +3526,20 @@
@Override
public String toString() {
- return mContainer.toString();
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("ChangeInfo{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" container=").append(mContainer);
+ sb.append(" flags=0x").append(Integer.toHexString(mFlags));
+ sb.append('}');
+ return sb.toString();
}
boolean hasChanged() {
final boolean currVisible = mContainer.isVisibleRequested();
// the task including transient launch must promote to root task
if (currVisible && ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
- || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0)
+ || (mFlags & ChangeInfo.FLAG_TRANSIENT_HIDE) != 0)
|| (mFlags & ChangeInfo.FLAG_BACK_GESTURE_ANIMATION) != 0
|| (mFlags & ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION) != 0) {
return true;
@@ -3530,7 +3560,7 @@
@TransitionInfo.TransitionMode
int getTransitMode(@NonNull WindowContainer wc) {
- if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
+ if ((mFlags & ChangeInfo.FLAG_TRANSIENT_HIDE) != 0) {
return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
}
if ((mFlags & ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION) != 0) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 5525842..e0c473d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2121,7 +2121,8 @@
}
/**
- * For all tasks at or below this container call the callback.
+ * Calls the given {@param callback} for all tasks in depth-first top-down z-order at or below
+ * this container.
*
* @param callback Calls the {@link ToBooleanFunction#apply} method for each task found and
* stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
diff --git a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
index de53266..fc4cc25 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
@@ -63,11 +63,12 @@
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+ displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
Pair<Integer, Integer> stateAndReason =
mDisplayStateController.updateDisplayState(
displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
assertTrue(Display.STATE_OFF == stateAndReason.first);
- assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+ assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
Display.STATE_OFF);
assertEquals(true, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -105,11 +106,12 @@
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
Pair<Integer, Integer> stateAndReason =
mDisplayStateController.updateDisplayState(
displayPowerRequest, !DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
assertTrue(Display.STATE_OFF == stateAndReason.first);
- assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+ assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
Display.STATE_ON);
assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -123,11 +125,12 @@
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.policyReason = Display.STATE_REASON_MOTION;
Pair<Integer, Integer> stateAndReason =
mDisplayStateController.updateDisplayState(
displayPowerRequest, DISPLAY_ENABLED, DISPLAY_IN_TRANSITION);
assertTrue(Display.STATE_OFF == stateAndReason.first);
- assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+ assertTrue(Display.STATE_REASON_MOTION == stateAndReason.second);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
Display.STATE_ON);
assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -141,6 +144,7 @@
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.policyReason = Display.STATE_REASON_DEFAULT_POLICY;
Pair<Integer, Integer> stateAndReason =
mDisplayStateController.updateDisplayState(
displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
@@ -156,6 +160,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
new DisplayManagerInternal.DisplayPowerRequest();
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ displayPowerRequest.policyReason = Display.STATE_REASON_DRAW_WAKE_LOCK;
mDisplayStateController.overrideDozeScreenState(
Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_OFFLOAD);
@@ -172,8 +177,9 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
new DisplayManagerInternal.DisplayPowerRequest();
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+ displayPowerRequest.policyReason = Display.STATE_REASON_DEFAULT_POLICY;
mDisplayStateController.overrideDozeScreenState(
- Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DEFAULT_POLICY);
+ Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DRAW_WAKE_LOCK);
Pair<Integer, Integer> stateAndReason =
mDisplayStateController.updateDisplayState(
@@ -183,6 +189,53 @@
assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
}
+ @Test
+ public void policyOff_usespolicyReasonFromRequest() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+ displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
+
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+ assertTrue(Display.STATE_OFF == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
+ }
+
+ @Test
+ public void policyBright_usespolicyReasonFromRequest() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ displayPowerRequest.policyReason = Display.STATE_REASON_DREAM_MANAGER;
+
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+ assertTrue(Display.STATE_ON == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_DREAM_MANAGER == stateAndReason.second);
+ }
+
+ @Test
+ public void policyRequestHasDozeScreenState_usesPolicyDozeScreenStateReason() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+ new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ displayPowerRequest.policyReason = Display.STATE_REASON_MOTION;
+ displayPowerRequest.dozeScreenState = Display.STATE_ON;
+ displayPowerRequest.dozeScreenStateReason = Display.STATE_REASON_OFFLOAD;
+
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+ assertTrue(Display.STATE_ON == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_OFFLOAD == stateAndReason.second);
+ }
+
private void validDisplayState(int policy, int displayState, boolean isEnabled,
boolean isInTransition) {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
index 4fac647..809b7bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
@@ -2,3 +2,4 @@
per-file ApplicationStartInfoTest.java = yforta@google.com, carmenjackson@google.com, jji@google.com
per-file CachedAppOptimizerTest.java = file:/PERFORMANCE_OWNERS
+per-file BaseBroadcastQueueTest.java = file:/BROADCASTS_OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
new file mode 100644
index 0000000..8c66fd0
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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.server.job;
+
+import static android.app.job.Flags.FLAG_HANDLE_ABANDONED_JOBS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.AppGlobals;
+import android.app.job.JobParameters;
+import android.content.Context;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.server.job.JobServiceContext.JobCallback;
+import com.android.server.job.controllers.JobStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+
+public class JobServiceContextTest {
+ private static final String TAG = JobServiceContextTest.class.getSimpleName();
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule();
+ @Mock
+ private JobSchedulerService mMockJobSchedulerService;
+ @Mock
+ private JobConcurrencyManager mMockConcurrencyManager;
+ @Mock
+ private JobNotificationCoordinator mMockNotificationCoordinator;
+ @Mock
+ private IBatteryStats.Stub mMockBatteryStats;
+ @Mock
+ private JobPackageTracker mMockJobPackageTracker;
+ @Mock
+ private Looper mMockLooper;
+ @Mock
+ private Context mMockContext;
+ @Mock
+ private JobStatus mMockJobStatus;
+ @Mock
+ private JobParameters mMockJobParameters;
+ @Mock
+ private JobCallback mMockJobCallback;
+ private MockitoSession mMockingSession;
+ private JobServiceContext mJobServiceContext;
+ private Object mLock;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockingSession =
+ mockitoSession()
+ .initMocks(this)
+ .mockStatic(AppGlobals.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+ doReturn(mock(PowerManager.class)).when(mMockContext).getSystemService(PowerManager.class);
+ doReturn(mMockContext).when(mMockJobSchedulerService).getContext();
+ mLock = new Object();
+ doReturn(mLock).when(mMockJobSchedulerService).getLock();
+ mJobServiceContext =
+ new JobServiceContext(
+ mMockJobSchedulerService,
+ mMockConcurrencyManager,
+ mMockNotificationCoordinator,
+ mMockBatteryStats,
+ mMockJobPackageTracker,
+ mMockLooper);
+ spyOn(mJobServiceContext);
+ mJobServiceContext.setJobParamsLockedForTest(mMockJobParameters);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private Clock getAdvancedClock(Clock clock, long incrementMs) {
+ return Clock.offset(clock, Duration.ofMillis(incrementMs));
+ }
+
+ private void advanceElapsedClock(long incrementMs) {
+ JobSchedulerService.sElapsedRealtimeClock =
+ getAdvancedClock(JobSchedulerService.sElapsedRealtimeClock, incrementMs);
+ }
+
+ /**
+ * Test that Abandoned jobs that are timed out are stopped with the correct stop reason
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_TimeoutAbandonedJob() {
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+ mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(true).when(mMockJobStatus).isAbandoned();
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+ mJobServiceContext.handleOpTimeoutLocked();
+
+ String stopMessage = captor.getValue();
+ assertEquals("timeout while executing and maybe abandoned", stopMessage);
+ verify(mMockJobParameters)
+ .setStopReason(
+ JobParameters.STOP_REASON_TIMEOUT_ABANDONED,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED,
+ "client timed out and maybe abandoned");
+ }
+
+ /**
+ * Test that non-abandoned jobs that are timed out are stopped with the correct stop reason
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_TimeoutNoAbandonedJob() {
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+ mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(false).when(mMockJobStatus).isAbandoned();
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+ mJobServiceContext.handleOpTimeoutLocked();
+
+ String stopMessage = captor.getValue();
+ assertEquals("timeout while executing", stopMessage);
+ verify(mMockJobParameters)
+ .setStopReason(
+ JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT,
+ "client timed out");
+ }
+
+ /**
+ * Test that abandoned jobs that are timed out while the flag is disabled
+ * are stopped with the correct stop reason
+ */
+ @Test
+ @DisableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_TimeoutAbandonedJob_flagHandleAbandonedJobsDisabled() {
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+ mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(true).when(mMockJobStatus).isAbandoned();
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+ mJobServiceContext.handleOpTimeoutLocked();
+
+ String stopMessage = captor.getValue();
+ assertEquals("timeout while executing", stopMessage);
+ verify(mMockJobParameters)
+ .setStopReason(
+ JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT,
+ "client timed out");
+ }
+
+ /**
+ * Test that the JobStatus is marked as abandoned when the JobServiceContext
+ * receives a MSG_HANDLE_ABANDONED_JOB message
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_HandleAbandonedJob() {
+ final int jobId = 123;
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+ doReturn(jobId).when(mMockJobStatus).getJobId();
+
+ mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+ verify(mMockJobStatus).setAbandoned(true);
+ }
+
+ /**
+ * Test that the JobStatus is not marked as abandoned when the
+ * JobServiceContext receives a MSG_HANDLE_ABANDONED_JOB message and the
+ * JobServiceContext is not running a job
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_HandleAbandonedJob_notRunningJob() {
+ final int jobId = 123;
+ mJobServiceContext.setRunningJobLockedForTest(null);
+ mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+
+ mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+ verify(mMockJobStatus, never()).setAbandoned(true);
+ }
+
+ /**
+ * Test that the JobStatus is not marked as abandoned when the
+ * JobServiceContext receives a MSG_HANDLE_ABANDONED_JOB message and the
+ * JobServiceContext is running a job with a different jobId
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testJobServiceContext_HandleAbandonedJob_differentJobId() {
+ final int jobId = 123;
+ final int differentJobId = 456;
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+ doReturn(differentJobId).when(mMockJobStatus).getJobId();
+
+ mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+ verify(mMockJobStatus, never()).setAbandoned(true);
+ }
+
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index 473c8c5..648da65 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -27,10 +27,13 @@
import static android.os.PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
import static android.os.PowerManager.WAKE_REASON_GESTURE;
import static android.os.PowerManager.WAKE_REASON_PLUGGED_IN;
+import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+import static android.view.Display.STATE_REASON_DEFAULT_POLICY;
+import static android.view.Display.STATE_REASON_MOTION;
import static com.android.server.power.PowerManagerService.USER_ACTIVITY_SCREEN_BRIGHT;
import static com.android.server.power.PowerManagerService.WAKE_LOCK_DOZE;
@@ -44,6 +47,7 @@
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
@@ -53,6 +57,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.LatencyTracker;
+import com.android.server.power.feature.PowerManagerFlags;
import org.junit.Before;
import org.junit.Test;
@@ -79,18 +84,23 @@
private static final float BRIGHTNESS = 0.99f;
private static final float BRIGHTNESS_DOZE = 0.5f;
+ private static final LatencyTracker LATENCY_TRACKER = LatencyTracker.getInstance(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
private PowerGroup mPowerGroup;
@Mock private PowerGroup.PowerGroupListener mWakefulnessCallbackMock;
@Mock private Notifier mNotifier;
@Mock private DisplayManagerInternal mDisplayManagerInternal;
+ @Mock private PowerManagerFlags mFeatureFlags;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()).thenReturn(true);
mPowerGroup = new PowerGroup(GROUP_ID, mWakefulnessCallbackMock, mNotifier,
mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
- /* supportsSandman= */ true, TIMESTAMP_CREATE);
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
}
@Test
@@ -101,10 +111,8 @@
eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), /* details= */
isNull());
String details = "wake PowerGroup1";
- LatencyTracker latencyTracker = LatencyTracker.getInstance(
- InstrumentationRegistry.getInstrumentation().getContext());
mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_PLUGGED_IN, details, UID,
- /* opPackageName= */ null, /* opUid= */ 0, latencyTracker);
+ /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
eq(WAKEFULNESS_AWAKE), eq(TIMESTAMP2), eq(WAKE_REASON_PLUGGED_IN), eq(UID),
/* opUid= */ anyInt(), /* opPackageName= */ isNull(), eq(details));
@@ -247,6 +255,10 @@
@Test
public void testUpdateWhileAwake_UpdatesDisplayPowerRequest() {
+ mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+ mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+ /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
final boolean batterySaverEnabled = true;
float brightnessFactor = 0.7f;
PowerSaveState powerSaveState = new PowerSaveState.Builder()
@@ -274,6 +286,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_MOTION);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.screenBrightnessOverrideTag.toString()).isEqualTo(tag);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
@@ -288,6 +301,55 @@
}
@Test
+ public void testWakefulnessReasonInDisplayPowerRequestDisabled_wakefulnessReasonNotPopulated() {
+ when(mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()).thenReturn(false);
+ mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+ mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+ /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
+ mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ "my/tag",
+ /* useProximitySensor= */ false,
+ /* boostScreenBrightness= */ false,
+ /* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
+ /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+ /* useNormalBrightnessForDoze= */ false,
+ /* overrideDrawWakeLock= */ false,
+ new PowerSaveState.Builder().build(),
+ /* quiescent= */ false,
+ /* dozeAfterScreenOff= */ false,
+ /* bootCompleted= */ true,
+ /* screenBrightnessBoostInProgress= */ false,
+ /* waitForNegativeProximity= */ false,
+ /* brightWhenDozing= */ false);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+ mPowerGroup.mDisplayPowerRequest;
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
+
+ mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_PLUGGED_IN, "details", UID,
+ /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+ mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+ /* overrideTag= */ "my/tag",
+ /* useProximitySensor= */ false,
+ /* boostScreenBrightness= */ false,
+ /* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
+ /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+ /* useNormalBrightnessForDoze= */ false,
+ /* overrideDrawWakeLock= */ false,
+ new PowerSaveState.Builder().build(),
+ /* quiescent= */ false,
+ /* dozeAfterScreenOff= */ false,
+ /* bootCompleted= */ true,
+ /* screenBrightnessBoostInProgress= */ false,
+ /* waitForNegativeProximity= */ false,
+ /* brightWhenDozing= */ false);
+ displayPowerRequest = mPowerGroup.mDisplayPowerRequest;
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
+ }
+
+ @Test
public void testUpdateWhileDozing_UpdatesDisplayPowerRequest() {
final boolean useNormalBrightnessForDoze = false;
final boolean batterySaverEnabled = false;
@@ -319,6 +381,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -363,6 +426,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -405,6 +469,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -419,6 +484,10 @@
@Test
public void testUpdateQuiescent() {
+ mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+ mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+ /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
final boolean batterySaverEnabled = false;
float brightnessFactor = 0.3f;
PowerSaveState powerSaveState = new PowerSaveState.Builder()
@@ -446,6 +515,11 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+ // Note how the reason is STATE_REASON_DEFAULT_POLICY, instead of STATE_REASON_MOTION.
+ // This is because - although there was a wake up request from a motion, the quiescent state
+ // preceded and forced the policy to be OFF, so we ignore the reason associated with the
+ // wake up request.
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -487,6 +561,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -529,6 +604,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -569,6 +645,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -610,6 +687,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -650,6 +728,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+ assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index e9e21de..b10200d 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -70,7 +70,6 @@
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.hardware.power.Boost;
import android.hardware.power.Mode;
import android.os.BatteryManager;
@@ -1894,15 +1893,6 @@
}
@Test
- public void testBoot_DesiredScreenPolicyShouldBeBright() {
- createService();
- startSystem();
-
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_BRIGHT);
- }
-
- @Test
public void testQuiescentBoot_ShouldBeAsleep() {
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
createService();
@@ -1914,40 +1904,6 @@
}
@Test
- public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() {
- when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
- createService();
- startSystem();
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_OFF);
- }
-
- @Test
- public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() {
- when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
- createService();
- startSystem();
- forceAwake();
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
- DisplayPowerRequest.POLICY_BRIGHT);
- }
-
- @Test
- public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() {
- when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
- createService();
- startSystem();
-
- mService.getBinderServiceInstance().wakeUp(mClock.now(),
- PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
-
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
- assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
- DisplayPowerRequest.POLICY_BRIGHT);
- }
-
- @Test
public void testIsAmbientDisplayAvailable_available() {
createService();
when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index 510dd4d..cf628ad 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -315,7 +315,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_PRIVATE_SPACE_BP)
+ @RequiresFlagsEnabled(Flags.FLAG_EFFECTIVE_USER_BP)
public void testCredentialOwnerIdAsUserId() throws Exception {
when(mUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(OWNER_ID);
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 ac021e1..cbfdc5f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -2895,6 +2895,44 @@
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testRemoveScheduledForceGroup_onNotificationCanceled() throws Exception {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "tag", null,
+ false);
+ when(mGroupHelper.onNotificationPosted(any(), anyBoolean())).thenReturn(false);
+ mService.addEnqueuedNotification(r);
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
+ runnable.run();
+ waitForIdle();
+
+ // Post an update to the notification
+ NotificationRecord r_update =
+ generateNotificationRecord(mTestNotificationChannel, 0, "tag", null, false);
+ mService.addEnqueuedNotification(r_update);
+ runnable = mService.new PostNotificationRunnable(r_update.getKey(),
+ r_update.getSbn().getPackageName(), r_update.getUid(),
+ mPostNotificationTrackerFactory.newTracker(null));
+ runnable.run();
+ waitForIdle();
+
+ // Cancel the notification
+ mBinderService.cancelNotificationWithTag(r.getSbn().getPackageName(),
+ r.getSbn().getPackageName(), r.getSbn().getTag(),
+ r.getSbn().getId(), r.getSbn().getUserId());
+ waitForIdle();
+
+ mTestableLooper.moveTimeForward(DELAY_FORCE_REGROUP_TIME);
+ waitForIdle();
+
+ // Check that onNotificationPostedWithDelay was canceled
+ verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+ verify(mGroupHelper, never()).onNotificationPostedWithDelay(any(), any(), any());
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testEnqueueNotification_forceGrouped_clearsSummaryFlag() throws Exception {
final String originalGroupName = "originalGroup";
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index e045f10..4c7b25a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -223,7 +223,6 @@
doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
- doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
doReturn(mUnderlyingNetworkController)
.when(mDeps)
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index bc7ff47..441b780 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -20,7 +20,6 @@
import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -127,8 +126,6 @@
false /* isInTestMode */));
doNothing().when(mVcnContext).ensureRunningOnLooperThread();
- doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
-
setupSystemService(
mContext,
mConnectivityManager,
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index 6f31d8d..e540932 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -226,7 +226,6 @@
private void resetVcnContext(VcnContext vcnContext) {
reset(vcnContext);
doNothing().when(vcnContext).ensureRunningOnLooperThread();
- doReturn(true).when(vcnContext).isFlagIpSecTransformStateEnabled();
}
// Package private for use in NetworkPriorityClassifierTest
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index 590f719..e6d0a3d 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -58,6 +58,7 @@
"junit",
"objenesis",
"mockito",
+ "systemfeatures-gen-lib",
"truth",
],
}
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index 196b5e7..1abe77f 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -71,7 +71,7 @@
println("Usage: SystemFeaturesGenerator <outputClassName> [options]")
println(" Options:")
println(" --readonly=true|false Whether to encode features as build-time constants")
- println(" --feature=\$NAME:\$VER A feature+version pair, where \$VER can be:")
+ println(" --feature=\$NAME:\$VER A feature+version pair, where \$VER can be:")
println(" * blank/empty == undefined (variable API)")
println(" * valid int == enabled (constant API)")
println(" * UNAVAILABLE == disabled (constant API)")
@@ -89,6 +89,17 @@
/** Main entrypoint for build-time system feature codegen. */
@JvmStatic
fun main(args: Array<String>) {
+ generate(args, System.out)
+ }
+
+ /**
+ * Simple API entrypoint for build-time system feature codegen.
+ *
+ * Note: Typically this would be implemented in terms of a proper Builder-type input argument,
+ * but it's primarily used for testing as opposed to direct production usage.
+ */
+ @JvmStatic
+ fun generate(args: Array<String>, output: Appendable) {
if (args.size < 1) {
usage()
return
@@ -155,7 +166,7 @@
.addFileComment("This file is auto-generated. DO NOT MODIFY.\n")
.addFileComment("Args: ${args.joinToString(" \\\n ")}")
.build()
- .writeTo(System.out)
+ .writeTo(output)
}
/*
@@ -171,12 +182,27 @@
return when (featureArgs.getOrNull(1)) {
null, "" -> FeatureInfo(name, null, readonly = false)
"UNAVAILABLE" -> FeatureInfo(name, null, readonly = true)
- else -> FeatureInfo(name, featureArgs[1].toIntOrNull(), readonly = true)
+ else -> {
+ val featureVersion =
+ featureArgs[1].toIntOrNull()
+ ?: throw IllegalArgumentException(
+ "Invalid feature version input for $name: ${featureArgs[1]}"
+ )
+ FeatureInfo(name, featureArgs[1].toInt(), readonly = true)
+ }
}
}
private fun parseFeatureName(name: String): String =
- if (name.startsWith("FEATURE_")) name else "FEATURE_$name"
+ when {
+ name.startsWith("android") ->
+ throw IllegalArgumentException(
+ "Invalid feature name input: \"android\"-namespaced features must be " +
+ "provided as PackageManager.FEATURE_* suffixes, not raw feature strings."
+ )
+ name.startsWith("FEATURE_") -> name
+ else -> "FEATURE_$name"
+ }
/*
* Adds per-feature query methods to the class with the form:
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java
new file mode 100644
index 0000000..f8c585d
--- /dev/null
+++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.systemfeatures;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.IOException;
+
+// Note: This is a very simple argument test to validate certain behaviors for
+// invalid arguments. Correctness and validity is largely exercised by
+// SystemFeaturesGeneratorTest.
+@RunWith(JUnit4.class)
+public class SystemFeaturesGeneratorApiTest {
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock private Appendable mOut;
+
+ @Test
+ public void testEmpty() throws IOException {
+ final String[] args = new String[] {};
+ // This should just print the commandline and return.
+ SystemFeaturesGenerator.generate(args, mOut);
+ verify(mOut, never()).append(any());
+ }
+
+ @Test
+ public void testBasic() throws IOException {
+ final String[] args = new String[] {
+ "com.foo.Features",
+ "--feature=TELEVISION:0",
+ };
+ SystemFeaturesGenerator.generate(args, mOut);
+ verify(mOut, atLeastOnce()).append(any());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidFeatureVersion() throws IOException {
+ final String[] args = new String[] {
+ "com.foo.Features",
+ "--feature=TELEVISION:blarg",
+ };
+ SystemFeaturesGenerator.generate(args, mOut);
+ verify(mOut, never()).append(any());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidFeatureNameFromAndroidNamespace() throws IOException {
+ final String[] args = new String[] {
+ "com.foo.Features",
+ "--feature=android.hardware.doesntexist:0",
+ };
+ SystemFeaturesGenerator.generate(args, mOut);
+ verify(mOut, never()).append(any());
+ }
+}