Merge "Ensure notif-based bubbles don't occur once and for all!! & improve logging" into qt-dev
This commit is contained in:
@@ -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 {
|
||||
|
||||
|
||||
@@ -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,58 +528,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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user