diff options
30 files changed, 341 insertions, 294 deletions
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index 7cbb98e1bb4e..19f578ecfc66 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -203,6 +203,15 @@ public final class MediaParser { /** Returned by {@link #getDurationMicros()} when the duration is unknown. */ public static final int UNKNOWN_DURATION = Integer.MIN_VALUE; + /** + * For each {@link #getSeekPoints} call, returns a single {@link SeekPoint} whose {@link + * SeekPoint#timeMicros} matches the requested timestamp, and whose {@link + * SeekPoint#position} is 0. + * + * @hide + */ + public static final SeekMap DUMMY = new SeekMap(new DummyExoPlayerSeekMap()); + private final com.google.android.exoplayer2.extractor.SeekMap mExoPlayerSeekMap; private SeekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) { @@ -795,6 +804,18 @@ public final class MediaParser { */ public static final String PARAMETER_EAGERLY_EXPOSE_TRACKTYPE = "android.media.mediaparser.eagerlyExposeTrackType"; + /** + * Sets whether a dummy {@link SeekMap} should be exposed before starting extraction. {@code + * boolean} expected. Default value is {@code false}. + * + * <p>For each {@link SeekMap#getSeekPoints} call, the dummy {@link SeekMap} returns a single + * {@link SeekPoint} whose {@link SeekPoint#timeMicros} matches the requested timestamp, and + * whose {@link SeekPoint#position} is 0. + * + * @hide + */ + public static final String PARAMETER_EXPOSE_DUMMY_SEEKMAP = + "android.media.mediaparser.exposeDummySeekMap"; // Private constants. @@ -958,6 +979,7 @@ public final class MediaParser { private boolean mIncludeSupplementalData; private boolean mIgnoreTimestampOffset; private boolean mEagerlyExposeTrackType; + private boolean mExposeDummySeekMap; private String mParserName; private Extractor mExtractor; private ExtractorInput mExtractorInput; @@ -1017,6 +1039,9 @@ public final class MediaParser { if (PARAMETER_EAGERLY_EXPOSE_TRACKTYPE.equals(parameterName)) { mEagerlyExposeTrackType = (boolean) value; } + if (PARAMETER_EXPOSE_DUMMY_SEEKMAP.equals(parameterName)) { + mExposeDummySeekMap = (boolean) value; + } mParserParameters.put(parameterName, value); return this; } @@ -1078,11 +1103,10 @@ public final class MediaParser { } mExoDataReader.mInputReader = seekableInputReader; - // TODO: Apply parameters when creating extractor instances. if (mExtractor == null) { + mPendingExtractorInit = true; if (!mParserName.equals(PARSER_NAME_UNKNOWN)) { mExtractor = createExtractor(mParserName); - mExtractor.init(new ExtractorOutputAdapter()); } else { for (String parserName : mParserNamesPool) { Extractor extractor = createExtractor(parserName); @@ -1107,9 +1131,18 @@ public final class MediaParser { } if (mPendingExtractorInit) { + if (mExposeDummySeekMap) { + // We propagate the dummy seek map before initializing the extractor, in case the + // extractor initialization outputs a seek map. + mOutputConsumer.onSeekMapFound(SeekMap.DUMMY); + } mExtractor.init(new ExtractorOutputAdapter()); mPendingExtractorInit = false; + // We return after initialization to allow clients use any output information before + // starting actual extraction. + return true; } + if (isPendingSeek()) { mExtractor.seek(mPendingSeekPosition, mPendingSeekTimeMicros); removePendingSeek(); @@ -1683,6 +1716,28 @@ public final class MediaParser { } } + private static final class DummyExoPlayerSeekMap + implements com.google.android.exoplayer2.extractor.SeekMap { + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getDurationUs() { + return C.TIME_UNSET; + } + + @Override + public SeekPoints getSeekPoints(long timeUs) { + com.google.android.exoplayer2.extractor.SeekPoint seekPoint = + new com.google.android.exoplayer2.extractor.SeekPoint( + timeUs, /* position= */ 0); + return new SeekPoints(seekPoint, seekPoint); + } + } + /** Creates extractor instances. */ private interface ExtractorFactory { @@ -1923,6 +1978,7 @@ public final class MediaParser { expectedTypeByParameterName.put(PARAMETER_INCLUDE_SUPPLEMENTAL_DATA, Boolean.class); expectedTypeByParameterName.put(PARAMETER_IGNORE_TIMESTAMP_OFFSET, Boolean.class); expectedTypeByParameterName.put(PARAMETER_EAGERLY_EXPOSE_TRACKTYPE, Boolean.class); + expectedTypeByParameterName.put(PARAMETER_EXPOSE_DUMMY_SEEKMAP, Boolean.class); EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName); } } diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index a03b24cbbcf6..f970b598d6f8 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -233,10 +233,12 @@ void JMediaCodec::release() { } void JMediaCodec::releaseAsync() { - if (mCodec != NULL) { - mCodec->releaseAsync(); - } - mInitStatus = NO_INIT; + std::call_once(mAsyncReleaseFlag, [this] { + if (mCodec != NULL) { + mCodec->releaseAsync(new AMessage(kWhatAsyncReleaseComplete, this)); + } + mInitStatus = NO_INIT; + }); } JMediaCodec::~JMediaCodec() { @@ -1084,6 +1086,12 @@ void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) { handleFrameRenderedNotification(msg); break; } + case kWhatAsyncReleaseComplete: + { + mCodec.clear(); + mLooper->stop(); + break; + } default: TRESPASS(); } diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 5c34341a86a1..a58f9a74b563 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -173,6 +173,7 @@ private: enum { kWhatCallbackNotify, kWhatFrameRendered, + kWhatAsyncReleaseComplete, }; jclass mClass; @@ -185,6 +186,7 @@ private: bool mGraphicOutput{false}; bool mHasCryptoOrDescrambler{false}; std::once_flag mReleaseFlag; + std::once_flag mAsyncReleaseFlag; sp<AMessage> mCallbackNotification; sp<AMessage> mOnFrameRenderedNotification; diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 820615a6098d..48ff5c681853 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1256,7 +1256,7 @@ <string name="manage_notifications_history_text">History</string> <!-- Section title for notifications that have recently appeared. [CHAR LIMIT=40] --> - <string name="notification_section_header_incoming">Incoming</string> + <string name="notification_section_header_incoming">New</string> <!-- Section title for notifications that do not vibrate or make noise. [CHAR LIMIT=40] --> <string name="notification_section_header_gentle">Silent</string> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 97a73043aa08..7f78ddf2cf1c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -303,10 +303,10 @@ class Bubble implements BubbleViewProvider { mDotPath = info.dotPath; if (mExpandedView != null) { - mExpandedView.update(/* bubble */ this); + mExpandedView.update(this /* bubble */); } if (mIconView != null) { - mIconView.setRenderedBubble(/* bubble */ this); + mIconView.setRenderedBubble(this /* bubble */); } } @@ -548,13 +548,13 @@ class Bubble implements BubbleViewProvider { } private boolean shouldSuppressNotification() { - if (mEntry == null) return false; + if (mEntry == null) return true; return mEntry.getBubbleMetadata() != null && mEntry.getBubbleMetadata().isNotificationSuppressed(); } boolean shouldAutoExpand() { - if (mEntry == null) return false; + if (mEntry == null) return mShouldAutoExpand; Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand; } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index cf793f021a29..278f3e581a70 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -156,6 +156,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final ShadeController mShadeController; private final FloatingContentCoordinator mFloatingContentCoordinator; private final BubbleDataRepository mDataRepository; + private BubbleLogger mLogger = new BubbleLoggerImpl(); private BubbleData mBubbleData; private ScrimView mBubbleScrim; @@ -317,6 +318,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi BubbleDataRepository dataRepository, SysUiState sysUiState, INotificationManager notificationManager, + @Nullable IStatusBarService statusBarService, WindowManager windowManager) { dumpManager.registerDumpable(TAG, this); mContext = context; @@ -387,8 +389,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mSurfaceSynchronizer = synchronizer; mWindowManager = windowManager; - mBarService = IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mBarService = statusBarService == null + ? IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)) + : statusBarService; mBubbleScrim = new ScrimView(mContext); @@ -894,9 +898,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } void promoteBubbleFromOverflow(Bubble bubble) { + mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); bubble.setInflateSynchronously(mInflateSynchronously); - setIsBubble(bubble, /* isBubble */ true); - mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory); + bubble.setShouldAutoExpand(true); + bubble.markUpdatedAt(System.currentTimeMillis()); + setIsBubble(bubble, true /* isBubble */); } /** @@ -910,20 +916,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi Bubble bubble = mBubbleData.getBubbleInStackWithKey(key); if (bubble != null) { mBubbleData.setSelectedBubble(bubble); + mBubbleData.setExpanded(true); } else { bubble = mBubbleData.getOverflowBubbleWithKey(key); if (bubble != null) { - bubble.setShouldAutoExpand(true); promoteBubbleFromOverflow(bubble); } else if (entry.canBubble()) { // It can bubble but it's not -- it got aged out of the overflow before it // was dismissed or opened, make it a bubble again. - setIsBubble(entry, true); - updateBubble(entry, true /* suppressFlyout */, false /* showInShade */); + setIsBubble(entry, true /* isBubble */, true /* autoExpand */); } } - - mBubbleData.setExpanded(true); } /** @@ -967,13 +970,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) { - // Lazy init stack view when a bubble is created - ensureStackViewCreated(); // If this is an interruptive notif, mark that it's interrupted if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) { notif.setInterruption(); } - Bubble bubble = mBubbleData.getOrCreateBubble(notif); + Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); + inflateAndAdd(bubble, suppressFlyout, showInShade); + } + + void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) { + // Lazy init stack view when a bubble is created + ensureStackViewCreated(); bubble.setInflateSynchronously(mInflateSynchronously); bubble.inflate( b -> { @@ -1119,7 +1126,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } - private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble) { + private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble, + final boolean autoExpand) { Objects.requireNonNull(entry); if (isBubble) { entry.getSbn().getNotification().flags |= FLAG_BUBBLE; @@ -1127,7 +1135,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi entry.getSbn().getNotification().flags &= ~FLAG_BUBBLE; } try { - mBarService.onNotificationBubbleChanged(entry.getKey(), isBubble, 0); + int flags = 0; + if (autoExpand) { + flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE; + } + mBarService.onNotificationBubbleChanged(entry.getKey(), isBubble, flags); } catch (RemoteException e) { // Bad things have happened } @@ -1141,13 +1154,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi b.disable(FLAG_BUBBLE); } if (b.getEntry() != null) { - setIsBubble(b.getEntry(), isBubble); + // Updating the entry to be a bubble will trigger our normal update flow + setIsBubble(b.getEntry(), isBubble, b.shouldAutoExpand()); } else { - try { - mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0); - } catch (RemoteException e) { - // Bad things have happened - } + // If we have no entry to update, it's a persisted bubble so + // we need to add it to the stack ourselves + Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */); + inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */, + !bubble.shouldAutoExpand() /* showInShade */); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 996a5553b5b2..24d44d5cb291 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -115,7 +115,7 @@ public class BubbleData { /** Bubbles that aged out to overflow. */ private final List<Bubble> mOverflowBubbles; /** Bubbles that are being loaded but haven't been added to the stack just yet. */ - private final List<Bubble> mPendingBubbles; + private final HashMap<String, Bubble> mPendingBubbles; private Bubble mSelectedBubble; private boolean mShowingOverflow; private boolean mExpanded; @@ -151,7 +151,7 @@ public class BubbleData { mContext = context; mBubbles = new ArrayList<>(); mOverflowBubbles = new ArrayList<>(); - mPendingBubbles = new ArrayList<>(); + mPendingBubbles = new HashMap<>(); mStateChange = new Update(mBubbles, mOverflowBubbles); mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered); mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow); @@ -203,62 +203,46 @@ public class BubbleData { dispatchPendingChanges(); } - public void promoteBubbleFromOverflow(Bubble bubble, BubbleStackView stack, - BubbleIconFactory factory) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "promoteBubbleFromOverflow: " + bubble); - } - mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); - moveOverflowBubbleToPending(bubble); - // Preserve new order for next repack, which sorts by last updated time. - bubble.inflate( - b -> { - b.setShouldAutoExpand(true); - b.markUpdatedAt(mTimeSource.currentTimeMillis()); - notificationEntryUpdated(bubble, false /* suppressFlyout */, - true /* showInShade */); - }, - mContext, stack, factory, false /* skipInflation */); - } - void setShowingOverflow(boolean showingOverflow) { mShowingOverflow = showingOverflow; } - private void moveOverflowBubbleToPending(Bubble b) { - mOverflowBubbles.remove(b); - mPendingBubbles.add(b); - } - /** * Constructs a new bubble or returns an existing one. Does not add new bubbles to * bubble data, must go through {@link #notificationEntryUpdated(Bubble, boolean, boolean)} * for that. + * + * @param entry The notification entry to use, only null if it's a bubble being promoted from + * the overflow that was persisted over reboot. + * @param persistedBubble The bubble to use, only non-null if it's a bubble being promoted from + * the overflow that was persisted over reboot. */ - Bubble getOrCreateBubble(NotificationEntry entry) { - String key = entry.getKey(); - Bubble bubble = getBubbleInStackWithKey(entry.getKey()); - if (bubble != null) { - bubble.setEntry(entry); - } else { - bubble = getOverflowBubbleWithKey(key); - if (bubble != null) { - moveOverflowBubbleToPending(bubble); - bubble.setEntry(entry); - return bubble; - } - // Check for it in pending - for (int i = 0; i < mPendingBubbles.size(); i++) { - Bubble b = mPendingBubbles.get(i); - if (b.getKey().equals(entry.getKey())) { - b.setEntry(entry); - return b; - } + Bubble getOrCreateBubble(NotificationEntry entry, Bubble persistedBubble) { + String key = entry != null ? entry.getKey() : persistedBubble.getKey(); + Bubble bubbleToReturn = getBubbleInStackWithKey(key); + + if (bubbleToReturn == null) { + bubbleToReturn = getOverflowBubbleWithKey(key); + if (bubbleToReturn != null) { + // Promoting from overflow + mOverflowBubbles.remove(bubbleToReturn); + } else if (mPendingBubbles.containsKey(key)) { + // Update while it was pending + bubbleToReturn = mPendingBubbles.get(key); + } else if (entry != null) { + // New bubble + bubbleToReturn = new Bubble(entry, mSuppressionListener); + } else { + // Persisted bubble being promoted + bubbleToReturn = persistedBubble; } - bubble = new Bubble(entry, mSuppressionListener); - mPendingBubbles.add(bubble); } - return bubble; + + if (entry != null) { + bubbleToReturn.setEntry(entry); + } + mPendingBubbles.put(key, bubbleToReturn); + return bubbleToReturn; } /** @@ -270,7 +254,7 @@ public class BubbleData { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "notificationEntryUpdated: " + bubble); } - mPendingBubbles.remove(bubble); // No longer pending once we're here + mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); suppressFlyout |= bubble.getEntry() == null || !bubble.getEntry().getRanking().visuallyInterruptive(); @@ -408,10 +392,8 @@ public class BubbleData { Log.d(TAG, "doRemove: " + key); } // If it was pending remove it - for (int i = 0; i < mPendingBubbles.size(); i++) { - if (mPendingBubbles.get(i).getKey().equals(key)) { - mPendingBubbles.remove(mPendingBubbles.get(i)); - } + if (mPendingBubbles.containsKey(key)) { + mPendingBubbles.remove(key); } int indexToRemove = indexForKey(key); if (indexToRemove == -1) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index c3dcc0b3038c..db7980251daf 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -490,7 +490,7 @@ public class BubbleExpandedView extends LinearLayout { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null")); } - boolean isNew = mBubble == null; + boolean isNew = mBubble == null || didBackingContentChange(bubble); if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) { mBubble = bubble; mSettingsIcon.setContentDescription(getResources().getString( @@ -523,6 +523,12 @@ public class BubbleExpandedView extends LinearLayout { } } + private boolean didBackingContentChange(Bubble newBubble) { + boolean prevWasIntentBased = mBubble != null && mPendingIntent != null; + boolean newIsIntentBased = newBubble.getBubbleIntent() != null; + return prevWasIntentBased != newIsIntentBased; + } + /** * Lets activity view know it should be shown / populated with activity content. */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 95c8d08841df..978ae635274d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -79,7 +79,6 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ContrastColorUtil; -import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; @@ -292,20 +291,6 @@ public class BubbleStackView extends FrameLayout private ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater = this::updateSystemGestureExcludeRects; - private ViewClippingUtil.ClippingParameters mClippingParameters = - new ViewClippingUtil.ClippingParameters() { - - @Override - public boolean shouldFinish(View view) { - return false; - } - - @Override - public boolean isClippingEnablingAllowed(View view) { - return !mIsExpanded; - } - }; - /** Float property that 'drags' the flyout. */ private final FloatPropertyCompat mFlyoutCollapseProperty = new FloatPropertyCompat("FlyoutCollapseSpring") { @@ -1399,7 +1384,6 @@ public class BubbleStackView extends FrameLayout mBubbleContainer.addView(bubble.getIconView(), 0, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters); animateInFlyoutForBubble(bubble); requestUpdate(); logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__POSTED); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java index d1d07f6ba4ce..097932e5f447 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java @@ -20,6 +20,7 @@ import android.app.INotificationManager; import android.content.Context; import android.view.WindowManager; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubbleData; import com.android.systemui.bubbles.BubbleDataRepository; @@ -70,6 +71,7 @@ public interface BubbleModule { BubbleDataRepository bubbleDataRepository, SysUiState sysUiState, INotificationManager notifManager, + IStatusBarService statusBarService, WindowManager windowManager) { return new BubbleController( context, @@ -91,6 +93,7 @@ public interface BubbleModule { bubbleDataRepository, sysUiState, notifManager, + statusBarService, windowManager); } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java index 03b1ddca4648..7f7e1085d497 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java @@ -17,7 +17,6 @@ package com.android.systemui.pip; import android.animation.Animator; -import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.content.Context; @@ -27,7 +26,6 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Interpolators; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -76,12 +74,15 @@ public class PipAnimationController { || direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN; } + private final Interpolator mFastOutSlowInInterpolator; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private PipTransitionAnimator mCurrentAnimator; @Inject PipAnimationController(Context context, PipSurfaceTransactionHelper helper) { + mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_slow_in); mSurfaceTransactionHelper = helper; } @@ -103,11 +104,10 @@ public class PipAnimationController { } @SuppressWarnings("unchecked") - PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds, - Rect sourceHintRect) { + PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds) { if (mCurrentAnimator == null) { mCurrentAnimator = setupPipTransitionAnimator( - PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect)); + PipTransitionAnimator.ofBounds(leash, startBounds, endBounds)); } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA && mCurrentAnimator.isRunning()) { // If we are still animating the fade into pip, then just move the surface and ensure @@ -122,7 +122,7 @@ public class PipAnimationController { } else { mCurrentAnimator.cancel(); mCurrentAnimator = setupPipTransitionAnimator( - PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect)); + PipTransitionAnimator.ofBounds(leash, startBounds, endBounds)); } return mCurrentAnimator; } @@ -133,7 +133,7 @@ public class PipAnimationController { private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) { animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + animator.setInterpolator(mFastOutSlowInInterpolator); animator.setFloatValues(FRACTION_START, FRACTION_END); return animator; } @@ -331,7 +331,6 @@ public class PipAnimationController { @Override void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { getSurfaceTransactionHelper() - .resetScale(tx, leash, getDestinationBounds()) .crop(tx, leash, getDestinationBounds()) .round(tx, leash, shouldApplyCornerRadius()); tx.show(leash); @@ -347,46 +346,35 @@ public class PipAnimationController { } static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash, - Rect startValue, Rect endValue, Rect sourceHintRect) { - // Just for simplicity we'll interpolate between the source rect hint insets and empty - // insets to calculate the window crop - final Rect initialStartValue = new Rect(startValue); - final Rect sourceHintRectInsets = sourceHintRect != null - ? new Rect(sourceHintRect.left - startValue.left, - sourceHintRect.top - startValue.top, - startValue.right - sourceHintRect.right, - startValue.bottom - sourceHintRect.bottom) - : null; - final Rect sourceInsets = new Rect(0, 0, 0, 0); - + Rect startValue, Rect endValue) { // construct new Rect instances in case they are recycled return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS, endValue, new Rect(startValue), new Rect(endValue)) { - private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); - private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect()); + private final Rect mTmpRect = new Rect(); + + private int getCastedFractionValue(float start, float end, float fraction) { + return (int) (start * (1 - fraction) + end * fraction + .5f); + } @Override void applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction) { final Rect start = getStartValue(); final Rect end = getEndValue(); - Rect bounds = mRectEvaluator.evaluate(fraction, start, end); - setCurrentValue(bounds); + mTmpRect.set( + getCastedFractionValue(start.left, end.left, fraction), + getCastedFractionValue(start.top, end.top, fraction), + getCastedFractionValue(start.right, end.right, fraction), + getCastedFractionValue(start.bottom, end.bottom, fraction)); + setCurrentValue(mTmpRect); if (inScaleTransition()) { if (isOutPipDirection(getTransitionDirection())) { - getSurfaceTransactionHelper().scale(tx, leash, end, bounds); + getSurfaceTransactionHelper().scale(tx, leash, end, mTmpRect); } else { - getSurfaceTransactionHelper().scale(tx, leash, start, bounds); + getSurfaceTransactionHelper().scale(tx, leash, start, mTmpRect); } } else { - if (sourceHintRectInsets != null) { - Rect insets = mInsetsEvaluator.evaluate(fraction, sourceInsets, - sourceHintRectInsets); - getSurfaceTransactionHelper().scaleAndCrop(tx, leash, initialStartValue, - bounds, insets); - } else { - getSurfaceTransactionHelper().scale(tx, leash, start, bounds); - } + getSurfaceTransactionHelper().crop(tx, leash, mTmpRect); } tx.apply(); } @@ -402,11 +390,11 @@ public class PipAnimationController { @Override void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { + if (!inScaleTransition()) return; // NOTE: intentionally does not apply the transaction here. // this end transaction should get executed synchronously with the final // WindowContainerTransaction in task organizer - getSurfaceTransactionHelper() - .resetScale(tx, leash, getDestinationBounds()) + getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds()) .crop(tx, leash, getDestinationBounds()); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 26576940740f..0d3a16ec1028 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -296,14 +296,6 @@ public class PipBoundsHandler { */ public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, Rect outInsetBounds, int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) { - // Calculate the snap fraction of the current stack along the old movement bounds - final Rect postChangeStackBounds = new Rect(oldBounds); - final float snapFraction = getSnapFraction(postChangeStackBounds); - - // Update the display layout, note that we have to do this on every rotation even if we - // aren't in PIP since we need to update the display layout to get the right resources - mDisplayLayout.rotateTo(mContext.getResources(), toRotation); - // Bail early if the event is not sent to current {@link #mDisplayInfo} if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) { return false; @@ -320,6 +312,13 @@ public class PipBoundsHandler { return false; } + // Calculate the snap fraction of the current stack along the old movement bounds + final Rect postChangeStackBounds = new Rect(oldBounds); + final float snapFraction = getSnapFraction(postChangeStackBounds); + + // Update the display layout + mDisplayLayout.rotateTo(mContext.getResources(), toRotation); + // Populate the new {@link #mDisplayInfo}. // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation, // therefore, the width/height may require a swap first. diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java index 65ea887259be..fc41d2ea8862 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java @@ -44,7 +44,6 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf private final float[] mTmpFloat9 = new float[9]; private final RectF mTmpSourceRectF = new RectF(); private final RectF mTmpDestinationRectF = new RectF(); - private final Rect mTmpDestinationRect = new Rect(); @Inject public PipSurfaceTransactionHelper(Context context, ConfigurationController configController) { @@ -91,30 +90,7 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf mTmpDestinationRectF.set(destinationBounds); mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); tx.setMatrix(leash, mTmpTransform, mTmpFloat9) - .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top); - return this; - } - - /** - * Operates the scale (setMatrix) on a given transaction and leash - * @return same {@link PipSurfaceTransactionHelper} instance for method chaining - */ - PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash, - Rect sourceBounds, Rect destinationBounds, Rect insets) { - mTmpSourceRectF.set(sourceBounds); - mTmpDestinationRect.set(sourceBounds); - mTmpDestinationRect.inset(insets); - // Scale by the shortest edge and offset such that the top/left of the scaled inset source - // rect aligns with the top/left of the destination bounds - final float scale = sourceBounds.width() <= sourceBounds.height() - ? (float) destinationBounds.width() / sourceBounds.width() - : (float) destinationBounds.height() / sourceBounds.height(); - final float left = destinationBounds.left - insets.left * scale; - final float top = destinationBounds.top - insets.top * scale; - mTmpTransform.setScale(scale, scale); - tx.setMatrix(leash, mTmpTransform, mTmpFloat9) - .setWindowCrop(leash, mTmpDestinationRect) - .setPosition(leash, left, top); + .setPosition(leash, destinationBounds.left, destinationBounds.top); return this; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 42e0c56d6cc8..c6f144aa57a1 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -143,10 +143,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements case MSG_RESIZE_ANIMATE: { Rect currentBounds = (Rect) args.arg2; Rect toBounds = (Rect) args.arg3; - Rect sourceHintRect = (Rect) args.arg4; int duration = args.argi2; - animateResizePip(currentBounds, toBounds, sourceHintRect, - args.argi1 /* direction */, duration); + animateResizePip(currentBounds, toBounds, args.argi1 /* direction */, duration); if (updateBoundsCallback != null) { updateBoundsCallback.accept(toBounds); } @@ -296,8 +294,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements public void onTransactionReady(int id, SurfaceControl.Transaction t) { t.apply(); scheduleAnimateResizePip(mLastReportedBounds, destinationBounds, - null /* sourceHintRect */, direction, animationDurationMs, - null /* updateBoundsCallback */); + direction, animationDurationMs, null /* updateBoundsCallback */); mInPip = false; } }); @@ -360,8 +357,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { - final Rect sourceHintRect = getValidSourceHintRect(info, currentBounds); - scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect, + scheduleAnimateResizePip(currentBounds, destinationBounds, TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, null /* updateBoundsCallback */); } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { @@ -372,21 +368,6 @@ public class PipTaskOrganizer extends TaskOrganizer implements } } - /** - * Returns the source hint rect if it is valid (if provided and is contained by the current - * task bounds). - */ - private Rect getValidSourceHintRect(ActivityManager.RunningTaskInfo info, Rect sourceBounds) { - final Rect sourceHintRect = info.pictureInPictureParams != null - && info.pictureInPictureParams.hasSourceBoundsHint() - ? info.pictureInPictureParams.getSourceRectHint() - : null; - if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) { - return sourceHintRect; - } - return null; - } - private void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) { // If we are fading the PIP in, then we should move the pip to the final location as // soon as possible, but set the alpha immediately since the transaction can take a @@ -571,13 +552,13 @@ public class PipTaskOrganizer extends TaskOrganizer implements Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred"); return; } - scheduleAnimateResizePip(mLastReportedBounds, toBounds, null /* sourceHintRect */, + scheduleAnimateResizePip(mLastReportedBounds, toBounds, TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback); } private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds, - Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, - int durationMs, Consumer<Rect> updateBoundsCallback) { + @PipAnimationController.TransitionDirection int direction, int durationMs, + Consumer<Rect> updateBoundsCallback) { if (!mInPip) { // can be initiated in other component, ignore if we are no longer in PIP return; @@ -587,7 +568,6 @@ public class PipTaskOrganizer extends TaskOrganizer implements args.arg1 = updateBoundsCallback; args.arg2 = currentBounds; args.arg3 = destinationBounds; - args.arg4 = sourceHintRect; args.argi1 = direction; args.argi2 = durationMs; mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args)); @@ -687,8 +667,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements } final Rect destinationBounds = new Rect(originalBounds); destinationBounds.offset(xOffset, yOffset); - animateResizePip(originalBounds, destinationBounds, null /* sourceHintRect */, - TRANSITION_DIRECTION_SAME, durationMs); + animateResizePip(originalBounds, destinationBounds, TRANSITION_DIRECTION_SAME, durationMs); } private void resizePip(Rect destinationBounds) { @@ -766,7 +745,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements WindowOrganizer.applyTransaction(wct); } - private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect, + private void animateResizePip(Rect currentBounds, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, int durationMs) { if (Looper.myLooper() != mUpdateHandler.getLooper()) { throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of " @@ -778,7 +757,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements return; } mPipAnimationController - .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect) + .getAnimator(mLeash, currentBounds, destinationBounds) .setTransitionDirection(direction) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(durationMs) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 26e80adafd3d..bf53a2f113e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -59,6 +59,7 @@ import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.RectF; import android.hardware.display.DisplayManager; import android.inputmethodservice.InputMethodService; import android.net.Uri; @@ -83,6 +84,7 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.WindowInsetsController.Appearance; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; @@ -217,11 +219,12 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback * original handle hidden and we'll flip the visibilities once the * {@link #mTasksFrozenListener} fires */ - private NavigationHandle mOrientationHandle; + private VerticalNavigationHandle mOrientationHandle; private WindowManager.LayoutParams mOrientationParams; private int mStartingQuickSwitchRotation; private int mCurrentRotation; private boolean mFixedRotationEnabled; + private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener; /** Only for default display */ @Nullable @@ -519,6 +522,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback getContext().getSystemService(DisplayManager.class).unregisterDisplayListener(this); getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener); mWindowManager.removeView(mOrientationHandle); + mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener( + mOrientationHandleGlobalLayoutListener); } } @@ -573,6 +578,20 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback PixelFormat.TRANSLUCENT); mWindowManager.addView(mOrientationHandle, mOrientationParams); mOrientationHandle.setVisibility(View.GONE); + mOrientationHandleGlobalLayoutListener = + () -> { + if (mStartingQuickSwitchRotation == -1) { + return; + } + + RectF boundsOnScreen = mOrientationHandle.computeHomeHandleBounds(); + mOrientationHandle.mapRectFromViewToScreenCoords(boundsOnScreen, true); + Rect boundsRounded = new Rect(); + boundsOnScreen.roundOut(boundsRounded); + mNavigationBarView.setOrientedHandleSamplingRegion(boundsRounded); + }; + mOrientationHandle.getViewTreeObserver().addOnGlobalLayoutListener( + mOrientationHandleGlobalLayoutListener); } private void orientSecondaryHomeHandle() { @@ -621,6 +640,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } if (mNavigationBarView != null) { mNavigationBarView.setVisibility(View.VISIBLE); + mNavigationBarView.setOrientedHandleSamplingRegion(null); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 6b37ac317cdd..4821d8c46ed4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -35,6 +35,7 @@ import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.DrawableRes; +import android.annotation.Nullable; import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; @@ -158,6 +159,14 @@ public class NavigationBarView extends FrameLayout implements */ private ScreenPinningNotify mScreenPinningNotify; private Rect mSamplingBounds = new Rect(); + /** + * When quickswitching between apps of different orientations, we draw a secondary home handle + * in the position of the first app's orientation. This rect represents the region of that + * home handle so we can apply the correct light/dark luma on that. + * @see {@link NavigationBarFragment#mOrientationHandle} + */ + @Nullable + private Rect mOrientedHandleSamplingRegion; private class NavTransitionListener implements TransitionListener { private boolean mBackTransitioning; @@ -327,6 +336,10 @@ public class NavigationBarView extends FrameLayout implements @Override public Rect getSampledRegion(View sampledView) { + if (mOrientedHandleSamplingRegion != null) { + return mOrientedHandleSamplingRegion; + } + updateSamplingRect(); return mSamplingBounds; } @@ -897,6 +910,11 @@ public class NavigationBarView extends FrameLayout implements } } + void setOrientedHandleSamplingRegion(Rect orientedHandleSamplingRegion) { + mOrientedHandleSamplingRegion = orientedHandleSamplingRegion; + mRegionSamplingHelper.updateSamplingRect(); + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); @@ -1190,6 +1208,8 @@ public class NavigationBarView extends FrameLayout implements mIsVertical ? "true" : "false", getLightTransitionsController().getCurrentDarkIntensity())); + pw.println(" mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion); + dumpButton(pw, "back", getBackButton()); dumpButton(pw, "home", getHomeButton()); dumpButton(pw, "rcnt", getRecentsButton()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java index a15ca9532a88..0cdf1d32d6a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java @@ -18,12 +18,14 @@ package com.android.systemui.statusbar.phone; import android.content.Context; import android.graphics.Canvas; +import android.graphics.RectF; import com.android.systemui.R; /** Temporarily shown view when using QuickSwitch to switch between apps of different rotations */ public class VerticalNavigationHandle extends NavigationHandle { private final int mWidth; + private final RectF mTmpBoundsRectF = new RectF(); public VerticalNavigationHandle(Context context) { super(context); @@ -32,16 +34,21 @@ public class VerticalNavigationHandle extends NavigationHandle { @Override protected void onDraw(Canvas canvas) { + canvas.drawRoundRect(computeHomeHandleBounds(), mRadius, mRadius, mPaint); + } + + RectF computeHomeHandleBounds() { int left; int top; int bottom; int right; - + int topStart = getLocationOnScreen()[1]; int radiusOffset = mRadius * 2; right = getWidth() - mBottom; - top = getHeight() / 2 - (mWidth / 2); /* (height of screen / 2) - (height of bar / 2) */ + top = getHeight() / 2 - (mWidth / 2) - (topStart / 2); left = getWidth() - mBottom - radiusOffset; - bottom = getHeight() / 2 + (mWidth / 2); - canvas.drawRoundRect(left, top, right, bottom, mRadius, mRadius, mPaint); + bottom = top + mWidth; + mTmpBoundsRectF.set(left, top, right, bottom); + return mTmpBoundsRectF; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index c89f6c2597d0..472b121f7b95 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -44,7 +44,6 @@ import android.app.IActivityManager; import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; -import android.content.res.Resources; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.face.FaceManager; import android.os.Handler; @@ -59,7 +58,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; -import com.android.systemui.SystemUIFactory; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; @@ -70,20 +69,16 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; -import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; @@ -93,7 +88,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.util.InjectionInflationController; import com.google.common.collect.ImmutableList; @@ -173,23 +167,18 @@ public class BubbleControllerTest extends SysuiTestCase { @Mock ColorExtractor.GradientColors mGradientColors; @Mock - private Resources mResources; - @Mock private ShadeController mShadeController; @Mock - private NotificationRowComponent mNotificationRowComponent; - @Mock private NotifPipeline mNotifPipeline; @Mock private FeatureFlags mFeatureFlagsOldPipeline; @Mock private DumpManager mDumpManager; @Mock - private LockscreenLockIconController mLockIconController; - @Mock private NotificationShadeWindowView mNotificationShadeWindowView; + @Mock + private IStatusBarService mStatusBarService; - private SuperStatusBarViewFactory mSuperStatusBarViewFactory; private BubbleData mBubbleData; private TestableLooper mTestableLooper; @@ -203,23 +192,6 @@ public class BubbleControllerTest extends SysuiTestCase { mContext.addMockSystemService(FaceManager.class, mFaceManager); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); - mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext, - new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()), - new NotificationRowComponent.Builder() { - @Override - public NotificationRowComponent.Builder activatableNotificationView( - ActivatableNotificationView view) { - return this; - } - - @Override - public NotificationRowComponent build() { - return mNotificationRowComponent; - } - }, - mLockIconController); - - // Bubbles get added to status bar window view mNotificationShadeWindowController = new NotificationShadeWindowController(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardBypassController, mColorExtractor, @@ -282,6 +254,7 @@ public class BubbleControllerTest extends SysuiTestCase { mDataRepository, mSysUiState, mock(INotificationManager.class), + mStatusBarService, mWindowManager); mBubbleController.setExpandListener(mBubbleExpandListener); @@ -327,7 +300,7 @@ public class BubbleControllerTest extends SysuiTestCase { } @Test - public void testPromoteBubble_autoExpand() { + public void testPromoteBubble_autoExpand() throws Exception { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.removeBubble( @@ -337,13 +310,19 @@ public class BubbleControllerTest extends SysuiTestCase { assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b)); verify(mNotificationEntryManager, never()).performRemoveNotification( eq(mRow.getEntry().getSbn()), anyInt()); - assertFalse(mRow.getEntry().isBubble()); + assertThat(mRow.getEntry().isBubble()).isFalse(); Bubble b2 = mBubbleData.getBubbleInStackWithKey(mRow2.getEntry().getKey()); assertThat(mBubbleData.getSelectedBubble()).isEqualTo(b2); mBubbleController.promoteBubbleFromOverflow(b); - assertThat(mBubbleData.getSelectedBubble()).isEqualTo(b); + + assertThat(b.isBubble()).isTrue(); + assertThat(b.shouldAutoExpand()).isTrue(); + int flags = Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE + | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + verify(mStatusBarService, times(1)).onNotificationBubbleChanged( + eq(b.getKey()), eq(true), eq(flags)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index 8224c88e6c75..17022da3ecde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -278,7 +278,7 @@ public class BubbleDataTest extends SysuiTestCase { assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_AGED); assertOverflowChangedTo(ImmutableList.of(mBubbleA1)); - Bubble bubbleA1 = mBubbleData.getOrCreateBubble(mEntryA1); + Bubble bubbleA1 = mBubbleData.getOrCreateBubble(mEntryA1, null /* persistedBubble */); bubbleA1.markUpdatedAt(7000L); mBubbleData.notificationEntryUpdated(bubbleA1, false /* suppressFlyout*/, true /* showInShade */); @@ -890,7 +890,7 @@ public class BubbleDataTest extends SysuiTestCase { private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) { setPostTime(entry, postTime); // BubbleController calls this: - Bubble b = mBubbleData.getOrCreateBubble(entry); + Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */); // And then this mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/, true /* showInShade */); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index ead95ca1665e..47cd9bca2861 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -55,7 +55,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; -import com.android.systemui.SystemUIFactory; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; @@ -64,14 +64,12 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; @@ -87,7 +85,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.util.InjectionInflationController; import org.junit.Before; import org.junit.Test; @@ -174,8 +171,9 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { private DumpManager mDumpManager; @Mock private LockscreenLockIconController mLockIconController; + @Mock + private IStatusBarService mStatusBarService; - private SuperStatusBarViewFactory mSuperStatusBarViewFactory; private BubbleData mBubbleData; private TestableLooper mTestableLooper; @@ -189,22 +187,6 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mContext.addMockSystemService(FaceManager.class, mFaceManager); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); - mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext, - new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()), - new NotificationRowComponent.Builder() { - @Override - public NotificationRowComponent.Builder activatableNotificationView( - ActivatableNotificationView view) { - return this; - } - - @Override - public NotificationRowComponent build() { - return mNotificationRowComponent; - } - }, - mLockIconController); - // Bubbles get added to status bar window view mNotificationShadeWindowController = new NotificationShadeWindowController(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, @@ -257,6 +239,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mDataRepository, mSysUiState, mock(INotificationManager.class), + mStatusBarService, mWindowManager); mBubbleController.addNotifCallback(mNotifCallback); mBubbleController.setExpandListener(mBubbleExpandListener); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java index bdb79449ea2a..ab49134ee8c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java @@ -20,6 +20,7 @@ import android.app.INotificationManager; import android.content.Context; import android.view.WindowManager; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -59,13 +60,14 @@ public class TestableBubbleController extends BubbleController { BubbleDataRepository dataRepository, SysUiState sysUiState, INotificationManager notificationManager, + IStatusBarService statusBarService, WindowManager windowManager) { super(context, notificationShadeWindowController, statusBarStateController, shadeController, data, Runnable::run, configurationController, interruptionStateProvider, zenModeController, lockscreenUserManager, groupManager, entryManager, notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, - dataRepository, sysUiState, notificationManager, + dataRepository, sysUiState, notificationManager, statusBarService, windowManager); setInflateSynchronously(true); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java index 536cae4380c4..b7a2633d0d36 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java @@ -82,7 +82,7 @@ public class PipAnimationControllerTest extends SysuiTestCase { @Test public void getAnimator_withBounds_returnBoundsAnimator() { final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, new Rect(), new Rect(), null); + .getAnimator(mLeash, new Rect(), new Rect()); assertEquals("Expect ANIM_TYPE_BOUNDS animation", animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS); @@ -94,12 +94,12 @@ public class PipAnimationControllerTest extends SysuiTestCase { final Rect endValue1 = new Rect(100, 100, 200, 200); final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue1, null); + .getAnimator(mLeash, startValue, endValue1); oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); oldAnimator.start(); final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue2, null); + .getAnimator(mLeash, startValue, endValue2); assertEquals("getAnimator with same type returns same animator", oldAnimator, newAnimator); @@ -129,7 +129,7 @@ public class PipAnimationControllerTest extends SysuiTestCase { final Rect endValue1 = new Rect(100, 100, 200, 200); final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue1, null); + .getAnimator(mLeash, startValue, endValue1); animator.updateEndValue(endValue2); @@ -141,7 +141,7 @@ public class PipAnimationControllerTest extends SysuiTestCase { final Rect startValue = new Rect(0, 0, 100, 100); final Rect endValue = new Rect(100, 100, 200, 200); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue, null); + .getAnimator(mLeash, startValue, endValue); animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); animator.setPipAnimationCallback(mPipAnimationCallback); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5585e9816783..0a8155a819b3 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4015,7 +4015,7 @@ public class NotificationManagerService extends SystemService { private void cancelNotificationFromListenerLocked(ManagedServiceInfo info, int callingUid, int callingPid, String pkg, String tag, int id, int userId) { cancelNotification(callingUid, callingPid, pkg, tag, id, 0, - FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, + FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE, true, userId, REASON_LISTENER_CANCEL, info); } @@ -6248,6 +6248,13 @@ public class NotificationManagerService extends SystemService { mUsageStats.registerClickedByUser(r); } + if (mReason == REASON_LISTENER_CANCEL + && (r.getNotification().flags & FLAG_BUBBLE) != 0) { + mNotificationDelegate.onBubbleNotificationSuppressionChanged( + r.getKey(), /* suppressed */ true); + return; + } + if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) { return; } diff --git a/services/core/java/com/android/server/wm/AlertWindowNotification.java b/services/core/java/com/android/server/wm/AlertWindowNotification.java index 7b511ec20541..fde036950245 100644 --- a/services/core/java/com/android/server/wm/AlertWindowNotification.java +++ b/services/core/java/com/android/server/wm/AlertWindowNotification.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; +import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.content.Context.NOTIFICATION_SERVICE; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -139,7 +140,8 @@ class AlertWindowNotification { Uri.fromParts("package", packageName, null)); intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); // Calls into activity manager... - return PendingIntent.getActivity(context, mRequestCode, intent, FLAG_CANCEL_CURRENT); + return PendingIntent.getActivity(context, mRequestCode, intent, + FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE); } private void createNotificationChannel(Context context, String appName) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a5b94b327699..807cd9c0d412 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3506,22 +3506,21 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * doesn't support IME/system decorations. * * @param target current IME target. - * @return {@link WindowState} that can host IME. + * @return {@link InsetsControlTarget} that can host IME. */ - WindowState getImeHostOrFallback(WindowState target) { + InsetsControlTarget getImeHostOrFallback(WindowState target) { if (target != null && target.getDisplayContent().canShowIme()) { return target; } return getImeFallback(); } - WindowState getImeFallback() { - + InsetsControlTarget getImeFallback() { // host is in non-default display that doesn't support system decor, default to - // default display's StatusBar to control IME. - // TODO: (b/148234093)find a better host OR control IME animation/visibility directly - // because it won't work when statusbar isn't available. - return mWmService.getDefaultDisplayContentLocked().getDisplayPolicy().getStatusBar(); + // default display's StatusBar to control IME (when available), else let system control it. + WindowState statusBar = + mWmService.getDefaultDisplayContentLocked().getDisplayPolicy().getStatusBar(); + return statusBar != null ? statusBar : mRemoteInsetsControlTarget; } boolean canShowIme() { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a8f3ae5f24a3..0590288a7f8b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7623,8 +7623,12 @@ public class WindowManagerService extends IWindowManager.Stub if (imeTarget == null) { return; } - imeTarget = imeTarget.getImeControlTarget(); - imeTarget.getDisplayContent().getInsetsStateController().getImeSourceProvider() + imeTarget = imeTarget.getImeControlTarget().getWindow(); + // If InsetsControlTarget doesn't have a window, its using remoteControlTarget which + // is controlled by default display + final DisplayContent dc = imeTarget != null + ? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked(); + dc.getInsetsStateController().getImeSourceProvider() .scheduleShowImePostLayout(imeTarget); } } @@ -7637,7 +7641,9 @@ public class WindowManagerService extends IWindowManager.Stub // The target window no longer exists. return; } - final DisplayContent dc = imeTarget.getImeControlTarget().getDisplayContent(); + imeTarget = imeTarget.getImeControlTarget().getWindow(); + final DisplayContent dc = imeTarget != null + ? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked(); // If there was a pending IME show(), reset it as IME has been // requested to be hidden. dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 36232e13fcf1..fe3ee50c34c5 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5404,10 +5404,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} is unknown, * use {@link DisplayContent#getImeControlTarget()} instead. * - * @return {@link WindowState} of host that controls the IME. + * @return {@link InsetsControlTarget} of host that controls the IME. * When window is doesn't have a parent, it is returned as-is. */ - WindowState getImeControlTarget() { + InsetsControlTarget getImeControlTarget() { final DisplayContent dc = getDisplayContent(); final WindowState parentWindow = dc.getParentWindow(); diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk Binary files differindex 8056e0bf6e50..211e064399a8 100644 --- a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk +++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index 53c9bb22e752..9ca84d33998c 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -121,7 +121,7 @@ public class AppIntegrityManagerServiceImplTest { private static final String INSTALLER_SHA256 = "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227"; private static final String SOURCE_STAMP_CERTIFICATE_HASH = - "681B0E56A796350C08647352A4DB800CC44B2ADC8F4C72FA350BD05D4D50264D"; + "C6E737809CEF2B08CC6694892215F82A5E8FBC3C2A0F6212770310B90622D2D9"; private static final String DUMMY_APP_TWO_CERTS_CERT_1 = "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94"; 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 de9b77c68336..ae22b2be0c69 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5455,25 +5455,49 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testCancelNotificationsFromListener_ignoresBubbles() throws Exception { - final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel); - final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel); - nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE; + public void testCancelNotificationsFromListener_cancelsNonBubble() throws Exception { + // Add non-bubble notif + final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(nr); - mService.addNotification(nrNormal); - mService.addNotification(nrBubble); + // Cancel via listener + String[] keys = {nr.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + + // Notif not active anymore + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(0, notifs.length); + assertEquals(0, mService.getNotificationRecordCount()); + // Cancel event is logged + assertEquals(1, mNotificationRecordLogger.numCalls()); + assertEquals(NotificationRecordLogger.NotificationCancelledEvent + .NOTIFICATION_CANCEL_LISTENER_CANCEL, mNotificationRecordLogger.event(0)); + } + + @Test + public void testCancelNotificationsFromListener_suppressesBubble() throws Exception { + // Add bubble notif + setUpPrefsForBubbles(PKG, mUid, + true /* global */, + BUBBLE_PREFERENCE_ALL /* app */, + true /* channel */); + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag"); - String[] keys = {nrNormal.getSbn().getKey(), nrBubble.getSbn().getKey()}; + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + // Cancel via listener + String[] keys = {nr.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); + // Bubble notif active and suppressed StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); assertEquals(1, notifs.length); assertEquals(1, mService.getNotificationRecordCount()); - - assertEquals(1, mNotificationRecordLogger.numCalls()); - assertEquals(NotificationRecordLogger.NotificationCancelledEvent - .NOTIFICATION_CANCEL_LISTENER_CANCEL, mNotificationRecordLogger.event(0)); + assertTrue(notifs[0].getNotification().getBubbleMetadata().isNotificationSuppressed()); } @Test |