diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 1dbd69c67831c..24fe0638b0910 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -77,7 +77,7 @@ interface IStatusBarService void onNotificationSmartReplySent(in String key, in int replyIndex, in CharSequence reply, in int notificationLocation, boolean modifiedBeforeSending); void onNotificationSettingsViewed(String key); - void onNotificationBubbleChanged(String key, boolean isBubble); + void onNotificationBubbleChanged(String key, boolean isBubble, int flags); void onBubbleNotificationSuppressionChanged(String key, boolean isSuppressed); void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName); void clearInlineReplyUriPermissions(String key); diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml index 425801991927e..ec54091e5a20b 100644 --- a/core/res/res/layout/notification_material_action_list.xml +++ b/core/res/res/layout/notification_material_action_list.xml @@ -20,16 +20,34 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/notification_action_list_margin_top" android:layout_gravity="bottom"> - - - + + + + + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ef1e8b74b05fb..69862b2e2820e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3495,6 +3495,7 @@ + diff --git a/packages/SystemUI/res/drawable/ic_create_bubble.xml b/packages/SystemUI/res/drawable/ic_create_bubble.xml index 1947f58f8f5e6..d58e9a347a2ff 100644 --- a/packages/SystemUI/res/drawable/ic_create_bubble.xml +++ b/packages/SystemUI/res/drawable/ic_create_bubble.xml @@ -14,16 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_stop_bubble.xml b/packages/SystemUI/res/drawable/ic_stop_bubble.xml new file mode 100644 index 0000000000000..11bc741a4f178 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_stop_bubble.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 9d885fd3c207e..878a88452f438 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -17,6 +17,8 @@ package com.android.systemui.bubbles; import static android.app.Notification.FLAG_BUBBLE; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; @@ -30,7 +32,6 @@ import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER; -import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_EXPERIMENTS; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -43,6 +44,9 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.UserIdInt; import android.app.ActivityManager.RunningTaskInfo; +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; @@ -83,6 +87,7 @@ import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; +import com.android.systemui.statusbar.notification.NotificationChannelHelper; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotifCollection; @@ -169,6 +174,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final NotificationShadeWindowController mNotificationShadeWindowController; private final ZenModeController mZenModeController; private StatusBarStateListener mStatusBarStateListener; + private INotificationManager mINotificationManager; // Callback that updates BubbleOverflowActivity on data change. @Nullable private Runnable mOverflowCallback = null; @@ -293,11 +299,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi FeatureFlags featureFlags, DumpManager dumpManager, FloatingContentCoordinator floatingContentCoordinator, - SysUiState sysUiState) { + SysUiState sysUiState, + INotificationManager notificationManager) { this(context, notificationShadeWindowController, statusBarStateController, shadeController, data, null /* synchronizer */, configurationController, interruptionStateProvider, zenModeController, notifUserManager, groupManager, entryManager, - notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState); + notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState, + notificationManager); } /** @@ -319,7 +327,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi FeatureFlags featureFlags, DumpManager dumpManager, FloatingContentCoordinator floatingContentCoordinator, - SysUiState sysUiState) { + SysUiState sysUiState, + INotificationManager notificationManager) { dumpManager.registerDumpable(TAG, this); mContext = context; mShadeController = shadeController; @@ -327,6 +336,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mNotifUserManager = notifUserManager; mZenModeController = zenModeController; mFloatingContentCoordinator = floatingContentCoordinator; + mINotificationManager = notificationManager; mZenModeController.addCallback(new ZenModeController.Callback() { @Override public void onZenChanged(int zen) { @@ -829,37 +839,43 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * This method will collapse the shade, create the bubble without a flyout or dot, and suppress * the notification from appearing in the shade. * - * @param entry the notification to show as a bubble. + * @param entry the notification to change bubble state for. + * @param shouldBubble whether the notification should show as a bubble or not. */ - public void onUserCreatedBubbleFromNotification(NotificationEntry entry) { - if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) { - Log.d(TAG, "onUserCreatedBubble: " + entry.getKey()); + public void onUserChangedBubble(NotificationEntry entry, boolean shouldBubble) { + NotificationChannel channel = entry.getChannel(); + final String appPkg = entry.getSbn().getPackageName(); + final int appUid = entry.getSbn().getUid(); + if (channel == null || appPkg == null) { + return; } - mShadeController.collapsePanel(true); - entry.setFlagBubble(true); - updateBubble(entry, true /* suppressFlyout */, false /* showInShade */); - mUserCreatedBubbles.add(entry.getKey()); - mUserBlockedBubbles.remove(entry.getKey()); - } - /** - * Called when a user has indicated that an active notification appearing as a bubble should - * no longer be shown as a bubble. - * - * @param entry the notification to no longer show as a bubble. - */ - public void onUserDemotedBubbleFromNotification(NotificationEntry entry) { - if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) { - Log.d(TAG, "onUserDemotedBubble: " + entry.getKey()); + // Update the state in NotificationManagerService + try { + int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + mBarService.onNotificationBubbleChanged(entry.getKey(), shouldBubble, flags); + } catch (RemoteException e) { } - entry.setFlagBubble(false); - removeBubble(entry, DISMISS_BLOCKED); - mUserCreatedBubbles.remove(entry.getKey()); - if (BubbleExperimentConfig.isPackageWhitelistedToAutoBubble( - mContext, entry.getSbn().getPackageName())) { - // This package is whitelist but user demoted the bubble, let's save it so we don't - // auto-bubble for the whitelist again. - mUserBlockedBubbles.add(entry.getKey()); + + // Change the settings + channel = NotificationChannelHelper.createConversationChannelIfNeeded(mContext, + mINotificationManager, entry, channel); + channel.setAllowBubbles(shouldBubble); + try { + int currentPref = mINotificationManager.getBubblePreferenceForPackage(appPkg, appUid); + if (shouldBubble && currentPref == BUBBLE_PREFERENCE_NONE) { + mINotificationManager.setBubblesAllowed(appPkg, appUid, BUBBLE_PREFERENCE_SELECTED); + } + mINotificationManager.updateNotificationChannelForPackage(appPkg, appUid, channel); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + + if (shouldBubble) { + mShadeController.collapsePanel(true); + if (entry.getRow() != null) { + entry.getRow().updateBubbleButton(); + } } } @@ -1007,14 +1023,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } else { // Update the flag for SysUI bubble.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE; + if (bubble.getEntry().getRow() != null) { + bubble.getEntry().getRow().updateBubbleButton(); + } - // Make sure NoMan knows it's not a bubble anymore so anyone querying it - // will get right result back + // Update the state in NotificationManagerService try { mBarService.onNotificationBubbleChanged(bubble.getKey(), - false /* isBubble */); + false /* isBubble */, 0 /* flags */); } catch (RemoteException e) { - // Bad things have happened } } 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 e84e932c9e61d..72d646e0554d5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java @@ -16,6 +16,7 @@ package com.android.systemui.bubbles.dagger; +import android.app.INotificationManager; import android.content.Context; import com.android.systemui.bubbles.BubbleController; @@ -64,14 +65,15 @@ public interface BubbleModule { FeatureFlags featureFlags, DumpManager dumpManager, FloatingContentCoordinator floatingContentCoordinator, - SysUiState sysUiState) { + SysUiState sysUiState, + INotificationManager notifManager) { return new BubbleController( context, notificationShadeWindowController, statusBarStateController, shadeController, data, - /* synchronizer */null, + null /* synchronizer */, configurationController, interruptionStateProvider, zenModeController, @@ -82,6 +84,7 @@ public interface BubbleModule { featureFlags, dumpManager, floatingContentCoordinator, - sysUiState); + sysUiState, + notifManager); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java new file mode 100644 index 0000000000000..ff945d15a4ed6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.content.Context; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.systemui.R; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * Helps SystemUI create notification channels. + */ +public class NotificationChannelHelper { + private static final String TAG = "NotificationChannelHelper"; + + /** Creates a conversation channel based on the shortcut info or notification title. */ + public static NotificationChannel createConversationChannelIfNeeded( + Context context, + INotificationManager notificationManager, + NotificationEntry entry, + NotificationChannel channel) { + if (!TextUtils.isEmpty(channel.getConversationId())) { + return channel; + } + final String conversationId = entry.getSbn().getShortcutId(context); + final String pkg = entry.getSbn().getPackageName(); + final int appUid = entry.getSbn().getUid(); + if (TextUtils.isEmpty(conversationId) || TextUtils.isEmpty(pkg)) { + return channel; + } + + String name; + if (entry.getRanking().getShortcutInfo() != null) { + name = entry.getRanking().getShortcutInfo().getShortLabel().toString(); + } else { + Bundle extras = entry.getSbn().getNotification().extras; + String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE); + if (TextUtils.isEmpty(nameString)) { + nameString = extras.getString(Notification.EXTRA_TITLE); + } + name = nameString; + } + + // If this channel is not already a customized conversation channel, create + // a custom channel + try { + // TODO: When shortcuts are enforced remove this and use the shortcut label for naming + channel.setName(context.getString( + R.string.notification_summary_message_format, + name, channel.getName())); + notificationManager.createConversationNotificationChannelForPackage( + pkg, appUid, entry.getSbn().getKey(), channel, + conversationId); + channel = notificationManager.getConversationNotificationChannel( + context.getOpPackageName(), UserHandle.getUserId(appUid), pkg, + channel.getId(), false, conversationId); + } catch (RemoteException e) { + Slog.e(TAG, "Could not create conversation channel", e); + } + return channel; + } +} 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 5c578dfc57441..ba060636500a9 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 @@ -70,6 +70,7 @@ import com.android.internal.widget.CachingIconView; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -579,6 +580,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + /** Call when bubble state has changed and the button on the notification should be updated. */ + public void updateBubbleButton() { + for (NotificationContentView l : mLayouts) { + l.updateBubbleButton(mEntry); + } + } + @VisibleForTesting void updateShelfIconColor() { StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon(); @@ -1086,6 +1094,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateClickAndFocus(); } + /** The click listener for the bubble button. */ + public View.OnClickListener getBubbleClickListener() { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + Dependency.get(BubbleController.class) + .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */); + mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */); + } + }; + } + private void updateClickAndFocus() { boolean normalChild = !isChildInGroup() || isGroupExpanded(); boolean clickable = mOnClickListener != null && normalChild; @@ -1267,7 +1287,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mNotificationColor; } - private void updateNotificationColor() { + public void updateNotificationColor() { Configuration currentConfig = getResources().getConfiguration(); boolean nightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; @@ -1613,6 +1633,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mFalsingManager = falsingManager; mStatusbarStateController = statusBarStateController; mPeopleNotificationIdentifier = peopleNotificationIdentifier; + for (NotificationContentView l : mLayouts) { + l.setPeopleNotificationIdentifier(mPeopleNotificationIdentifier); + } } private void initDimens() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 3c3f1b21fb3c0..bd1745eaa0284 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -16,13 +16,18 @@ package com.android.systemui.statusbar.notification.row; + +import static android.provider.Settings.Global.NOTIFICATION_BUBBLES; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Build; +import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.ArraySet; @@ -47,6 +52,7 @@ import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -118,6 +124,7 @@ public class NotificationContentView extends FrameLayout { private NotificationGroupManager mGroupManager; private RemoteInputController mRemoteInputController; private Runnable mExpandedVisibleListener; + private PeopleNotificationIdentifier mPeopleIdentifier; /** * List of listeners for when content views become inactive (i.e. not the showing view). */ @@ -454,6 +461,9 @@ public class NotificationContentView extends FrameLayout { mExpandedChild = child; mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child, mContainingNotification); + if (mContainingNotification != null) { + applyBubbleAction(mExpandedChild, mContainingNotification.getEntry()); + } } /** @@ -493,6 +503,9 @@ public class NotificationContentView extends FrameLayout { mHeadsUpChild = child; mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child, mContainingNotification); + if (mContainingNotification != null) { + applyBubbleAction(mHeadsUpChild, mContainingNotification.getEntry()); + } } @Override @@ -1138,6 +1151,8 @@ public class NotificationContentView extends FrameLayout { mForceSelectNextLayout = true; mPreviousExpandedRemoteInputIntent = null; mPreviousHeadsUpRemoteInputIntent = null; + applyBubbleAction(mExpandedChild, entry); + applyBubbleAction(mHeadsUpChild, entry); } private void updateAllSingleLineViews() { @@ -1308,6 +1323,58 @@ public class NotificationContentView extends FrameLayout { return null; } + /** + * Call to update state of the bubble button (i.e. does it show bubble or unbubble or no + * icon at all). + * + * @param entry the new entry to use. + */ + public void updateBubbleButton(NotificationEntry entry) { + applyBubbleAction(mExpandedChild, entry); + } + + private boolean isBubblesEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, 0) == 1; + } + + private void applyBubbleAction(View layout, NotificationEntry entry) { + if (layout == null || mContainingNotification == null || mPeopleIdentifier == null) { + return; + } + ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button); + View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container); + if (bubbleButton == null || actionContainer == null) { + return; + } + boolean isPerson = + mPeopleIdentifier.getPeopleNotificationType(entry.getSbn(), entry.getRanking()) + != PeopleNotificationIdentifier.TYPE_NON_PERSON; + boolean showButton = isBubblesEnabled() + && isPerson + && entry.getBubbleMetadata() != null; + if (showButton) { + Drawable d = mContext.getResources().getDrawable(entry.isBubble() + ? R.drawable.ic_stop_bubble + : R.drawable.ic_create_bubble); + mContainingNotification.updateNotificationColor(); + final int tint = mContainingNotification.getNotificationColor(); + d.setTint(tint); + + String contentDescription = mContext.getResources().getString(entry.isBubble() + ? R.string.notification_conversation_unbubble + : R.string.notification_conversation_bubble); + + bubbleButton.setContentDescription(contentDescription); + bubbleButton.setImageDrawable(d); + bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener()); + bubbleButton.setVisibility(VISIBLE); + actionContainer.setVisibility(VISIBLE); + } else { + bubbleButton.setVisibility(GONE); + } + } + private void applySmartReplyView( SmartRepliesAndActions smartRepliesAndActions, NotificationEntry entry) { @@ -1512,6 +1579,10 @@ public class NotificationContentView extends FrameLayout { mContainingNotification = containingNotification; } + public void setPeopleNotificationIdentifier(PeopleNotificationIdentifier peopleIdentifier) { + mPeopleIdentifier = peopleIdentifier; + } + public void requestSelectLayout(boolean needsAnimation) { selectLayout(needsAnimation, false); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 290784ddbffec..a27199370b163 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -40,11 +40,9 @@ import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.graphics.drawable.Icon; -import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.os.RemoteException; -import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.transition.ChangeBounds; @@ -53,7 +51,6 @@ import android.transition.TransitionManager; import android.transition.TransitionSet; import android.util.AttributeSet; import android.util.Log; -import android.util.Slog; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; @@ -64,6 +61,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.notification.ConversationIconFactory; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.NotificationChannelHelper; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -205,7 +203,8 @@ public class NotificationConversationInfo extends LinearLayout implements } mShortcutInfo = entry.getRanking().getShortcutInfo(); - createConversationChannelIfNeeded(); + mNotificationChannel = NotificationChannelHelper.createConversationChannelIfNeeded( + getContext(), mINotificationManager, entry, mNotificationChannel); bindHeader(); bindActions(); @@ -214,27 +213,6 @@ public class NotificationConversationInfo extends LinearLayout implements done.setOnClickListener(mOnDone); } - void createConversationChannelIfNeeded() { - // If this channel is not already a customized conversation channel, create - // a custom channel - if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) { - try { - // TODO: remove - mNotificationChannel.setName(mContext.getString( - R.string.notification_summary_message_format, - getName(), mNotificationChannel.getName())); - mINotificationManager.createConversationNotificationChannelForPackage( - mPackageName, mAppUid, mSbn.getKey(), mNotificationChannel, - mConversationId); - mNotificationChannel = mINotificationManager.getConversationNotificationChannel( - mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName, - mNotificationChannel.getId(), false, mConversationId); - } catch (RemoteException e) { - Slog.e(TAG, "Could not create conversation channel", e); - } - } - } - private void bindActions() { // TODO: b/152050825 @@ -318,24 +296,6 @@ public class NotificationConversationInfo extends LinearLayout implements } } - private void bindName() { - TextView name = findViewById(R.id.name); - name.setText(getName()); - } - - private String getName() { - if (mShortcutInfo != null) { - return mShortcutInfo.getShortLabel().toString(); - } else { - Bundle extras = mSbn.getNotification().extras; - String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE); - if (TextUtils.isEmpty(nameString)) { - nameString = extras.getString(Notification.EXTRA_TITLE); - } - return nameString; - } - } - private void bindPackage() { ApplicationInfo info; try { 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 037f04ec1d7cd..95f0bd52f4ee7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.IActivityManager; +import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; import android.content.res.Resources; @@ -273,7 +274,8 @@ public class BubbleControllerTest extends SysuiTestCase { mFeatureFlagsOldPipeline, mDumpManager, mFloatingContentCoordinator, - mSysUiState); + mSysUiState, + mock(INotificationManager.class)); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); 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 545de210d5b50..e8721a89c3b4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.IActivityManager; +import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; import android.content.res.Resources; @@ -250,7 +251,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mFeatureFlagsNewPipeline, mDumpManager, mFloatingContentCoordinator, - mSysUiState); + mSysUiState, + mock(INotificationManager.class)); mBubbleController.addNotifCallback(mNotifCallback); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); 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 f4861028e81ae..7815ae78823ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java @@ -16,6 +16,7 @@ package com.android.systemui.bubbles; +import android.app.INotificationManager; import android.content.Context; import com.android.systemui.dump.DumpManager; @@ -54,12 +55,14 @@ public class TestableBubbleController extends BubbleController { FeatureFlags featureFlags, DumpManager dumpManager, FloatingContentCoordinator floatingContentCoordinator, - SysUiState sysUiState) { + SysUiState sysUiState, + INotificationManager notificationManager) { super(context, notificationShadeWindowController, statusBarStateController, shadeController, data, Runnable::run, configurationController, interruptionStateProvider, zenModeController, lockscreenUserManager, groupManager, entryManager, - notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState); + notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState, + notificationManager); setInflateSynchronously(true); } } diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index b8140be2e2663..1051423ea17fb 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -51,7 +51,7 @@ public interface NotificationDelegate { /** * Called when the state of {@link Notification#FLAG_BUBBLE} is changed. */ - void onNotificationBubbleChanged(String key, boolean isBubble); + void onNotificationBubbleChanged(String key, boolean isBubble, int flags); /** * Called when the state of {@link Notification.BubbleMetadata#FLAG_SUPPRESS_NOTIFICATION} * changes. diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2a81e37b8fa59..c11a80ddf352f 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1196,14 +1196,7 @@ public class NotificationManagerService extends SystemService { } @Override - public void onNotificationBubbleChanged(String key, boolean isBubble) { - String pkg; - synchronized (mNotificationLock) { - NotificationRecord r = mNotificationsByKey.get(key); - pkg = r != null && r.getSbn() != null ? r.getSbn().getPackageName() : null; - } - boolean isAppForeground = pkg != null - && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; + public void onNotificationBubbleChanged(String key, boolean isBubble, int flags) { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { @@ -1220,8 +1213,13 @@ public class NotificationManagerService extends SystemService { // be applied there. r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; r.setFlagBubbleRemoved(false); + if (r.getNotification().getBubbleMetadata() != null) { + r.getNotification().getBubbleMetadata().setFlags(flags); + } + // Force isAppForeground true here, because for sysui's purposes we + // want to adjust the flag behaviour. mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(), - r, isAppForeground)); + r, true /* isAppForeground*/)); } } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index d7a0c9871b483..289bf66e1add1 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1380,11 +1380,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onNotificationBubbleChanged(String key, boolean isBubble) { + public void onNotificationBubbleChanged(String key, boolean isBubble, int flags) { enforceStatusBarService(); long identity = Binder.clearCallingIdentity(); try { - mNotificationDelegate.onNotificationBubbleChanged(key, isBubble); + mNotificationDelegate.onNotificationBubbleChanged(key, isBubble, flags); } finally { Binder.restoreCallingIdentity(identity); } 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 e6e84eeab4ed2..3cd0e92964ec1 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5548,7 +5548,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertTrue((notifsBefore[0].getNotification().flags & FLAG_BUBBLE) != 0); // Notify we're not a bubble - mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false); + mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false, 0); waitForIdle(); // Make sure we are not a bubble @@ -5587,7 +5587,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { reset(mListeners); // Notify we are now a bubble - mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true); + mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true, 0); waitForIdle(); // Make sure we are a bubble @@ -5618,7 +5618,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0); // Notify we are now a bubble - mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true); + mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true, 0); waitForIdle(); // We still wouldn't be a bubble because the notification didn't meet requirements @@ -5646,7 +5646,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertFalse(recordToCheck.isFlagBubbleRemoved()); // Notify we're not a bubble - mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false); + mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false, 0); waitForIdle(); // Flag should be modified recordToCheck = mService.getNotificationRecord(nr.getSbn().getKey()); @@ -5681,14 +5681,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertFalse(recordToCheck.isFlagBubbleRemoved()); // Notify we're not a bubble - mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false); + mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false, 0); waitForIdle(); // Flag should be modified recordToCheck = mService.getNotificationRecord(nr.getSbn().getKey()); assertTrue(recordToCheck.isFlagBubbleRemoved()); // Notify we are a bubble - mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true); + mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true, 0); waitForIdle(); // And the flag is reset assertFalse(recordToCheck.isFlagBubbleRemoved());