Loading packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +19 −7 Original line number Diff line number Diff line Loading @@ -64,7 +64,8 @@ class Bubble { private long mLastUpdated; private long mLastAccessed; private boolean mIsRemoved; private boolean mIsUserCreated; /** * Whether this notification should be shown in the shade when it is also displayed as a bubble. Loading @@ -74,9 +75,7 @@ class Bubble { */ private boolean mShowInShadeWhenBubble = true; /** * Whether the bubble should show a dot for the notification indicating updated content. */ /** Whether the bubble should show a dot for the notification indicating updated content. */ private boolean mShowBubbleUpdateDot = true; /** Whether flyout text should be suppressed, regardless of any other flags or state. */ Loading Loading @@ -294,6 +293,20 @@ class Bubble { return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; } /** * Whether this bubble was explicitly created by the user via a SysUI affordance. */ boolean isUserCreated() { return mIsUserCreated; } /** * Set whether this bubble was explicitly created by the user via a SysUI affordance. */ void setUserCreated(boolean isUserCreated) { mIsUserCreated = isUserCreated; } float getDesiredHeight(Context context) { Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); boolean useRes = data.getDesiredHeightResId() != 0; Loading @@ -319,9 +332,8 @@ class Bubble { @Nullable PendingIntent getBubbleIntent(Context context) { Notification notif = mEntry.getSbn().getNotification(); Notification.BubbleMetadata data = notif.getBubbleMetadata(); if (BubbleController.canLaunchInActivityView(context, mEntry) && data != null) { Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); if (data != null) { return data.getIntent(); } return null; Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +75 −19 Original line number Diff line number Diff line Loading @@ -67,7 +67,6 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.ActivityManagerWrapper; Loading @@ -82,6 +81,7 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; Loading @@ -96,6 +96,8 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import dagger.Lazy; /** * Bubbles are a special type of content that can "float" on top of other apps or System UI. * Bubbles can be expanded to show more content. Loading Loading @@ -132,6 +134,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private BubbleExpandListener mExpandListener; @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; private final NotificationGroupManager mNotificationGroupManager; private final Lazy<ShadeController> mShadeController; private BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; Loading Loading @@ -206,24 +209,34 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } @Inject public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, ConfigurationController configurationController, public BubbleController(Context context, StatusBarWindowController statusBarWindowController, StatusBarStateController statusBarStateController, Lazy<ShadeController> shadeController, BubbleData data, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager) { this(context, statusBarWindowController, data, null /* synchronizer */, configurationController, interruptionStateProvider, zenModeController, notifUserManager, groupManager); } public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, NotificationGroupManager groupManager, NotificationEntryManager entryManager) { this(context, statusBarWindowController, statusBarStateController, shadeController, data, null /* synchronizer */, configurationController, interruptionStateProvider, zenModeController, notifUserManager, groupManager, entryManager); } public BubbleController(Context context, StatusBarWindowController statusBarWindowController, StatusBarStateController statusBarStateController, Lazy<ShadeController> shadeController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager) { NotificationGroupManager groupManager, NotificationEntryManager entryManager) { mContext = context; mNotificationInterruptionStateProvider = interruptionStateProvider; mNotifUserManager = notifUserManager; Loading @@ -249,7 +262,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleData = data; mBubbleData.setListener(mBubbleDataListener); mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); mNotificationEntryManager = entryManager; mNotificationEntryManager.addNotificationEntryListener(mEntryListener); mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor); mNotificationGroupManager = groupManager; Loading @@ -271,9 +284,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } }); mShadeController = shadeController; mStatusBarWindowController = statusBarWindowController; mStatusBarStateListener = new StatusBarStateListener(); Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener); statusBarStateController.addCallback(mStatusBarStateListener); mTaskStackListener = new BubbleTaskStackListener(); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); Loading Loading @@ -497,15 +511,45 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @param notif the notification associated with this bubble. */ void updateBubble(NotificationEntry notif) { updateBubble(notif, /* supressFlyout */ false); updateBubble(notif, false /* suppressFlyout */); } void updateBubble(NotificationEntry notif, boolean suppressFlyout) { updateBubble(notif, suppressFlyout, true /* showInShade */); } void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) { // If this is an interruptive notif, mark that it's interrupted if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) { notif.setInterruption(); } mBubbleData.notificationEntryUpdated(notif, suppressFlyout); mBubbleData.notificationEntryUpdated(notif, suppressFlyout, showInShade); } /** * Called when a user has indicated that an active notification should be shown as a bubble. * <p> * 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. */ public void onUserCreatedBubbleFromNotification(NotificationEntry entry) { mShadeController.get().collapsePanel(true); entry.setFlagBubble(true); updateBubble(entry, true /* suppressFlyout */, false /* showInShade */); mBubbleData.getBubbleWithKey(entry.getKey()).setUserCreated(true); } /** * 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) { entry.setFlagBubble(false); removeBubble(entry.getKey(), DISMISS_BLOCKED); } /** Loading Loading @@ -571,7 +615,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mNotificationEntryManager.updateNotifications( "BubbleController.onNotificationRemoveRequested"); return true; } else if (!userRemovedNotif && entry != null) { } else if (!userRemovedNotif && entry != null && !bubble.isUserCreated()) { // This wasn't a user removal so we should remove the bubble as well mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL); return false; Loading Loading @@ -631,6 +675,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { @Override public void onPendingEntryAdded(NotificationEntry entry) { Bubble b = mBubbleData.getBubbleWithKey(entry.getKey()); BubbleExperimentConfig.adjustForExperiments(mContext, entry, b); if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry)) { updateBubble(entry); Loading @@ -639,13 +686,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Override public void onPreEntryUpdated(NotificationEntry entry) { Bubble b = mBubbleData.getBubbleWithKey(entry.getKey()); BubbleExperimentConfig.adjustForExperiments(mContext, entry, b); boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) { // It was previously a bubble but no longer a bubble -- lets remove it removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE); } else if (shouldBubble) { Bubble b = mBubbleData.getBubbleWithKey(entry.getKey()); updateBubble(entry); } } Loading Loading @@ -949,19 +998,26 @@ public class BubbleController implements ConfigurationController.ConfigurationLi PendingIntent intent = entry.getBubbleMetadata() != null ? entry.getBubbleMetadata().getIntent() : null; return canLaunchIntentInActivityView(context, entry, intent); } static boolean canLaunchIntentInActivityView(Context context, NotificationEntry entry, PendingIntent intent) { if (intent == null) { Log.w(TAG, "Unable to create bubble -- no intent"); Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey()); return false; } ActivityInfo info = intent.getIntent().resolveActivityInfo(context.getPackageManager(), 0); if (info == null) { Log.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: " Log.w(TAG, "Unable to send as bubble, " + entry.getKey() + " couldn't find activity info for intent: " + intent); return false; } if (!ActivityInfo.isResizeableMode(info.resizeMode)) { Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: " Log.w(TAG, "Unable to send as bubble, " + entry.getKey() + " activity is not resizable for intent: " + intent); return false; } Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +3 −2 Original line number Diff line number Diff line Loading @@ -179,7 +179,8 @@ public class BubbleData { dispatchPendingChanges(); } void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout) { void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout, boolean showInShade) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "notificationEntryUpdated: " + entry); } Loading Loading @@ -208,7 +209,7 @@ public class BubbleData { setSelectedBubbleInternal(bubble); } boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble; bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected); bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected && showInShade); bubble.setShowBubbleDot(!isBubbleExpandedAndSelected); dispatchPendingChanges(); } Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +6 −4 Original line number Diff line number Diff line Loading @@ -184,12 +184,14 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList + " mActivityViewStatus=" + mActivityViewStatus + " bubble=" + getBubbleKey()); } if (mBubble != null && !mBubble.isUserCreated()) { if (mBubble != null) { // Must post because this is called from a binder thread. post(() -> mBubbleController.removeBubble(mBubble.getKey(), BubbleController.DISMISS_TASK_FINISHED)); } } } }; public BubbleExpandedView(Context context) { Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java 0 → 100644 +98 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.bubbles; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.graphics.drawable.Icon; import android.provider.Settings; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Common class for experiments controlled via secure settings. */ public class BubbleExperimentConfig { private static final String ALLOW_ANY_NOTIF_TO_BUBBLE = "allow_any_notif_to_bubble"; private static final boolean ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT = false; private static final String ALLOW_MESSAGE_NOTIFS_TO_BUBBLE = "allow_message_notifs_to_bubble"; private static final boolean ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT = false; /** * When true, if a notification has the information necessary to bubble (i.e. valid * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata} * object will be created by the system and added to the notification. * * This does not produce a bubble, only adds the metadata. It should be used in conjunction * with {@see #allowNotifBubbleMenu} which shows an affordance to bubble notification content. */ static boolean allowAnyNotifToBubble(Context context) { return Settings.Secure.getInt(context.getContentResolver(), ALLOW_ANY_NOTIF_TO_BUBBLE, ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT ? 1 : 0) != 0; } /** * Same as {@link #allowAnyNotifToBubble(Context)} except it filters for notifications that * are using {@link Notification.MessagingStyle} and have remote input. */ static boolean allowMessageNotifsToBubble(Context context) { return Settings.Secure.getInt(context.getContentResolver(), ALLOW_MESSAGE_NOTIFS_TO_BUBBLE, ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT ? 1 : 0) != 0; } /** * If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds * {@link android.app.Notification.BubbleMetadata} to the notification entry as long as * the notification has necessary info for BubbleMetadata. */ static void adjustForExperiments(Context context, NotificationEntry entry, Bubble previousBubble) { if (entry.getBubbleMetadata() != null) { // Has metadata, nothing to do. return; } Notification notification = entry.getSbn().getNotification(); boolean isMessage = Notification.MessagingStyle.class.equals( notification.getNotificationStyle()); boolean bubbleNotifForExperiment = (isMessage && allowMessageNotifsToBubble(context)) || allowAnyNotifToBubble(context); final PendingIntent intent = notification.contentIntent; if (bubbleNotifForExperiment && BubbleController.canLaunchIntentInActivityView(context, entry, intent)) { final Icon smallIcon = entry.getSbn().getNotification().getSmallIcon(); Notification.BubbleMetadata.Builder metadata = new Notification.BubbleMetadata.Builder() .setDesiredHeight(10000) .setIcon(smallIcon) .setIntent(intent); entry.setBubbleMetadata(metadata.build()); } if (previousBubble != null) { // Update to a previously user-created bubble, set its flag now so the update goes // to the bubble. entry.setFlagBubble(true); } } } Loading
packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +19 −7 Original line number Diff line number Diff line Loading @@ -64,7 +64,8 @@ class Bubble { private long mLastUpdated; private long mLastAccessed; private boolean mIsRemoved; private boolean mIsUserCreated; /** * Whether this notification should be shown in the shade when it is also displayed as a bubble. Loading @@ -74,9 +75,7 @@ class Bubble { */ private boolean mShowInShadeWhenBubble = true; /** * Whether the bubble should show a dot for the notification indicating updated content. */ /** Whether the bubble should show a dot for the notification indicating updated content. */ private boolean mShowBubbleUpdateDot = true; /** Whether flyout text should be suppressed, regardless of any other flags or state. */ Loading Loading @@ -294,6 +293,20 @@ class Bubble { return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; } /** * Whether this bubble was explicitly created by the user via a SysUI affordance. */ boolean isUserCreated() { return mIsUserCreated; } /** * Set whether this bubble was explicitly created by the user via a SysUI affordance. */ void setUserCreated(boolean isUserCreated) { mIsUserCreated = isUserCreated; } float getDesiredHeight(Context context) { Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); boolean useRes = data.getDesiredHeightResId() != 0; Loading @@ -319,9 +332,8 @@ class Bubble { @Nullable PendingIntent getBubbleIntent(Context context) { Notification notif = mEntry.getSbn().getNotification(); Notification.BubbleMetadata data = notif.getBubbleMetadata(); if (BubbleController.canLaunchInActivityView(context, mEntry) && data != null) { Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); if (data != null) { return data.getIntent(); } return null; Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +75 −19 Original line number Diff line number Diff line Loading @@ -67,7 +67,6 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.ActivityManagerWrapper; Loading @@ -82,6 +81,7 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; Loading @@ -96,6 +96,8 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import dagger.Lazy; /** * Bubbles are a special type of content that can "float" on top of other apps or System UI. * Bubbles can be expanded to show more content. Loading Loading @@ -132,6 +134,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private BubbleExpandListener mExpandListener; @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; private final NotificationGroupManager mNotificationGroupManager; private final Lazy<ShadeController> mShadeController; private BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; Loading Loading @@ -206,24 +209,34 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } @Inject public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, ConfigurationController configurationController, public BubbleController(Context context, StatusBarWindowController statusBarWindowController, StatusBarStateController statusBarStateController, Lazy<ShadeController> shadeController, BubbleData data, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager) { this(context, statusBarWindowController, data, null /* synchronizer */, configurationController, interruptionStateProvider, zenModeController, notifUserManager, groupManager); } public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, NotificationGroupManager groupManager, NotificationEntryManager entryManager) { this(context, statusBarWindowController, statusBarStateController, shadeController, data, null /* synchronizer */, configurationController, interruptionStateProvider, zenModeController, notifUserManager, groupManager, entryManager); } public BubbleController(Context context, StatusBarWindowController statusBarWindowController, StatusBarStateController statusBarStateController, Lazy<ShadeController> shadeController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager) { NotificationGroupManager groupManager, NotificationEntryManager entryManager) { mContext = context; mNotificationInterruptionStateProvider = interruptionStateProvider; mNotifUserManager = notifUserManager; Loading @@ -249,7 +262,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleData = data; mBubbleData.setListener(mBubbleDataListener); mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); mNotificationEntryManager = entryManager; mNotificationEntryManager.addNotificationEntryListener(mEntryListener); mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor); mNotificationGroupManager = groupManager; Loading @@ -271,9 +284,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } }); mShadeController = shadeController; mStatusBarWindowController = statusBarWindowController; mStatusBarStateListener = new StatusBarStateListener(); Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener); statusBarStateController.addCallback(mStatusBarStateListener); mTaskStackListener = new BubbleTaskStackListener(); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); Loading Loading @@ -497,15 +511,45 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @param notif the notification associated with this bubble. */ void updateBubble(NotificationEntry notif) { updateBubble(notif, /* supressFlyout */ false); updateBubble(notif, false /* suppressFlyout */); } void updateBubble(NotificationEntry notif, boolean suppressFlyout) { updateBubble(notif, suppressFlyout, true /* showInShade */); } void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) { // If this is an interruptive notif, mark that it's interrupted if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) { notif.setInterruption(); } mBubbleData.notificationEntryUpdated(notif, suppressFlyout); mBubbleData.notificationEntryUpdated(notif, suppressFlyout, showInShade); } /** * Called when a user has indicated that an active notification should be shown as a bubble. * <p> * 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. */ public void onUserCreatedBubbleFromNotification(NotificationEntry entry) { mShadeController.get().collapsePanel(true); entry.setFlagBubble(true); updateBubble(entry, true /* suppressFlyout */, false /* showInShade */); mBubbleData.getBubbleWithKey(entry.getKey()).setUserCreated(true); } /** * 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) { entry.setFlagBubble(false); removeBubble(entry.getKey(), DISMISS_BLOCKED); } /** Loading Loading @@ -571,7 +615,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mNotificationEntryManager.updateNotifications( "BubbleController.onNotificationRemoveRequested"); return true; } else if (!userRemovedNotif && entry != null) { } else if (!userRemovedNotif && entry != null && !bubble.isUserCreated()) { // This wasn't a user removal so we should remove the bubble as well mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL); return false; Loading Loading @@ -631,6 +675,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { @Override public void onPendingEntryAdded(NotificationEntry entry) { Bubble b = mBubbleData.getBubbleWithKey(entry.getKey()); BubbleExperimentConfig.adjustForExperiments(mContext, entry, b); if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry)) { updateBubble(entry); Loading @@ -639,13 +686,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Override public void onPreEntryUpdated(NotificationEntry entry) { Bubble b = mBubbleData.getBubbleWithKey(entry.getKey()); BubbleExperimentConfig.adjustForExperiments(mContext, entry, b); boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) { // It was previously a bubble but no longer a bubble -- lets remove it removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE); } else if (shouldBubble) { Bubble b = mBubbleData.getBubbleWithKey(entry.getKey()); updateBubble(entry); } } Loading Loading @@ -949,19 +998,26 @@ public class BubbleController implements ConfigurationController.ConfigurationLi PendingIntent intent = entry.getBubbleMetadata() != null ? entry.getBubbleMetadata().getIntent() : null; return canLaunchIntentInActivityView(context, entry, intent); } static boolean canLaunchIntentInActivityView(Context context, NotificationEntry entry, PendingIntent intent) { if (intent == null) { Log.w(TAG, "Unable to create bubble -- no intent"); Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey()); return false; } ActivityInfo info = intent.getIntent().resolveActivityInfo(context.getPackageManager(), 0); if (info == null) { Log.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: " Log.w(TAG, "Unable to send as bubble, " + entry.getKey() + " couldn't find activity info for intent: " + intent); return false; } if (!ActivityInfo.isResizeableMode(info.resizeMode)) { Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: " Log.w(TAG, "Unable to send as bubble, " + entry.getKey() + " activity is not resizable for intent: " + intent); return false; } Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +3 −2 Original line number Diff line number Diff line Loading @@ -179,7 +179,8 @@ public class BubbleData { dispatchPendingChanges(); } void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout) { void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout, boolean showInShade) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "notificationEntryUpdated: " + entry); } Loading Loading @@ -208,7 +209,7 @@ public class BubbleData { setSelectedBubbleInternal(bubble); } boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble; bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected); bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected && showInShade); bubble.setShowBubbleDot(!isBubbleExpandedAndSelected); dispatchPendingChanges(); } Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +6 −4 Original line number Diff line number Diff line Loading @@ -184,12 +184,14 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList + " mActivityViewStatus=" + mActivityViewStatus + " bubble=" + getBubbleKey()); } if (mBubble != null && !mBubble.isUserCreated()) { if (mBubble != null) { // Must post because this is called from a binder thread. post(() -> mBubbleController.removeBubble(mBubble.getKey(), BubbleController.DISMISS_TASK_FINISHED)); } } } }; public BubbleExpandedView(Context context) { Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java 0 → 100644 +98 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.bubbles; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.graphics.drawable.Icon; import android.provider.Settings; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Common class for experiments controlled via secure settings. */ public class BubbleExperimentConfig { private static final String ALLOW_ANY_NOTIF_TO_BUBBLE = "allow_any_notif_to_bubble"; private static final boolean ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT = false; private static final String ALLOW_MESSAGE_NOTIFS_TO_BUBBLE = "allow_message_notifs_to_bubble"; private static final boolean ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT = false; /** * When true, if a notification has the information necessary to bubble (i.e. valid * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata} * object will be created by the system and added to the notification. * * This does not produce a bubble, only adds the metadata. It should be used in conjunction * with {@see #allowNotifBubbleMenu} which shows an affordance to bubble notification content. */ static boolean allowAnyNotifToBubble(Context context) { return Settings.Secure.getInt(context.getContentResolver(), ALLOW_ANY_NOTIF_TO_BUBBLE, ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT ? 1 : 0) != 0; } /** * Same as {@link #allowAnyNotifToBubble(Context)} except it filters for notifications that * are using {@link Notification.MessagingStyle} and have remote input. */ static boolean allowMessageNotifsToBubble(Context context) { return Settings.Secure.getInt(context.getContentResolver(), ALLOW_MESSAGE_NOTIFS_TO_BUBBLE, ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT ? 1 : 0) != 0; } /** * If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds * {@link android.app.Notification.BubbleMetadata} to the notification entry as long as * the notification has necessary info for BubbleMetadata. */ static void adjustForExperiments(Context context, NotificationEntry entry, Bubble previousBubble) { if (entry.getBubbleMetadata() != null) { // Has metadata, nothing to do. return; } Notification notification = entry.getSbn().getNotification(); boolean isMessage = Notification.MessagingStyle.class.equals( notification.getNotificationStyle()); boolean bubbleNotifForExperiment = (isMessage && allowMessageNotifsToBubble(context)) || allowAnyNotifToBubble(context); final PendingIntent intent = notification.contentIntent; if (bubbleNotifForExperiment && BubbleController.canLaunchIntentInActivityView(context, entry, intent)) { final Icon smallIcon = entry.getSbn().getNotification().getSmallIcon(); Notification.BubbleMetadata.Builder metadata = new Notification.BubbleMetadata.Builder() .setDesiredHeight(10000) .setIcon(smallIcon) .setIntent(intent); entry.setBubbleMetadata(metadata.build()); } if (previousBubble != null) { // Update to a previously user-created bubble, set its flag now so the update goes // to the bubble. entry.setFlagBubble(true); } } }