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

Commit f8b7c82a authored by Mady Mellor's avatar Mady Mellor Committed by Android (Google) Code Review
Browse files

Merge "Flag to allow notifs with shortcuts to have BubbleMetadata"

parents 7fd13f6a b547e5ed
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -206,6 +206,9 @@
    <!-- shortcut manager -->
    <uses-permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING" />

    <!-- launcher apps -->
    <uses-permission android:name="android.permission.ACCESS_SHORTCUTS" />

    <uses-permission android:name="android.permission.MODIFY_THEME_OVERLAY" />

    <!-- accessibility -->
+29 −1
Original line number Diff line number Diff line
@@ -27,9 +27,13 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
@@ -57,6 +61,7 @@ class Bubble {
    private final String mGroupId;
    private String mAppName;
    private Drawable mUserBadgedAppIcon;
    private ShortcutInfo mShortcutInfo;

    private boolean mInflated;
    private BubbleView mIconView;
@@ -94,6 +99,14 @@ class Bubble {
        mLastUpdated = e.getSbn().getPostTime();
        mGroupId = groupId(e);

        String shortcutId = e.getSbn().getNotification().getShortcutId();
        if (BubbleExperimentConfig.useShortcutInfoToBubble(context)
                && shortcutId != null) {
            mShortcutInfo = BubbleExperimentConfig.getShortcutInfo(context,
                    e.getSbn().getPackageName(),
                    e.getSbn().getUser(), shortcutId);
        }

        PackageManager pm = context.getPackageManager();
        ApplicationInfo info;
        try {
@@ -137,6 +150,21 @@ class Bubble {
        return mUserBadgedAppIcon;
    }

    @Nullable
    public ShortcutInfo getShortcutInfo() {
        return mShortcutInfo;
    }

    /**
     * Whether shortcut information should be used to populate the bubble.
     * <p>
     * To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}.
     * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
     */
    public boolean usingShortcutInfo() {
        return BubbleExperimentConfig.isShortcutIntent(getBubbleIntent());
    }

    boolean isInflated() {
        return mInflated;
    }
@@ -331,7 +359,7 @@ class Bubble {
    }

    @Nullable
    PendingIntent getBubbleIntent(Context context) {
    PendingIntent getBubbleIntent() {
        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
        if (data != null) {
            return data.getIntent();
+11 −6
Original line number Diff line number Diff line
@@ -130,12 +130,17 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
                            Log.d(TAG, "onActivityViewReady: calling startActivity, "
                                    + "bubble=" + getBubbleKey());
                        }
                        try {
                            if (mBubble.usingShortcutInfo()) {
                                mActivityView.startShortcutActivity(mBubble.getShortcutInfo(),
                                        options, null /* sourceBounds */);
                            } else {
                                Intent fillInIntent = new Intent();
                                // Apply flags to make behaviour match documentLaunchMode=always.
                                fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
                                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                        try {
                                mActivityView.startActivity(mBubbleIntent, fillInIntent, options);
                            }
                        } catch (RuntimeException e) {
                            // If there's a runtime exception here then there's something
                            // wrong with the intent, we can't really recover / try to populate
@@ -415,7 +420,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
                    + getBubbleKey());
        }

        mBubbleIntent = mBubble.getBubbleIntent(mContext);
        mBubbleIntent = mBubble.getBubbleIntent();
        if (mBubbleIntent != null) {
            setContentVisibility(false);
            mActivityView.setVisibility(VISIBLE);
+123 −19
Original line number Diff line number Diff line
@@ -16,32 +16,52 @@

package com.android.systemui.bubbles;

import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;

import static com.android.systemui.bubbles.BubbleController.canLaunchIntentInActivityView;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.provider.Settings;

import com.android.systemui.statusbar.notification.collection.NotificationEntry;

import java.util.Arrays;
import java.util.List;

/**
 * Common class for experiments controlled via secure settings.
 */
public class BubbleExperimentConfig {

    private static final String SHORTCUT_DUMMY_INTENT = "bubble_experiment_shortcut_intent";
    private static PendingIntent sDummyShortcutIntent;

    private static final int BUBBLE_HEIGHT = 10000;

    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;

    private static final String ALLOW_SHORTCUTS_TO_BUBBLE = "allow_shortcuts_to_bubble";
    private static final boolean ALLOW_SHORTCUT_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.
     * <p>
     * This does not produce a bubble, only adds the metadata based on the notification info.
     */
    static boolean allowAnyNotifToBubble(Context context) {
        return Settings.Secure.getInt(context.getContentResolver(),
@@ -59,6 +79,19 @@ public class BubbleExperimentConfig {
                ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
    }

    /**
     * When true, if the notification is able to bubble via {@link #allowAnyNotifToBubble(Context)}
     * or {@link #allowMessageNotifsToBubble(Context)} or via normal BubbleMetadata, then a new
     * BubbleMetadata object is constructed based on the shortcut info.
     * <p>
     * This does not produce a bubble, only adds the metadata based on shortcut info.
     */
    static boolean useShortcutInfoToBubble(Context context) {
        return Settings.Secure.getInt(context.getContentResolver(),
                ALLOW_SHORTCUTS_TO_BUBBLE,
                ALLOW_SHORTCUT_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
@@ -66,10 +99,9 @@ public class BubbleExperimentConfig {
     */
    static void adjustForExperiments(Context context, NotificationEntry entry,
            Bubble previousBubble) {
        if (entry.getBubbleMetadata() != null) {
            // Has metadata, nothing to do.
            return;
        }

        Notification.BubbleMetadata metadata = null;
        boolean addedMetadata = false;

        Notification notification = entry.getSbn().getNotification();
        boolean isMessage = Notification.MessagingStyle.class.equals(
@@ -77,22 +109,94 @@ public class BubbleExperimentConfig {
        boolean bubbleNotifForExperiment = (isMessage && allowMessageNotifsToBubble(context))
                || allowAnyNotifToBubble(context);

        boolean useShortcutInfo = useShortcutInfoToBubble(context);
        String shortcutId = entry.getSbn().getNotification().getShortcutId();

        if (useShortcutInfo && shortcutId != null) {
            // We don't actually get anything useful from ShortcutInfo so just check existence
            ShortcutInfo info = getShortcutInfo(context, entry.getSbn().getPackageName(),
                    entry.getSbn().getUser(), shortcutId);
            if (info != null) {
                metadata = createForShortcut(context, entry);
            }

            // Replace existing metadata with shortcut, or we're bubbling for experiment
            boolean shouldBubble = entry.getBubbleMetadata() != null || bubbleNotifForExperiment;

            if (shouldBubble && metadata != null) {
                entry.setBubbleMetadata(metadata);
                addedMetadata = true;
            }
        }

        // Didn't get metadata from a shortcut & we're bubbling for experiment
        if (entry.getBubbleMetadata() == null && bubbleNotifForExperiment) {
            metadata = createFromNotif(context, entry);
            if (metadata != null) {
                entry.setBubbleMetadata(metadata);
                addedMetadata = true;
            }
        }

        if (previousBubble != null && addedMetadata) {
            // Update to a previously bubble, set its flag now so the update goes
            // to the bubble.
            entry.setFlagBubble(true);
        }
    }

    static Notification.BubbleMetadata createFromNotif(Context context, NotificationEntry entry) {
        Notification notification = entry.getSbn().getNotification();
        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)
        if (canLaunchIntentInActivityView(context, entry, intent)) {
            return new Notification.BubbleMetadata.Builder()
                    .setDesiredHeight(BUBBLE_HEIGHT)
                    .setIcon(smallIcon)
                            .setIntent(intent);
            entry.setBubbleMetadata(metadata.build());
                    .setIntent(intent)
                    .build();
        }
        return null;
    }

        if (previousBubble != null) {
            // Update to a previously user-created bubble, set its flag now so the update goes
            // to the bubble.
            entry.setFlagBubble(true);
    static Notification.BubbleMetadata createForShortcut(Context context, NotificationEntry entry) {
        // ShortcutInfo does not return an icon, instead a Drawable, lets just use
        // notification icon for BubbleMetadata.
        Icon icon = entry.getSbn().getNotification().getSmallIcon();

        // ShortcutInfo does not return the intent, lets make a fake but identifiable
        // intent so we can still add bubbleMetadata
        if (sDummyShortcutIntent == null) {
            Intent i = new Intent(SHORTCUT_DUMMY_INTENT);
            sDummyShortcutIntent = PendingIntent.getActivity(context, 0, i,
                    PendingIntent.FLAG_UPDATE_CURRENT);
        }
        return new Notification.BubbleMetadata.Builder()
                .setDesiredHeight(BUBBLE_HEIGHT)
                .setIcon(icon)
                .setIntent(sDummyShortcutIntent)
                .build();
    }

    static ShortcutInfo getShortcutInfo(Context context, String packageName, UserHandle user,
            String shortcutId) {
        LauncherApps launcherAppService =
                (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
        if (packageName != null) {
            query.setPackage(packageName);
        }
        if (shortcutId != null) {
            query.setShortcutIds(Arrays.asList(shortcutId));
        }
        query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST);
        List<ShortcutInfo> shortcuts = launcherAppService.getShortcuts(query, user);
        return shortcuts != null && shortcuts.size() > 0
                ? shortcuts.get(0)
                : null;
    }

    static boolean isShortcutIntent(PendingIntent intent) {
        return intent.equals(sDummyShortcutIntent);
    }
}
+11 −3
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.bubbles;
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -243,10 +244,17 @@ public class BubbleView extends FrameLayout {
    }

    Drawable getBubbleDrawable(Context context) {
        if (mBubble.getShortcutInfo() != null && mBubble.usingShortcutInfo()) {
            LauncherApps launcherApps =
                    (LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
            int density = getContext().getResources().getConfiguration().densityDpi;
            return launcherApps.getShortcutIconDrawable(mBubble.getShortcutInfo(), density);
        } else {
            Notification.BubbleMetadata metadata = getEntry().getBubbleMetadata();
            Icon ic = metadata.getIcon();
            return ic.loadDrawable(context);
        }
    }

    BitmapInfo getBadgedBitmap() {
        Bitmap userBadgedBitmap = mBubbleIconFactory.createIconBitmap(