Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +52 −10 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.bubbles; import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS; 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; Loading @@ -41,7 +42,9 @@ import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Rect; Loading Loading @@ -112,7 +115,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi public static final int MAX_BUBBLES = 5; // TODO: actually enforce this // Enables some subset of notifs to automatically become bubbles private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false; public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false; /** Flag to enable or disable the entire feature */ private static final String ENABLE_BUBBLES = "experiment_enable_bubbles"; Loading Loading @@ -450,7 +453,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry)) { updateShowInShadeForSuppressNotification(entry); } } Loading @@ -460,7 +464,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry)) { updateBubble(entry); } } Loading @@ -470,7 +475,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry); boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.key)) { // It was previously a bubble but no longer a bubble -- lets remove it removeBubble(entry.key, DISMISS_NO_LONGER_BUBBLE); Loading Loading @@ -663,12 +669,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi || autoBubbleAll; } private boolean shouldAutoExpand(NotificationEntry entry) { Notification.BubbleMetadata metadata = entry.getBubbleMetadata(); return metadata != null && metadata.getAutoExpandBubble() && isForegroundApp(mContext, entry.notification.getPackageName()); } private void updateShowInShadeForSuppressNotification(NotificationEntry entry) { boolean suppressNotification = entry.getBubbleMetadata() != null && entry.getBubbleMetadata().isNotificationSuppressed() Loading Loading @@ -785,6 +785,48 @@ public class BubbleController implements ConfigurationController.ConfigurationLi (int) (defaultBounciness * 100)) / 100f; } /** * Whether an intent is properly configured to display in an {@link android.app.ActivityView}. * * Keep checks in sync with NotificationManagerService#canLaunchInActivityView. Typically * that should filter out any invalid bubbles, but should protect SysUI side just in case. * * @param context the context to use. * @param entry the entry to bubble. */ static boolean canLaunchInActivityView(Context context, NotificationEntry entry) { PendingIntent intent = entry.getBubbleMetadata() != null ? entry.getBubbleMetadata().getIntent() : null; if (intent == null) { Log.w(TAG, "Unable to create bubble -- no intent"); 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: " + intent); return false; } if (!ActivityInfo.isResizeableMode(info.resizeMode)) { Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: " + intent); return false; } if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) { Log.w(TAG, "Unable to send as bubble -- activity is not documentLaunchMode=always " + "for intent: " + intent); return false; } if ((info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) { Log.w(TAG, "Unable to send as bubble -- activity is not embeddable for intent: " + intent); return false; } return true; } /** PinnedStackListener that dispatches IME visibility updates to the stack. */ private class BubblesImeListener extends IPinnedStackListener.Stub { Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +4 −52 Original line number Diff line number Diff line Loading @@ -16,12 +16,10 @@ package com.android.systemui.bubbles; import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS; import static android.view.Display.INVALID_DISPLAY; import static com.android.systemui.bubbles.BubbleController.DEBUG_ENABLE_AUTO_BUBBLE; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.ActivityView; Loading @@ -30,7 +28,6 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; Loading Loading @@ -357,7 +354,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } setContentVisibility(false); mActivityView.setVisibility(VISIBLE); } else { } else if (DEBUG_ENABLE_AUTO_BUBBLE) { // Hide activity view if we had it previously mActivityView.setVisibility(GONE); mNotifRow = mEntry.getRow(); Loading Loading @@ -552,58 +549,13 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList @Nullable private PendingIntent getBubbleIntent(NotificationEntry entry) { Notification notif = entry.notification.getNotification(); String packageName = entry.notification.getPackageName(); Notification.BubbleMetadata data = notif.getBubbleMetadata(); if (data != null && canLaunchInActivityView(data.getIntent(), true /* enableLogging */, packageName)) { if (BubbleController.canLaunchInActivityView(mContext, entry) && data != null) { return data.getIntent(); } else if (BubbleController.shouldUseContentIntent(mContext) && canLaunchInActivityView(notif.contentIntent, false /* enableLogging */, packageName)) { return notif.contentIntent; } return null; } /** * Whether an intent is properly configured to display in an {@link android.app.ActivityView}. * * @param intent the pending intent of the bubble. * @param enableLogging whether bubble developer error should be logged. * @param packageName the notification package name for this bubble. * @return */ private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging, String packageName) { if (intent == null) { return false; } ActivityInfo info = intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0); if (info == null) { if (enableLogging) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING); } return false; } if (!ActivityInfo.isResizeableMode(info.resizeMode)) { if (enableLogging) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE); } return false; } if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) { if (enableLogging) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS); } return false; } return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0; } /** * Listener that is notified when a bubble is blocked. */ Loading services/core/java/com/android/server/notification/NotificationManagerService.java +67 −1 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import static android.content.Context.BIND_ADJUST_BELOW_PERCEPTIBLE; import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT; import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_FOREGROUND_SERVICE; import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS; import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.content.pm.PackageManager.MATCH_ALL; Loading Loading @@ -82,6 +83,9 @@ import static android.service.notification.NotificationListenerService.REASON_UN import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; import static android.service.notification.NotificationListenerService.TRIM_FULL; import static android.service.notification.NotificationListenerService.TRIM_LIGHT; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; Loading Loading @@ -131,6 +135,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; Loading Loading @@ -197,6 +202,7 @@ import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.StatsLog; import android.util.Xml; import android.util.proto.ProtoOutputStream; import android.view.accessibility.AccessibilityEvent; Loading Loading @@ -4821,9 +4827,12 @@ public class NotificationManagerService extends SystemService { private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId, NotificationRecord oldRecord) { Notification notification = r.getNotification(); Notification.BubbleMetadata metadata = notification.getBubbleMetadata(); boolean intentCanBubble = metadata != null && canLaunchInActivityView(getContext(), metadata.getIntent(), pkg); // Does the app want to bubble & is able to bubble boolean canBubble = notification.getBubbleMetadata() != null boolean canBubble = intentCanBubble && mPreferencesHelper.areBubblesAllowed(pkg, userId) && mPreferencesHelper.bubblesEnabled(r.sbn.getUser()) && r.getChannel().canBubble() Loading Loading @@ -4869,6 +4878,63 @@ public class NotificationManagerService extends SystemService { return false; } /** * Whether an intent is properly configured to display in an {@link android.app.ActivityView}. * * @param context the context to use. * @param pendingIntent the pending intent of the bubble. * @param packageName the notification package name for this bubble. */ // Keep checks in sync with BubbleController#canLaunchInActivityView. @VisibleForTesting protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent, String packageName) { if (pendingIntent == null) { Log.w(TAG, "Unable to create bubble -- no intent"); return false; } // Need escalated privileges to get the intent. final long token = Binder.clearCallingIdentity(); Intent intent; try { intent = pendingIntent.getIntent(); } finally { Binder.restoreCallingIdentity(token); } ActivityInfo info = intent != null ? intent.resolveActivityInfo(context.getPackageManager(), 0) : null; if (info == null) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING); Log.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: " + intent); return false; } if (!ActivityInfo.isResizeableMode(info.resizeMode)) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE); Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: " + intent); return false; } if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS); Log.w(TAG, "Unable to send as bubble -- activity is not documentLaunchMode=always " + "for intent: " + intent); return false; } if ((info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) { Log.w(TAG, "Unable to send as bubble -- activity is not embeddable for intent: " + intent); return false; } return true; } private void doChannelWarningToast(CharSequence toastText) { Binder.withCleanCallingIdentity(() -> { final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0; Loading services/tests/uiservicestests/AndroidManifest.xml +2 −0 Original line number Diff line number Diff line Loading @@ -30,12 +30,14 @@ <uses-permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/> <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> <provider android:name=".DummyProvider" android:authorities="com.android.services.uitests" /> </application> <instrumentation Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +6 −0 Original line number Diff line number Diff line Loading @@ -304,6 +304,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { void onGranted(ComponentName assistant, int userId, boolean granted); } @Override protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent, String packageName) { // Tests for this not being true are in CTS NotificationManagerTest return true; } } private class TestableToastCallback extends ITransientNotification.Stub { Loading Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +52 −10 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.bubbles; import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS; 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; Loading @@ -41,7 +42,9 @@ import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Rect; Loading Loading @@ -112,7 +115,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi public static final int MAX_BUBBLES = 5; // TODO: actually enforce this // Enables some subset of notifs to automatically become bubbles private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false; public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false; /** Flag to enable or disable the entire feature */ private static final String ENABLE_BUBBLES = "experiment_enable_bubbles"; Loading Loading @@ -450,7 +453,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry)) { updateShowInShadeForSuppressNotification(entry); } } Loading @@ -460,7 +464,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry)) { updateBubble(entry); } } Loading @@ -470,7 +475,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry); boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && canLaunchInActivityView(mContext, entry); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.key)) { // It was previously a bubble but no longer a bubble -- lets remove it removeBubble(entry.key, DISMISS_NO_LONGER_BUBBLE); Loading Loading @@ -663,12 +669,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi || autoBubbleAll; } private boolean shouldAutoExpand(NotificationEntry entry) { Notification.BubbleMetadata metadata = entry.getBubbleMetadata(); return metadata != null && metadata.getAutoExpandBubble() && isForegroundApp(mContext, entry.notification.getPackageName()); } private void updateShowInShadeForSuppressNotification(NotificationEntry entry) { boolean suppressNotification = entry.getBubbleMetadata() != null && entry.getBubbleMetadata().isNotificationSuppressed() Loading Loading @@ -785,6 +785,48 @@ public class BubbleController implements ConfigurationController.ConfigurationLi (int) (defaultBounciness * 100)) / 100f; } /** * Whether an intent is properly configured to display in an {@link android.app.ActivityView}. * * Keep checks in sync with NotificationManagerService#canLaunchInActivityView. Typically * that should filter out any invalid bubbles, but should protect SysUI side just in case. * * @param context the context to use. * @param entry the entry to bubble. */ static boolean canLaunchInActivityView(Context context, NotificationEntry entry) { PendingIntent intent = entry.getBubbleMetadata() != null ? entry.getBubbleMetadata().getIntent() : null; if (intent == null) { Log.w(TAG, "Unable to create bubble -- no intent"); 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: " + intent); return false; } if (!ActivityInfo.isResizeableMode(info.resizeMode)) { Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: " + intent); return false; } if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) { Log.w(TAG, "Unable to send as bubble -- activity is not documentLaunchMode=always " + "for intent: " + intent); return false; } if ((info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) { Log.w(TAG, "Unable to send as bubble -- activity is not embeddable for intent: " + intent); return false; } return true; } /** PinnedStackListener that dispatches IME visibility updates to the stack. */ private class BubblesImeListener extends IPinnedStackListener.Stub { Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +4 −52 Original line number Diff line number Diff line Loading @@ -16,12 +16,10 @@ package com.android.systemui.bubbles; import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS; import static android.view.Display.INVALID_DISPLAY; import static com.android.systemui.bubbles.BubbleController.DEBUG_ENABLE_AUTO_BUBBLE; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.ActivityView; Loading @@ -30,7 +28,6 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; Loading Loading @@ -357,7 +354,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } setContentVisibility(false); mActivityView.setVisibility(VISIBLE); } else { } else if (DEBUG_ENABLE_AUTO_BUBBLE) { // Hide activity view if we had it previously mActivityView.setVisibility(GONE); mNotifRow = mEntry.getRow(); Loading Loading @@ -552,58 +549,13 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList @Nullable private PendingIntent getBubbleIntent(NotificationEntry entry) { Notification notif = entry.notification.getNotification(); String packageName = entry.notification.getPackageName(); Notification.BubbleMetadata data = notif.getBubbleMetadata(); if (data != null && canLaunchInActivityView(data.getIntent(), true /* enableLogging */, packageName)) { if (BubbleController.canLaunchInActivityView(mContext, entry) && data != null) { return data.getIntent(); } else if (BubbleController.shouldUseContentIntent(mContext) && canLaunchInActivityView(notif.contentIntent, false /* enableLogging */, packageName)) { return notif.contentIntent; } return null; } /** * Whether an intent is properly configured to display in an {@link android.app.ActivityView}. * * @param intent the pending intent of the bubble. * @param enableLogging whether bubble developer error should be logged. * @param packageName the notification package name for this bubble. * @return */ private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging, String packageName) { if (intent == null) { return false; } ActivityInfo info = intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0); if (info == null) { if (enableLogging) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING); } return false; } if (!ActivityInfo.isResizeableMode(info.resizeMode)) { if (enableLogging) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE); } return false; } if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) { if (enableLogging) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS); } return false; } return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0; } /** * Listener that is notified when a bubble is blocked. */ Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +67 −1 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import static android.content.Context.BIND_ADJUST_BELOW_PERCEPTIBLE; import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT; import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_FOREGROUND_SERVICE; import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS; import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.content.pm.PackageManager.MATCH_ALL; Loading Loading @@ -82,6 +83,9 @@ import static android.service.notification.NotificationListenerService.REASON_UN import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; import static android.service.notification.NotificationListenerService.TRIM_FULL; import static android.service.notification.NotificationListenerService.TRIM_LIGHT; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE; import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; Loading Loading @@ -131,6 +135,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; Loading Loading @@ -197,6 +202,7 @@ import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.StatsLog; import android.util.Xml; import android.util.proto.ProtoOutputStream; import android.view.accessibility.AccessibilityEvent; Loading Loading @@ -4821,9 +4827,12 @@ public class NotificationManagerService extends SystemService { private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId, NotificationRecord oldRecord) { Notification notification = r.getNotification(); Notification.BubbleMetadata metadata = notification.getBubbleMetadata(); boolean intentCanBubble = metadata != null && canLaunchInActivityView(getContext(), metadata.getIntent(), pkg); // Does the app want to bubble & is able to bubble boolean canBubble = notification.getBubbleMetadata() != null boolean canBubble = intentCanBubble && mPreferencesHelper.areBubblesAllowed(pkg, userId) && mPreferencesHelper.bubblesEnabled(r.sbn.getUser()) && r.getChannel().canBubble() Loading Loading @@ -4869,6 +4878,63 @@ public class NotificationManagerService extends SystemService { return false; } /** * Whether an intent is properly configured to display in an {@link android.app.ActivityView}. * * @param context the context to use. * @param pendingIntent the pending intent of the bubble. * @param packageName the notification package name for this bubble. */ // Keep checks in sync with BubbleController#canLaunchInActivityView. @VisibleForTesting protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent, String packageName) { if (pendingIntent == null) { Log.w(TAG, "Unable to create bubble -- no intent"); return false; } // Need escalated privileges to get the intent. final long token = Binder.clearCallingIdentity(); Intent intent; try { intent = pendingIntent.getIntent(); } finally { Binder.restoreCallingIdentity(token); } ActivityInfo info = intent != null ? intent.resolveActivityInfo(context.getPackageManager(), 0) : null; if (info == null) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING); Log.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: " + intent); return false; } if (!ActivityInfo.isResizeableMode(info.resizeMode)) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE); Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: " + intent); return false; } if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) { StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS); Log.w(TAG, "Unable to send as bubble -- activity is not documentLaunchMode=always " + "for intent: " + intent); return false; } if ((info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) { Log.w(TAG, "Unable to send as bubble -- activity is not embeddable for intent: " + intent); return false; } return true; } private void doChannelWarningToast(CharSequence toastText) { Binder.withCleanCallingIdentity(() -> { final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0; Loading
services/tests/uiservicestests/AndroidManifest.xml +2 −0 Original line number Diff line number Diff line Loading @@ -30,12 +30,14 @@ <uses-permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/> <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> <provider android:name=".DummyProvider" android:authorities="com.android.services.uitests" /> </application> <instrumentation Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +6 −0 Original line number Diff line number Diff line Loading @@ -304,6 +304,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { void onGranted(ComponentName assistant, int userId, boolean granted); } @Override protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent, String packageName) { // Tests for this not being true are in CTS NotificationManagerTest return true; } } private class TestableToastCallback extends ITransientNotification.Stub { Loading