summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/AttributionSource.java14
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java152
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt3
-rw-r--r--services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java19
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java1
-rw-r--r--services/core/java/com/android/server/DropBoxManagerService.java65
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java124
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java86
-rw-r--r--services/core/java/com/android/server/appop/AttributedOp.java94
-rw-r--r--services/core/java/com/android/server/hdmi/SendKeyAction.java6
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java149
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java4
24 files changed, 806 insertions, 240 deletions
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 7f01a8256cd0..37f419d717c2 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -462,6 +462,20 @@ public final class AttributionSource implements Parcelable {
}
/**
+ * @return The next package's device Id from its context.
+ * This device ID is used for permissions checking during attribution source validation.
+ *
+ * @hide
+ */
+ public int getNextDeviceId() {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
+ return mAttributionSourceState.next[0].deviceId;
+ }
+ return Context.DEVICE_ID_DEFAULT;
+ }
+
+ /**
* Checks whether this attribution source can be trusted. That is whether
* the app it refers to created it and provided to the attribution chain.
*
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a1daebd7513e..5857692cdaa9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -285,6 +285,9 @@
the amount by the view is positioned above the screen before the animation starts. -->
<dimen name="heads_up_appear_y_above_screen">32dp</dimen>
+ <!-- padding between the old and new heads up notifications for the hun cycling animation -->
+ <dimen name="heads_up_cycling_padding">8dp</dimen>
+
<!-- padding between the heads up and the statusbar -->
<dimen name="heads_up_status_bar_padding">8dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index 6539cf35b073..86cc6f522dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -23,10 +23,12 @@ import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
@@ -71,7 +73,7 @@ import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.background
+import com.android.compose.animation.Expandable
import com.android.compose.theme.colorAttr
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
@@ -134,7 +136,7 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic
}
}
- @OptIn(ExperimentalCoroutinesApi::class)
+ @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
@Composable
private fun Tile(
tile: TileViewModel,
@@ -147,28 +149,39 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic
.collectAsState(initial = tile.currentState.toUiState())
val context = LocalContext.current
- Row(
- modifier = modifier.clickable { tile.onClick(null) }.tileModifier(state.colors),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(iconOnly)
+ Expandable(
+ color = colorAttr(state.colors.background),
+ shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
) {
- val icon =
- remember(state.icon) {
- state.icon.get().let {
- if (it is QSTileImpl.ResourceIcon) {
- Icon.Resource(it.resId, null)
- } else {
- Icon.Loaded(it.getDrawable(context), null)
+ Row(
+ modifier =
+ modifier
+ .combinedClickable(
+ onClick = { tile.onClick(it) },
+ onLongClick = { tile.onLongClick(it) }
+ )
+ .tileModifier(state.colors),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(iconOnly),
+ ) {
+ val icon =
+ remember(state.icon) {
+ state.icon.get().let {
+ if (it is QSTileImpl.ResourceIcon) {
+ Icon.Resource(it.resId, null)
+ } else {
+ Icon.Loaded(it.getDrawable(context), null)
+ }
}
}
- }
- TileContent(
- label = state.label.toString(),
- secondaryLabel = state.secondaryLabel.toString(),
- icon = icon,
- colors = state.colors,
- iconOnly = iconOnly
- )
+ TileContent(
+ label = state.label.toString(),
+ secondaryLabel = state.secondaryLabel?.toString(),
+ icon = icon,
+ colors = state.colors,
+ iconOnly = iconOnly
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 6a38f8df4715..d2d0aaa63003 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.BOTTOM;
+import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.TOP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -43,6 +45,7 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -354,12 +357,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
@Override
public long performRemoveAnimation(long duration, long delay, float translationDirection,
boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAnimation;
if (mDrawingAppearAnimation) {
startAppearAnimation(false /* isAppearing */, translationDirection,
- delay, duration, onStartedRunnable, onFinishedRunnable, animationListener);
+ delay, duration, onStartedRunnable, onFinishedRunnable, animationListener,
+ clipSide);
} else {
if (onStartedRunnable != null) {
onStartedRunnable.run();
@@ -378,13 +382,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mIsHeadsUpAnimation = isHeadsUpAppear;
if (mDrawingAppearAnimation) {
startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
- duration, null, null, null);
+ duration, null, null, null, ClipSide.BOTTOM);
}
}
private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
mAnimationTranslationY = translationDirection * getActualHeight();
cancelAppearAnimation();
if (mAppearAnimationFraction == -1.0f) {
@@ -406,9 +410,16 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE;
targetValue = 0.0f;
}
+
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ // TODO(b/316404716): add avalanche filtering
+ mCurrentAppearInterpolator = Interpolators.LINEAR;
+ }
+
mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
targetValue);
- if (NotificationsImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled()
+ || NotificationHeadsUpCycling.isEnabled()) {
mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
} else {
mAppearAnimator.setInterpolator(Interpolators.LINEAR);
@@ -418,7 +429,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mAppearAnimator.addUpdateListener(animation -> {
mAppearAnimationFraction = (float) animation.getAnimatedValue();
updateAppearAnimationAlpha();
- updateAppearRect();
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ // For cycling out, we want the HUN to be clipped from the top.
+ updateAppearRect(clipSide);
+ } else {
+ updateAppearRect();
+ }
invalidate();
});
if (animationListener != null) {
@@ -426,7 +442,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
// we need to apply the initial state already to avoid drawn frames in the wrong state
updateAppearAnimationAlpha();
- updateAppearRect();
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ updateAppearRect(clipSide);
+ } else {
+ updateAppearRect();
+ }
mAppearAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mRunWithoutInterruptions;
@@ -508,14 +528,18 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
enableAppearDrawing(false);
}
- private void updateAppearRect() {
+ /**
+ * Update the View's Rect clipping to fit the appear animation
+ * @param clipSide Which side if view we want to clip from
+ */
+ private void updateAppearRect(ClipSide clipSide) {
float interpolatedFraction =
- NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction
+ NotificationsImprovedHunAnimation.isEnabled()
+ || NotificationHeadsUpCycling.isEnabled() ? mAppearAnimationFraction
: mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
- final int actualHeight = getActualHeight();
- float bottom = actualHeight * interpolatedFraction;
-
+ final int fullHeight = getActualHeight();
+ float height = fullHeight * interpolatedFraction;
if (mTargetPoint != null) {
int width = getWidth();
float fraction = 1 - mAppearAnimationFraction;
@@ -524,13 +548,26 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mAnimationTranslationY
+ (mAnimationTranslationY - mTargetPoint.y) * fraction,
width - (width - mTargetPoint.x) * fraction,
- actualHeight - (actualHeight - mTargetPoint.y) * fraction);
+ fullHeight - (fullHeight - mTargetPoint.y) * fraction);
} else {
- setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
- bottom + mAppearAnimationTranslation);
+ if (clipSide == TOP) {
+ setOutlineRect(
+ 0,
+ /* top= */ fullHeight - height,
+ getWidth(),
+ /* bottom= */ fullHeight
+ );
+ } else if (clipSide == BOTTOM) {
+ setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
+ height + mAppearAnimationTranslation);
+ }
}
}
+ private void updateAppearRect() {
+ updateAppearRect(ClipSide.BOTTOM);
+ }
+
private float getInterpolatedAppearAnimationFraction() {
if (mAppearAnimationFraction >= 0) {
@@ -540,11 +577,36 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
private void updateAppearAnimationAlpha() {
- float contentAlphaProgress = MathUtils.constrain(mAppearAnimationFraction,
- ALPHA_APPEAR_START_FRACTION, ALPHA_APPEAR_END_FRACTION);
- float range = ALPHA_APPEAR_END_FRACTION - ALPHA_APPEAR_START_FRACTION;
- float alpha = (contentAlphaProgress - ALPHA_APPEAR_START_FRACTION) / range;
- setContentAlpha(Interpolators.ALPHA_IN.getInterpolation(alpha));
+ updateAppearAnimationContentAlpha(
+ mAppearAnimationFraction,
+ ALPHA_APPEAR_START_FRACTION,
+ ALPHA_APPEAR_END_FRACTION,
+ Interpolators.ALPHA_IN
+ );
+ }
+
+ /**
+ * Update the alpha value of the content view during the appear animation. We suppose that the
+ * content alpha changes from 0 to 1 during some part of the appear animation.
+ * @param appearFraction the current appearFraction, should be in the range of [0, 1], where
+ * 1 represents fully appeared
+ * @param startFraction the appear fraction when the content view should be
+ * * fully transparent
+ * @param endFraction the appear fraction when the content view should be
+ * fully in-transparent, should be greater or equals to startFraction
+ * @param interpolator the interpolator to update the alpha
+ */
+ private void updateAppearAnimationContentAlpha(
+ float appearFraction,
+ float startFraction,
+ float endFraction,
+ Interpolator interpolator
+ ) {
+ float contentAlphaProgress = MathUtils.constrain(appearFraction, startFraction,
+ endFraction);
+ float range = endFraction - startFraction;
+ float alpha = (contentAlphaProgress - startFraction) / range;
+ setContentAlpha(interpolator.getInterpolation(alpha));
}
private void setContentAlpha(float contentAlpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 23c0a0db04d5..747cb3c8d934 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -3076,7 +3076,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
boolean isHeadsUpAnimation,
Runnable onStartedRunnable,
Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
if (mMenuRow != null && mMenuRow.isMenuVisible()) {
Animator anim = getTranslateViewAnimator(0f, null /* listener */);
if (anim != null) {
@@ -3092,7 +3092,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void onAnimationEnd(Animator animation) {
ExpandableNotificationRow.super.performRemoveAnimation(
duration, delay, translationDirection, isHeadsUpAnimation,
- null, onFinishedRunnable, animationListener);
+ null, onFinishedRunnable, animationListener, ClipSide.BOTTOM);
}
});
anim.start();
@@ -3100,7 +3100,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
return super.performRemoveAnimation(duration, delay, translationDirection,
- isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener);
+ isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener,
+ clipSide);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 05e8717d0005..2af119f98f4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -362,17 +362,17 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
/**
* Perform a remove animation on this view.
- * @param duration The duration of the remove animation.
- * @param delay The delay of the animation
+ *
+ * @param duration The duration of the remove animation.
+ * @param delay The delay of the animation
* @param translationDirection The direction value from [-1 ... 1] indicating in which the
* animation should be performed. A value of -1 means that The
* remove animation should be performed upwards,
* such that the child appears to be going away to the top. 1
* Should mean the opposite.
- * @param isHeadsUpAnimation Is this a headsUp animation.
- * @param onFinishedRunnable A runnable which should be run when the animation is finished.
- * @param animationListener An animation listener to add to the animation.
- *
+ * @param isHeadsUpAnimation Is this a headsUp animation.
+ * @param onFinishedRunnable A runnable which should be run when the animation is finished.
+ * @param animationListener An animation listener to add to the animation.
* @return The additional delay, in milliseconds, that this view needs to add before the
* animation starts.
*/
@@ -380,7 +380,12 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
long delay, float translationDirection, boolean isHeadsUpAnimation,
Runnable onStartedRunnable,
Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener);
+ AnimatorListenerAdapter animationListener, ClipSide clipSide);
+
+ public enum ClipSide {
+ TOP,
+ BOTTOM
+ }
public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
performAddAnimation(delay, duration, isHeadsUpAppear, null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 162e8af47394..291dc132686b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -252,7 +252,7 @@ public abstract class StackScrollerDecorView extends ExpandableView {
float translationDirection, boolean isHeadsUpAnimation,
Runnable onStartedRunnable,
Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
// TODO: Use duration
if (onStartedRunnable != null) {
onStartedRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
index 0344b32dd6ad..d4f8ea385667 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -33,7 +33,12 @@ object NotificationHeadsUpCycling {
/** Is the heads-up cycling animation enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.notificationContentAlphaOptimization()
+ get() = Flags.notificationHeadsUpCycling()
+
+ /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */
+ @JvmStatic
+ inline val animateTallToShort
+ get() = false
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index e520957975f3..5f4e832f31a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -293,6 +293,8 @@ public class AmbientState implements Dumpable {
}
String getAvalancheShowingHunKey() {
+ // If we don't have a previous showing hun, we don't consider the showing hun as avalanche
+ if (isNullAvalancheKey(getAvalanchePreviousHunKey())) return "";
return mAvalancheController.getShowingHunKey();
}
@@ -300,6 +302,11 @@ public class AmbientState implements Dumpable {
return mAvalancheController.getPreviousHunKey();
}
+ boolean isNullAvalancheKey(String key) {
+ if (key == null || key.isEmpty()) return true;
+ return key.equals("HeadsUpEntry null") || key.equals("HeadsUpEntry.mEntry null");
+ }
+
void setOverExpansion(float overExpansion) {
mOverExpansion = overExpansion;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index 5551ab46262c..bd7bd596438a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -70,13 +70,14 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie
}
override fun performRemoveAnimation(
- duration: Long,
- delay: Long,
- translationDirection: Float,
- isHeadsUpAnimation: Boolean,
- onStartedRunnable: Runnable?,
- onFinishedRunnable: Runnable?,
- animationListener: AnimatorListenerAdapter?
+ duration: Long,
+ delay: Long,
+ translationDirection: Float,
+ isHeadsUpAnimation: Boolean,
+ onStartedRunnable: Runnable?,
+ onFinishedRunnable: Runnable?,
+ animationListener: AnimatorListenerAdapter?,
+ clipSide: ClipSide
): Long {
return 0
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 232b4e993f06..bfc74250cefe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -112,6 +112,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -151,7 +152,6 @@ import java.util.function.Consumer;
public class NotificationStackScrollLayout
extends ViewGroup
implements Dumpable, NotificationScrollView {
-
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
@@ -3144,6 +3144,11 @@ public class NotificationStackScrollLayout
type = row.wasJustClicked()
? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
: AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ if (mStackScrollAlgorithm.isCyclingOut(row, mAmbientState)) {
+ type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT;
+ }
+ }
if (row.isChildInGroup()) {
// We can otherwise get stuck in there if it was just isolated
row.setHeadsUpAnimatingAway(false);
@@ -3164,6 +3169,11 @@ public class NotificationStackScrollLayout
if (pinnedAndClosed || shouldHunAppearFromTheBottom) {
// Our custom add animation
type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ if (mStackScrollAlgorithm.isCyclingIn(row, mAmbientState)) {
+ type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN;
+ }
+ }
} else {
// Normal add animation
type = AnimationEvent.ANIMATION_TYPE_ADD;
@@ -6135,6 +6145,22 @@ public class NotificationStackScrollLayout
.animateTopInset()
.animateY()
.animateZ(),
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT
+ new AnimationFilter()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_IN
+ new AnimationFilter()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
};
static int[] LENGTHS = new int[]{
@@ -6186,6 +6212,12 @@ public class NotificationStackScrollLayout
// ANIMATION_TYPE_EVERYTHING
StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT
+ StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING,
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_IN
+ StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING,
};
static final int ANIMATION_TYPE_ADD = 0;
@@ -6204,6 +6236,8 @@ public class NotificationStackScrollLayout
static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13;
static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14;
static final int ANIMATION_TYPE_EVERYTHING = 15;
+ static final int ANIMATION_TYPE_HEADS_UP_CYCLING_OUT = 16;
+ static final int ANIMATION_TYPE_HEADS_UP_CYCLING_IN = 17;
final long eventStartTime;
final ExpandableView mChangingView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index d0cebae40c5a..0fcfc4b4b2c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -38,6 +38,7 @@ import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import java.util.ArrayList;
@@ -75,6 +76,7 @@ public class StackScrollAlgorithm {
private float mSmallCornerRadius;
private float mLargeCornerRadius;
private int mHeadsUpAppearHeightBottom;
+ private int mHeadsUpCyclingPadding;
public StackScrollAlgorithm(
Context context,
@@ -99,6 +101,8 @@ public class StackScrollAlgorithm {
R.dimen.heads_up_status_bar_padding);
mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize(
R.dimen.heads_up_appear_y_above_screen);
+ mHeadsUpCyclingPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_cycling_padding);
mPinnedZTranslationExtra = res.getDimensionPixelSize(
R.dimen.heads_up_pinned_elevation);
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
@@ -348,7 +352,8 @@ public class StackScrollAlgorithm {
&& !firstHeadsUp
&& (isHeadsUp || child.isHeadsUpAnimatingAway())
&& newNotificationEnd > firstHeadsUpEnd
- && !ambientState.isShadeExpanded()) {
+ && !ambientState.isShadeExpanded()
+ && !skipClipBottomForCycling(child, ambientState)) {
// The bottom of this view is peeking out from under the previous view.
// Clip the part that is peeking out.
float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
@@ -370,6 +375,44 @@ public class StackScrollAlgorithm {
}
}
+ /**
+ * @return Should we skip clipping the bottom clipping when new hun has lower bottom line for
+ * the hun cycling animation.
+ */
+ private boolean skipClipBottomForCycling(ExpandableView view, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ if (!isCyclingOut(view, ambientState)) return false;
+ // skip bottom clipping if we animate the bottom line
+ return NotificationHeadsUpCycling.getAnimateTallToShort();
+ }
+
+ /**
+ * Whether the view is the hun that is cycling out by the notification avalanche.
+ */
+ public boolean isCyclingOut(ExpandableView view, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ if (!(view instanceof ExpandableNotificationRow)) return false;
+ return isCyclingOut((ExpandableNotificationRow) view, ambientState);
+ }
+
+ /**
+ * Whether the row is the hun that is cycling out by the notification avalanche.
+ */
+ public boolean isCyclingOut(ExpandableNotificationRow row, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ String cyclingOutKey = ambientState.getAvalanchePreviousHunKey();
+ return row.getEntry().getKey().equals(cyclingOutKey);
+ }
+
+ /**
+ * Whether the row is the hun that is cycling in by the notification avalanche.
+ */
+ public boolean isCyclingIn(ExpandableNotificationRow row, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ String cyclingInKey = ambientState.getAvalancheShowingHunKey();
+ return row.getEntry().getKey().equals(cyclingInKey);
+ }
+
/** Updates the dimmed and hiding sensitive states of the children. */
private void updateDimmedAndHideSensitive(AmbientState ambientState,
StackScrollAlgorithmState algorithmState) {
@@ -799,6 +842,7 @@ public class StackScrollAlgorithm {
}
ExpandableNotificationRow topHeadsUpEntry = null;
+ int cyclingInHunHeight = -1;
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
if (!(child instanceof ExpandableNotificationRow row)) {
@@ -839,6 +883,13 @@ public class StackScrollAlgorithm {
childState.setYTranslation(
Math.max(childState.getYTranslation(), headsUpTranslation));
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ if (isCyclingIn(row, ambientState)) {
+ if (cyclingInHunHeight == -1) {
+ cyclingInHunHeight = childState.height;
+ }
+ }
+ }
childState.hidden = false;
ExpandableViewState topState =
topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState();
@@ -860,6 +911,26 @@ public class StackScrollAlgorithm {
}
}
if (row.isHeadsUpAnimatingAway()) {
+ if (NotificationHeadsUpCycling.isEnabled() && isCyclingOut(row, ambientState)) {
+ // If the two HUNs in the cycling animation have different heights, we need
+ // an extra y translation to align the animation.
+ int extraTranslation;
+ if (NotificationHeadsUpCycling.getAnimateTallToShort()) {
+ if (cyclingInHunHeight > 0) {
+ extraTranslation = cyclingInHunHeight - childState.height;
+ } else {
+ extraTranslation = 0;
+ }
+ } else {
+ extraTranslation = cyclingInHunHeight >= childState.height
+ ? cyclingInHunHeight - childState.height : 0;
+ }
+ extraTranslation += mHeadsUpCyclingPadding;
+ float inSpaceTranslation = Math.max(childState.getYTranslation(),
+ headsUpTranslation);
+ childState.setYTranslation(inSpaceTranslation + extraTranslation);
+ cyclingInHunHeight = -1;
+ } else
if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) {
if (shouldHunAppearFromBottom(ambientState, childState)) {
// move to the bottom of the screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 5963d358443e..5dc544993ddc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.stack;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK;
@@ -57,6 +59,7 @@ public class StackStateAnimator {
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
+ public static final int ANIMATION_DURATION_HEADS_UP_CYCLING = 400;
public static final int ANIMATION_DURATION_FOLD_TO_AOD =
AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500;
@@ -68,6 +71,8 @@ public class StackStateAnimator {
@VisibleForTesting int mGoToFullShadeAppearingTranslation;
@VisibleForTesting float mHeadsUpAppearStartAboveScreen;
+ // Padding between the old and new heads up notifications for the hun cycling animation
+ private float mHeadsUpCyclingPadding;
private final ExpandableViewState mTmpState = new ExpandableViewState();
private final AnimationProperties mAnimationProperties;
public NotificationStackScrollLayout mHostLayout;
@@ -125,6 +130,8 @@ public class StackStateAnimator {
R.dimen.go_to_full_shade_appearing_translation);
mHeadsUpAppearStartAboveScreen = context.getResources()
.getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
+ mHeadsUpCyclingPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_cycling_padding);
}
protected void setLogger(StackStateLogger logger) {
@@ -449,7 +456,8 @@ public class StackStateAnimator {
}
changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
- startAnimation, postAnimation, getGlobalAnimationFinishedListener());
+ startAnimation, postAnimation, getGlobalAnimationFinishedListener(),
+ ExpandableView.ClipSide.BOTTOM);
needsCustomAnimation = true;
} else if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
@@ -464,6 +472,27 @@ public class StackStateAnimator {
.AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView;
row.prepareExpansionChanged();
+ } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_IN) {
+ mHeadsUpAppearChildren.add(changingView);
+
+ mTmpState.copyFrom(changingView.getViewState());
+ mTmpState.setYTranslation(changingView.getViewState().getYTranslation()
+ + getHeadsUpCyclingInYTranslationStart(event.headsUpFromBottom));
+ mTmpState.applyToView(changingView);
+
+ // TODO(b/339519404): use a different interpolator
+ Runnable onAnimationEnd = null;
+ if (loggable) {
+ // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with
+ // normal ADD animations, which would not be logged here.
+ String finalKey = key;
+ mLogger.logHUNViewAppearing(key);
+ onAnimationEnd = () -> {
+ mLogger.appearAnimationEnded(finalKey);
+ };
+ }
+ changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING,
+ /* isHeadsUpAppear= */ true, onAnimationEnd);
} else if (NotificationsImprovedHunAnimation.isEnabled()
&& (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) {
mHeadsUpAppearChildren.add(changingView);
@@ -486,6 +515,87 @@ public class StackStateAnimator {
}
changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
/* isHeadsUpAppear= */ true, onAnimationEnd);
+ } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_OUT) {
+ mHeadsUpDisappearChildren.add(changingView);
+ Runnable endRunnable = null;
+ mTmpState.copyFrom(changingView.getViewState());
+
+ if (changingView.getParent() == null) {
+ // This notification was actually removed, so we need to add it
+ // transiently
+ mHostLayout.addTransientView(changingView, 0);
+ changingView.setTransientContainer(mHostLayout);
+ // TODO(b/316404716): remove the hard-coded height
+ // StackScrollAlgorithm cannot find this view because it has been removed
+ // from the NSSL. To correctly translate the view to the top or bottom of
+ // the screen (where it animated from), we need to update its translation.
+ mTmpState.setYTranslation(
+ mTmpState.getYTranslation() + 10
+ );
+ endRunnable = changingView::removeFromTransientContainer;
+ }
+
+ boolean needsAnimation = true;
+ if (changingView instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row =
+ (ExpandableNotificationRow) changingView;
+ if (row.isDismissed()) {
+ needsAnimation = false;
+ }
+ }
+ if (needsAnimation) {
+ // We need to add the global animation listener, since once no animations are
+ // running anymore, the panel will instantly hide itself. We need to wait until
+ // the animation is fully finished for this though.
+ final Runnable tmpEndRunnable = endRunnable;
+ Runnable postAnimation;
+ Runnable startAnimation;
+ if (loggable) {
+ String finalKey1 = key;
+ final boolean finalIsHeadsUp = isHeadsUp;
+ final String type = "ANIMATION_TYPE_HEADS_UP_CYCLING_OUT";
+ startAnimation = () -> {
+ mLogger.animationStart(finalKey1, type, finalIsHeadsUp);
+ changingView.setInRemovalAnimation(true);
+ };
+ postAnimation = () -> {
+ mLogger.animationEnd(finalKey1, type, finalIsHeadsUp);
+ changingView.setInRemovalAnimation(false);
+ if (tmpEndRunnable != null) {
+ tmpEndRunnable.run();
+ }
+
+ };
+ } else {
+ postAnimation = () -> {
+ changingView.setInRemovalAnimation(false);
+ if (tmpEndRunnable != null) {
+ tmpEndRunnable.run();
+ }
+ };
+ startAnimation = () -> {
+ changingView.setInRemovalAnimation(true);
+ };
+ }
+ long removeAnimationDelay = changingView.performRemoveAnimation(
+ ANIMATION_DURATION_HEADS_UP_CYCLING,
+ /* delay= */ 0,
+ // It's a shame that translationDirection isn't where we do the y
+ // translation, the actual translation is in StackScrollAlgorithm.
+ /* translationDirection= */ 0.0f,
+ /* isHeadsUpAnimation= */ true,
+ startAnimation, postAnimation,
+ getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.TOP);
+ mAnimationProperties.delay += removeAnimationDelay;
+ mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_CYCLING;
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.LINEAR);
+ mAnimationProperties.getAnimationFilter().animateY = true;
+ mTmpState.animateTo(changingView, mAnimationProperties);
+ } else if (endRunnable != null) {
+ endRunnable.run();
+ }
+ needsCustomAnimation |= needsAnimation;
} else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
NotificationsImprovedHunAnimation.assertInLegacyMode();
// This item is added, initialize its properties.
@@ -565,21 +675,21 @@ public class StackStateAnimator {
}
};
} else {
+ startAnimation = () -> {
+ changingView.setInRemovalAnimation(true);
+ };
postAnimation = () -> {
changingView.setInRemovalAnimation(false);
if (tmpEndRunnable != null) {
tmpEndRunnable.run();
}
};
- startAnimation = () -> {
- changingView.setInRemovalAnimation(true);
- };
}
long removeAnimationDelay = changingView.performRemoveAnimation(
ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
0, 0.0f, true /* isHeadsUpAppear */,
startAnimation, postAnimation,
- getGlobalAnimationFinishedListener());
+ getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM);
mAnimationProperties.delay += removeAnimationDelay;
if (NotificationsImprovedHunAnimation.isEnabled()) {
mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
@@ -607,6 +717,38 @@ public class StackStateAnimator {
return -mStackTopMargin - mHeadsUpAppearStartAboveScreen;
}
+ /**
+ * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen
+ * @return The start y translation of the HUN cycling in animation
+ */
+ private float getHeadsUpCyclingInYTranslationStart(boolean headsUpFromBottom) {
+ if (headsUpFromBottom) {
+ // start from the bottom of the screen
+ return mHeadsUpAppearHeightBottom + mHeadsUpCyclingPadding;
+ }
+ // start from the top of the screen
+ return -mHeadsUpCyclingPadding;
+ }
+
+ /**
+ * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen
+ * @param oldHunHeight Height of the old HUN
+ * @param newHunHeight Height of the new HUN
+ * @return The y translation target value of the HUN cycling out animation
+ */
+ private float getHeadsUpCyclingOutYTranslation(
+ boolean headsUpFromBottom,
+ int oldHunHeight,
+ int newHunHeight
+ ) {
+ final float translationDistance = mHeadsUpCyclingPadding + newHunHeight - oldHunHeight;
+ if (headsUpFromBottom) {
+ // start from the bottom of the screen
+ return mHeadsUpAppearHeightBottom - translationDistance;
+ }
+ return translationDistance;
+ }
+
public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
final boolean isRubberbanded) {
final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index 4f0f91a7ee56..926c35f32967 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -134,7 +134,8 @@ class StackStateAnimatorTest : SysuiTestCase() {
/* isHeadsUpAnimation= */ eq(true),
/* onStartedRunnable= */ any(),
/* onFinishedRunnable= */ runnableCaptor.capture(),
- /* animationListener= */ any()
+ /* animationListener= */ any(),
+ /* clipSide= */ eq(ExpandableView.ClipSide.BOTTOM),
)
animatorTestRule.advanceTimeBy(disappearDuration) // move to the end of SSA animations
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 9c84b123a435..c4b461f869b0 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -564,6 +564,18 @@ public final class PresentationStatsEventLogger {
});
}
+ /** Sets focused_autofill_id using view id */
+ public void maybeSetFocusedId(AutofillId id) {
+ maybeSetFocusedId(id.getViewId());
+ }
+
+ /** Sets focused_autofill_id as long as mEventInternal is present */
+ public void maybeSetFocusedId(int id) {
+ mEventInternal.ifPresent(event -> {
+ event.mFocusedId = id;
+ });
+ }
+
public void logAndEndEvent() {
if (!mEventInternal.isPresent()) {
Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -608,7 +620,8 @@ public final class PresentationStatsEventLogger {
+ " mIsCredentialRequest=" + event.mIsCredentialRequest
+ " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential
+ " mViewFillableTotalCount=" + event.mViewFillableTotalCount
- + " mViewFillFailureCount=" + event.mViewFillFailureCount);
+ + " mViewFillFailureCount=" + event.mViewFillFailureCount
+ + " mFocusedId=" + event.mFocusedId);
}
// TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -651,7 +664,8 @@ public final class PresentationStatsEventLogger {
event.mIsCredentialRequest,
event.mWebviewRequestedCredential,
event.mViewFillableTotalCount,
- event.mViewFillFailureCount);
+ event.mViewFillFailureCount,
+ event.mFocusedId);
mEventInternal = Optional.empty();
}
@@ -689,6 +703,7 @@ public final class PresentationStatsEventLogger {
boolean mWebviewRequestedCredential = false;
int mViewFillableTotalCount = -1;
int mViewFillFailureCount = -1;
+ int mFocusedId = -1;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8b13c4b762d5..03cf74fe0619 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4669,6 +4669,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mFieldClassificationIdSnapshot);
mPresentationStatsEventLogger.maybeSetAvailableCount(
response.getDatasets(), mCurrentViewId);
+ mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId);
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 748253fc9194..1a8c3b086341 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -35,7 +35,6 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
-import android.os.Bundle;
import android.os.BundleMerger;
import android.os.Debug;
import android.os.DropBoxManager;
@@ -176,6 +175,16 @@ public final class DropBoxManagerService extends SystemService {
}
};
+ private static final BundleMerger sDropboxEntryAddedExtrasMerger;
+ static {
+ sDropboxEntryAddedExtrasMerger = new BundleMerger();
+ sDropboxEntryAddedExtrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST);
+ sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
+ BundleMerger.STRATEGY_COMPARABLE_MAX);
+ sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
+ BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
+ }
+
private final IDropBoxManagerService.Stub mStub = new IDropBoxManagerService.Stub() {
@Override
public void addData(String tag, byte[] data, int flags) {
@@ -284,7 +293,7 @@ public final class DropBoxManagerService extends SystemService {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SEND_BROADCAST:
- prepareAndSendBroadcast((Intent) msg.obj, null);
+ prepareAndSendBroadcast((Intent) msg.obj, false);
break;
case MSG_SEND_DEFERRED_BROADCAST:
Intent deferredIntent;
@@ -292,31 +301,42 @@ public final class DropBoxManagerService extends SystemService {
deferredIntent = mDeferredMap.remove((String) msg.obj);
}
if (deferredIntent != null) {
- prepareAndSendBroadcast(deferredIntent,
- createBroadcastOptions(deferredIntent));
+ prepareAndSendBroadcast(deferredIntent, true);
}
break;
}
}
- private void prepareAndSendBroadcast(Intent intent, Bundle options) {
+ private void prepareAndSendBroadcast(Intent intent, boolean deferrable) {
if (!DropBoxManagerService.this.mBooted) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
if (Flags.enableReadDropboxPermission()) {
- BroadcastOptions unbundledOptions = (options == null)
- ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options);
-
- unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+ options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+ if (deferrable) {
+ final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG)
+ + "-READ_DROPBOX_DATA";
+ setBroadcastOptionsForDeferral(options, matchingKey);
+ }
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle());
+ Manifest.permission.READ_DROPBOX_DATA, options.toBundle());
- unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+ options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+ if (deferrable) {
+ final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG)
+ + "-READ_LOGS";
+ setBroadcastOptionsForDeferral(options, matchingKey);
+ }
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.READ_LOGS, unbundledOptions.toBundle());
+ Manifest.permission.READ_LOGS, options.toBundle());
} else {
+ if (deferrable) {
+ final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG);
+ setBroadcastOptionsForDeferral(options, matchingKey);
+ }
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- android.Manifest.permission.READ_LOGS, options);
+ android.Manifest.permission.READ_LOGS, options.toBundle());
}
}
@@ -328,21 +348,12 @@ public final class DropBoxManagerService extends SystemService {
return dropboxIntent;
}
- private Bundle createBroadcastOptions(Intent intent) {
- final BundleMerger extrasMerger = new BundleMerger();
- extrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST);
- extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
- BundleMerger.STRATEGY_COMPARABLE_MAX);
- extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
- BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
-
- return BroadcastOptions.makeBasic()
- .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
+ private void setBroadcastOptionsForDeferral(BroadcastOptions options, String matchingKey) {
+ options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
.setDeliveryGroupMatchingKey(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED,
- intent.getStringExtra(DropBoxManager.EXTRA_TAG))
- .setDeliveryGroupExtrasMerger(extrasMerger)
- .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
- .toBundle();
+ matchingKey)
+ .setDeliveryGroupExtrasMerger(sDropboxEntryAddedExtrasMerger)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
}
/**
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index cc6ae5ca03e3..8647750d510f 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3277,7 +3277,13 @@ public class OomAdjuster {
}
final int curSchedGroup = state.getCurrentSchedulingGroup();
- if (state.getSetSchedGroup() != curSchedGroup) {
+ if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
+ && ActivityManager.isProcStateBackground(state.getCurProcState())
+ && !state.hasStartedServices()) {
+ app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
+ ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
+ success = false;
+ } else if (state.getSetSchedGroup() != curSchedGroup) {
int oldSchedGroup = state.getSetSchedGroup();
state.setSetSchedGroup(curSchedGroup);
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
@@ -3285,75 +3291,67 @@ public class OomAdjuster {
+ " to " + curSchedGroup + ": " + state.getAdjType();
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
}
- if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
- && ActivityManager.isProcStateBackground(state.getSetProcState())
- && !state.hasStartedServices()) {
- app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
- success = false;
- } else {
- int processGroup;
- switch (curSchedGroup) {
- case SCHED_GROUP_BACKGROUND:
- processGroup = THREAD_GROUP_BACKGROUND;
- break;
- case SCHED_GROUP_TOP_APP:
- case SCHED_GROUP_TOP_APP_BOUND:
- processGroup = THREAD_GROUP_TOP_APP;
- break;
- case SCHED_GROUP_RESTRICTED:
- processGroup = THREAD_GROUP_RESTRICTED;
- break;
- default:
- processGroup = THREAD_GROUP_DEFAULT;
- break;
- }
- mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
- 0 /* unused */, app.getPid(), processGroup, app.processName));
- try {
- final int renderThreadTid = app.getRenderThreadTid();
- if (curSchedGroup == SCHED_GROUP_TOP_APP) {
- // do nothing if we already switched to RT
- if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
- app.getWindowProcessController().onTopProcChanged();
- if (app.useFifoUiScheduling()) {
- // Switch UI pipeline for app to SCHED_FIFO
- state.setSavedPriority(Process.getThreadPriority(app.getPid()));
- ActivityManagerService.setFifoPriority(app, true /* enable */);
- } else {
- // Boost priority for top app UI and render threads
- setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
- if (renderThreadTid != 0) {
- try {
- setThreadPriority(renderThreadTid,
- THREAD_PRIORITY_TOP_APP_BOOST);
- } catch (IllegalArgumentException e) {
- // thread died, ignore
- }
- }
- }
- }
- } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
- && curSchedGroup != SCHED_GROUP_TOP_APP) {
+ int processGroup;
+ switch (curSchedGroup) {
+ case SCHED_GROUP_BACKGROUND:
+ processGroup = THREAD_GROUP_BACKGROUND;
+ break;
+ case SCHED_GROUP_TOP_APP:
+ case SCHED_GROUP_TOP_APP_BOUND:
+ processGroup = THREAD_GROUP_TOP_APP;
+ break;
+ case SCHED_GROUP_RESTRICTED:
+ processGroup = THREAD_GROUP_RESTRICTED;
+ break;
+ default:
+ processGroup = THREAD_GROUP_DEFAULT;
+ break;
+ }
+ mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
+ 0 /* unused */, app.getPid(), processGroup, app.processName));
+ try {
+ final int renderThreadTid = app.getRenderThreadTid();
+ if (curSchedGroup == SCHED_GROUP_TOP_APP) {
+ // do nothing if we already switched to RT
+ if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
app.getWindowProcessController().onTopProcChanged();
if (app.useFifoUiScheduling()) {
- // Reset UI pipeline to SCHED_OTHER
- ActivityManagerService.setFifoPriority(app, false /* enable */);
- setThreadPriority(app.getPid(), state.getSavedPriority());
+ // Switch UI pipeline for app to SCHED_FIFO
+ state.setSavedPriority(Process.getThreadPriority(app.getPid()));
+ ActivityManagerService.setFifoPriority(app, true /* enable */);
} else {
- // Reset priority for top app UI and render threads
- setThreadPriority(app.getPid(), 0);
- }
-
- if (renderThreadTid != 0) {
- setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
+ // Boost priority for top app UI and render threads
+ setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
+ if (renderThreadTid != 0) {
+ try {
+ setThreadPriority(renderThreadTid,
+ THREAD_PRIORITY_TOP_APP_BOOST);
+ } catch (IllegalArgumentException e) {
+ // thread died, ignore
+ }
+ }
}
}
- } catch (Exception e) {
- if (DEBUG_ALL) {
- Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
+ } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
+ && curSchedGroup != SCHED_GROUP_TOP_APP) {
+ app.getWindowProcessController().onTopProcChanged();
+ if (app.useFifoUiScheduling()) {
+ // Reset UI pipeline to SCHED_OTHER
+ ActivityManagerService.setFifoPriority(app, false /* enable */);
+ setThreadPriority(app.getPid(), state.getSavedPriority());
+ } else {
+ // Reset priority for top app UI and render threads
+ setThreadPriority(app.getPid(), 0);
+ }
+
+ if (renderThreadTid != 0) {
+ setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
}
}
+ } catch (Exception e) {
+ if (DEBUG_ALL) {
+ Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
+ }
}
}
if (state.hasRepForegroundActivities() != state.hasForegroundActivities()) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 85eb044ca967..1db3483e3800 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2912,10 +2912,12 @@ public class AppOpsService extends IAppOpsService.Stub {
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
- final int proxiedUid = attributionSource.getNextUid();
final int proxyVirtualDeviceId = attributionSource.getDeviceId();
+
+ final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+ final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
@@ -2952,7 +2954,8 @@ public class AppOpsService extends IAppOpsService.Stub {
final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
- Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted,
+ Process.INVALID_UID, null, null,
+ Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted,
"proxy " + message, shouldCollectMessage);
if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
@@ -2970,9 +2973,9 @@ public class AppOpsService extends IAppOpsService.Stub {
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
- proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName,
- proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName,
+ proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage);
}
@Override
@@ -3023,14 +3026,14 @@ public class AppOpsService extends IAppOpsService.Stub {
}
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
virtualDeviceId, Process.INVALID_UID, null, null,
- AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage);
}
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag, int virtualDeviceId, int proxyUid,
- String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId,
+ @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage) {
PackageVerificationResult pvr;
try {
@@ -3161,8 +3164,9 @@ public class AppOpsService extends IAppOpsService.Stub {
}
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
+
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- uidState.getState(), flags);
+ getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags);
if (shouldCollectAsyncNotedOp) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
@@ -3528,9 +3532,9 @@ public class AppOpsService extends IAppOpsService.Stub {
}
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
- virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF,
- startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- attributionFlags, attributionChainId);
+ virtualDeviceId, Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT,
+ OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, attributionFlags, attributionChainId);
}
/** @deprecated Use {@link #startProxyOperationWithState} instead. */
@@ -3568,18 +3572,32 @@ public class AppOpsService extends IAppOpsService.Stub {
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
- final int proxiedUid = attributionSource.getNextUid();
final int proxyVirtualDeviceId = attributionSource.getDeviceId();
+
+ final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+ final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
- Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
- + proxyVirtualDeviceId + " is invalid");
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
- proxiedPackageName);
+ Slog.w(
+ TAG,
+ "startProxyOperationImpl returned MODE_IGNORED as proxyVirtualDeviceId "
+ + proxyVirtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(
+ AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName);
+ }
+ if (!isValidVirtualDeviceId(proxiedVirtualDeviceId)) {
+ Slog.w(
+ TAG,
+ "startProxyOperationImpl returned MODE_IGNORED as proxiedVirtualDeviceId "
+ + proxiedVirtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(
+ AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName);
}
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
@@ -3621,7 +3639,7 @@ public class AppOpsService extends IAppOpsService.Stub {
// Test if the proxied operation will succeed before starting the proxy operation
final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
- proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
+ proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
startIfModeDefault);
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
@@ -3633,7 +3651,7 @@ public class AppOpsService extends IAppOpsService.Stub {
final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
- Process.INVALID_UID, null, null, proxyFlags,
+ Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, proxyFlags,
startIfModeDefault, !isProxyTrusted, "proxy " + message,
shouldCollectMessage, proxyAttributionFlags, attributionChainId);
if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
@@ -3642,9 +3660,10 @@ public class AppOpsService extends IAppOpsService.Stub {
}
return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName,
- proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
+ proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName,
+ proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, proxiedAttributionFlags,
+ attributionChainId);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3654,9 +3673,10 @@ public class AppOpsService extends IAppOpsService.Stub {
private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
@NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag,
- @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage,
- @AttributionFlags int attributionFlags, int attributionChainId) {
+ int proxyVirtualDeviceId, @OpFlags int flags, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3751,13 +3771,13 @@ public class AppOpsService extends IAppOpsService.Stub {
+ " flags: " + AppOpsManager.flagsToString(flags));
try {
if (isRestricted) {
- attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
- attributionFlags, attributionChainId);
+ attributedOp.createPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName,
+ proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+ uidState.getState(), flags, attributionFlags, attributionChainId);
} else {
- attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
- attributionFlags, attributionChainId);
+ attributedOp.started(clientId, virtualDeviceId, proxyUid, proxyPackageName,
+ proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+ uidState.getState(), flags, attributionFlags, attributionChainId);
startType = START_TYPE_STARTED;
}
} catch (RemoteException e) {
@@ -4946,7 +4966,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (accessTime > 0) {
attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
- proxyAttributionTag, uidState, opFlags);
+ proxyAttributionTag, PERSISTENT_DEVICE_ID_DEFAULT, uidState, opFlags);
}
if (rejectTime > 0) {
attributedOp.rejected(rejectTime, uidState, opFlags);
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 2285826c0b58..2760ccf72f98 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -24,7 +24,6 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
-import android.companion.virtual.VirtualDeviceManager;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
@@ -95,16 +94,17 @@ final class AttributedOp {
*
* @param proxyUid The uid of the proxy
* @param proxyPackageName The package name of the proxy
- * @param proxyAttributionTag the attributionTag in the proxies package
+ * @param proxyAttributionTag The attributionTag in the proxies package
+ * @param proxyDeviceId The device Id of the proxy
* @param uidState UID state of the app noteOp/startOp was called for
* @param flags OpFlags of the call
*/
public void accessed(int proxyUid, @Nullable String proxyPackageName,
- @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
- @AppOpsManager.OpFlags int flags) {
+ @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
long accessTime = System.currentTimeMillis();
- accessed(accessTime, -1, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState, flags);
+ accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId,
+ uidState, flags);
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, tag, uidState, flags, accessTime,
@@ -118,14 +118,16 @@ final class AttributedOp {
* @param duration The duration of the event
* @param proxyUid The uid of the proxy
* @param proxyPackageName The package name of the proxy
- * @param proxyAttributionTag the attributionTag in the proxies package
+ * @param proxyAttributionTag The attributionTag in the proxies package
+ * @param proxyDeviceId The device Id of the proxy
* @param uidState UID state of the app noteOp/startOp was called for
* @param flags OpFlags of the call
*/
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
public void accessed(long noteTime, long duration, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags) {
long key = makeKey(uidState, flags);
if (mAccessEvents == null) {
@@ -135,7 +137,7 @@ final class AttributedOp {
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ proxyAttributionTag, proxyDeviceId);
}
AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -189,35 +191,36 @@ final class AttributedOp {
* Update state when start was called
*
* @param clientId Id of the startOp caller
+ * @param virtualDeviceId The virtual device id of the startOp caller
* @param proxyUid The UID of the proxy app
* @param proxyPackageName The package name of the proxy app
* @param proxyAttributionTag The attribution tag of the proxy app
+ * @param proxyDeviceId The device id of the proxy app
* @param uidState UID state of the app startOp is called for
* @param flags The proxy flags
* @param attributionFlags The attribution flags associated with this operation.
- * @param attributionChainId The if of the attribution chain this operations is a part of.
+ * @param attributionChainId The if of the attribution chain this operations is a part of
*/
- public void started(@NonNull IBinder clientId, int proxyUid,
+ public void started(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
@AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
int attributionChainId) throws RemoteException {
- startedOrPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, proxyVirtualDeviceId, uidState, flags,
- /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags,
- attributionChainId);
+ startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag,
+ proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false,
+ true);
}
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
- private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
+ private void startedOrPaused(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
- @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange,
- boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags,
- int attributionChainId) throws RemoteException {
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId, boolean triggeredByUidStateChange, boolean isStarted)
+ throws RemoteException {
if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags,
+ parent.packageName, tag, virtualDeviceId, true, attributionFlags,
attributionChainId);
}
@@ -233,9 +236,9 @@ final class AttributedOp {
InProgressStartOpEvent event = events.get(clientId);
if (event == null) {
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
- SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId,
+ SystemClock.elapsedRealtime(), clientId, tag, virtualDeviceId,
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
- proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
+ proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, uidState, flags,
attributionFlags, attributionChainId);
events.put(clientId, event);
} else {
@@ -366,15 +369,14 @@ final class AttributedOp {
/**
* Create an event that will be started, if the op is unpaused.
*/
- public void createPaused(@NonNull IBinder clientId, int proxyUid,
- @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
- @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags int attributionFlags,
+ public void createPaused(@NonNull IBinder clientId, int virtualDeviceId,
+ int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
int attributionChainId) throws RemoteException {
- startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
- proxyVirtualDeviceId, uidState, flags, false, false,
- attributionFlags, attributionChainId);
+ startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag,
+ proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false,
+ false);
}
/**
@@ -496,16 +498,16 @@ final class AttributedOp {
// Call started() to add a new start event object and then add the
// previously removed unfinished start counts back
if (proxy != null) {
- startedOrPaused(event.getClientId(), proxy.getUid(),
- proxy.getPackageName(), proxy.getAttributionTag(),
- event.getVirtualDeviceId(), newState, event.getFlags(),
- true, isRunning,
- event.getAttributionFlags(), event.getAttributionChainId());
+ startedOrPaused(event.getClientId(), event.getVirtualDeviceId(),
+ proxy.getUid(), proxy.getPackageName(), proxy.getAttributionTag(),
+ proxy.getDeviceId(), newState, event.getFlags(),
+ event.getAttributionFlags(), event.getAttributionChainId(), true,
+ isRunning);
} else {
- startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
- event.getVirtualDeviceId(), newState, event.getFlags(), true,
- isRunning, event.getAttributionFlags(),
- event.getAttributionChainId());
+ startedOrPaused(event.getClientId(), event.getVirtualDeviceId(),
+ Process.INVALID_UID, null, null, null,
+ newState, event.getFlags(), event.getAttributionFlags(),
+ event.getAttributionChainId(), true, isRunning);
}
events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
@@ -847,7 +849,8 @@ final class AttributedOp {
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
@Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
int proxyUid, @Nullable String proxyPackageName,
- @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+ @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
+ @AppOpsManager.UidState int uidState,
@AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags
int attributionFlags, int attributionChainId) throws RemoteException {
@@ -856,7 +859,7 @@ final class AttributedOp {
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ proxyAttributionTag, proxyDeviceId);
}
if (recycled != null) {
@@ -880,7 +883,8 @@ final class AttributedOp {
super(maxUnusedPooledObjects);
}
- AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
+ AppOpsManager.OpEventProxyInfo acquire(
+ @IntRange(from = 0) int uid,
@Nullable String packageName,
@Nullable String attributionTag,
@Nullable String deviceId) {
@@ -890,7 +894,7 @@ final class AttributedOp {
return recycled;
}
- return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag);
+ return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag, deviceId);
}
}
}
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index 2703a2c01848..7e18d8412aae 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -158,9 +158,11 @@ final class SendKeyAction extends HdmiCecFeatureAction {
mTargetAddress, cecKeycodeAndParams), new SendMessageCallback() {
@Override
public void onSendCompleted(int error) {
- if (error != SendMessageResult.SUCCESS) {
+ // Disable System Audio Mode, if the AVR doesn't acknowledge
+ // a <User Control Pressed> message.
+ if (error == SendMessageResult.NACK) {
HdmiLogger.debug(
- "AVR did not respond to <User Control Pressed>");
+ "AVR did not acknowledge <User Control Pressed>");
localDevice().mService.setSystemAudioActivated(false);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 42ec1c3ad4ed..61054a9d4de5 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -11271,6 +11271,9 @@ public class NotificationManagerService extends SystemService {
// Lifetime extended notifications don't need to alert on state change.
record.setPostSilently(true);
+ // We also set FLAG_ONLY_ALERT_ONCE to avoid the notification from HUN-ing again.
+ record.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+
mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(),
record, isAppForeground,
mPostNotificationTrackerFactory.newTracker(null)));
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
new file mode 100644
index 000000000000..7f2327aa4f24
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.testing.TestableContext;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+public class AppOpsDeviceAwareServiceTest {
+
+ @Rule
+ public final TestableContext mContext =
+ new TestableContext(InstrumentationRegistry.getInstrumentation().getTargetContext());
+
+ @Rule
+ public VirtualDeviceRule virtualDeviceRule =
+ VirtualDeviceRule.withAdditionalPermissions(
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ Manifest.permission.GET_APP_OPS_STATS);
+
+ private static final String ATTRIBUTION_TAG_1 = "attributionTag1";
+ private static final String ATTRIBUTION_TAG_2 = "attributionTag2";
+ private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ private final PermissionManager mPermissionManager =
+ mContext.getSystemService(PermissionManager.class);
+
+ private VirtualDeviceManager.VirtualDevice mVirtualDevice;
+
+ @Before
+ public void setUp() {
+ mVirtualDevice =
+ virtualDeviceRule.createManagedVirtualDevice(
+ new VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM)
+ .build());
+
+ mPermissionManager.grantRuntimePermission(
+ mContext.getOpPackageName(),
+ Manifest.permission.CAMERA,
+ mVirtualDevice.getPersistentDeviceId());
+
+ mPermissionManager.grantRuntimePermission(
+ mContext.getOpPackageName(),
+ Manifest.permission.CAMERA,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+ @Test
+ public void noteProxyOp_proxyAppOnDefaultDevice() {
+ AppOpsManager.OpEventProxyInfo proxyInfo =
+ noteProxyOpWithDeviceId(TestableContext.DEVICE_ID_DEFAULT);
+ assertThat(proxyInfo.getDeviceId())
+ .isEqualTo(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+ @Test
+ public void noteProxyOp_proxyAppOnRemoteDevice() {
+ AppOpsManager.OpEventProxyInfo proxyInfo =
+ noteProxyOpWithDeviceId(mVirtualDevice.getDeviceId());
+ assertThat(proxyInfo.getDeviceId()).isEqualTo(mVirtualDevice.getPersistentDeviceId());
+ }
+
+ private AppOpsManager.OpEventProxyInfo noteProxyOpWithDeviceId(int proxyAppDeviceId) {
+ AttributionSource proxiedAttributionSource =
+ new AttributionSource.Builder(Process.myUid())
+ .setPackageName(mContext.getOpPackageName())
+ .setAttributionTag(ATTRIBUTION_TAG_2)
+ .setDeviceId(mVirtualDevice.getDeviceId())
+ .build();
+
+ AttributionSource proxyAttributionSource =
+ new AttributionSource.Builder(Process.myUid())
+ .setPackageName(mContext.getOpPackageName())
+ .setAttributionTag(ATTRIBUTION_TAG_1)
+ .setDeviceId(proxyAppDeviceId)
+ .setNextAttributionSource(proxiedAttributionSource)
+ .build();
+
+ int mode = mAppOpsManager.noteProxyOp(OP_CAMERA, proxyAttributionSource, null, false);
+ assertThat(mode).isEqualTo(AppOpsManager.MODE_ALLOWED);
+
+ List<AppOpsManager.PackageOps> packagesOps =
+ mAppOpsManager.getPackagesForOps(
+ new String[] {AppOpsManager.OPSTR_CAMERA},
+ mVirtualDevice.getPersistentDeviceId());
+
+ AppOpsManager.PackageOps packageOps =
+ packagesOps.stream()
+ .filter(
+ pkg ->
+ Objects.equals(
+ pkg.getPackageName(), mContext.getOpPackageName()))
+ .findFirst()
+ .orElseThrow();
+
+ AppOpsManager.OpEntry opEntry =
+ packageOps.getOps().stream()
+ .filter(op -> op.getOp() == OP_CAMERA)
+ .findFirst()
+ .orElseThrow();
+
+ return opEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED);
+ }
+}
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 5e2fe6a080eb..2d672b89662f 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5969,6 +5969,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(captor.getValue().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
assertThat(captor.getValue().shouldPostSilently()).isTrue();
}
@@ -8798,6 +8800,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(captor.getValue().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
assertThat(captor.getValue().shouldPostSilently()).isTrue();
}