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

Commit b547e5ed authored by Mady Mellor's avatar Mady Mellor
Browse files

Flag to allow notifs with shortcuts to have BubbleMetadata

- Add shortcut option to experiment class
- Attempts to create BubbleMetadata off of ShortcutInfo, unfortunately
  it doesn't really have a lot of info about the shortcut.... so using
  an intent extra to note that
- Adds new API to ActivityView to support launching shortcuts

Test: manual - enable flags, post a notification with valid shortcut info,
               longpress on it to bubble, tap on bubble
Bug: 143173197
Bug: 138116133
Change-Id: Id631bf4a4b97e0bb1f76760f48dbfc11ffc0630e
parent b11f9312
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(