Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 091729b4 authored by Android Build Merger (Role)'s avatar Android Build Merger (Role) Committed by Android (Google) Code Review
Browse files

Merge "Merge "Ensure notif-based bubbles don't occur once and for all!! &...

Merge "Merge "Ensure notif-based bubbles don't occur once and for all!! & improve logging" into qt-dev am: 6494b52f am: b1c51e6b am: 9a61ca90"
parents 82610310 372480a7
Loading
Loading
Loading
Loading
+52 −10
Original line number Diff line number Diff line
@@ -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);
@@ -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()
@@ -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 {

+4 −52
Original line number Diff line number Diff line
@@ -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;
@@ -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();
@@ -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.
     */
+67 −1
Original line number Diff line number Diff line
@@ -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;
+2 −0
Original line number Diff line number Diff line
@@ -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
+6 −0
Original line number Diff line number Diff line
@@ -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 {