diff options
| author | 2019-05-16 16:14:32 -0700 | |
|---|---|---|
| committer | 2019-05-22 06:23:01 -0700 | |
| commit | ca0c24c333d096309a7060c65baffbee63924fc3 (patch) | |
| tree | 122472996713b7dc8f070b6f849b8aff3488aa9f | |
| parent | d38f0d3440926757282b188c5ef26442c1cded80 (diff) | |
Ensure notif-based bubbles don't occur once and for all!! & improve logging
* Move filtering of intent into NotificationManagerService
* Add Log.w messages when an activity isn't configured correctly
* Updates tests so that they will still pass
* Add tests for badly-configured bubbles
Test: atest NotificationManagerTest (other CL)
Test: manual - send a badly configured bubble and note the logs & that
it doesn't appear
Fixes: 132913407
Change-Id: I3b3e2d63b34fe51f1aea178a92f567b6b3e0b927
5 files changed, 131 insertions, 64 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index dcc0419ab0cf..7e016bb000ad 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -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; @@ -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; @@ -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"; @@ -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); } } @@ -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); } } @@ -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); @@ -657,12 +663,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() @@ -771,6 +771,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 { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index e7948b5149a0..6add4a483103 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -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; @@ -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; @@ -337,11 +334,10 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mNotifRow = null; } mActivityView.setVisibility(VISIBLE); - } else { + } else if (DEBUG_ENABLE_AUTO_BUBBLE) { // Hide activity view if we had it previously mActivityView.setVisibility(GONE); mNotifRow = mEntry.getRow(); - } updateView(); } @@ -532,59 +528,14 @@ 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. */ public interface OnBubbleBlockedListener { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 46d6886f0854..8c58247cc601 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -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; @@ -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; @@ -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; @@ -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; @@ -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() @@ -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; diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index 3ff85c8806ce..7453c489ecc8 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -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 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 9db8753dc3b6..87f221a18161 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -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 { |