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

Commit e0d711f4 authored by Julia Reynolds's avatar Julia Reynolds Committed by Daniel Sandler
Browse files

Grant uri access to notification listeners.

Bug: 9069730
Bug: 63708826
Test: runtest systemui-notification
Change-Id: I87e9dffde35e1afc5e23137df09e0c093d656e23
parent 34b58512
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -1055,11 +1055,10 @@ public class Notification implements Parcelable
    /**
     * {@link #extras} key: A
     * {@link android.content.ContentUris content URI} pointing to an image that can be displayed
     * in the background when the notification is selected. The URI must point to an image stream
     * suitable for passing into
     * in the background when the notification is selected. Used on television platforms.
     * The URI must point to an image stream suitable for passing into
     * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
     * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider
     * URI used for this purpose must require no permissions to read the image data.
     * BitmapFactory.decodeStream}; all other content types will be ignored.
     */
    public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";

+12 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.annotation.TestApi;
import android.app.Notification.Builder;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.drawable.Icon;
import android.net.Uri;
@@ -343,6 +344,14 @@ public class NotificationManager {
     * the same tag and id has already been posted by your application and has not yet been
     * canceled, it will be replaced by the updated information.
     *
     * All {@link android.service.notification.NotificationListenerService listener services} will
     * be granted {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} access to any {@link Uri uris}
     * provided on this notification or the
     * {@link NotificationChannel} this notification is posted to using
     * {@link Context#grantUriPermission(String, Uri, int)}. Permission will be revoked when the
     * notification is canceled, or you can revoke permissions with
     * {@link Context#revokeUriPermission(Uri, int)}.
     *
     * @param tag A string identifier for this notification.  May be {@code null}.
     * @param id An identifier for this notification.  The pair (tag, id) must be unique
     *        within your application.
@@ -363,11 +372,13 @@ public class NotificationManager {
        String pkg = mContext.getPackageName();
        // Fix the notification as best we can.
        Notification.addFieldsFromContext(mContext, notification);

        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
            if (StrictMode.vmFileUriExposureEnabled()) {
                notification.sound.checkFileUriExposed("Notification.sound");
            }

        }
        fixLegacySmallIcon(notification, pkg);
        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
@@ -378,6 +389,7 @@ public class NotificationManager {
        }
        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
        notification.reduceImageSizes(mContext);

        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        boolean isLowRam = am.isLowRamDevice();
        final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam);
+4 −4
Original line number Diff line number Diff line
@@ -376,9 +376,7 @@ abstract public class ManagedServices {
    protected void upgradeXml(final int xmlVersion, final int userId) {}

    private void loadAllowedComponentsFromSettings() {

        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
        for (UserInfo user : userManager.getUsers()) {
        for (UserInfo user : mUm.getUsers()) {
            final ContentResolver cr = mContext.getContentResolver();
            addApprovedList(Settings.Secure.getStringForUser(
                    cr,
@@ -482,7 +480,9 @@ abstract public class ManagedServices {
        for (int i = 0; i < allowedByType.size(); i++) {
            final ArraySet<String> allowed = allowedByType.valueAt(i);
            allowedPackages.addAll(
                    allowed.stream().map(this::getPackageName).collect(Collectors.toList()));
                    allowed.stream().map(this::getPackageName).
                            filter(value -> !TextUtils.isEmpty(value))
                            .collect(Collectors.toList()));
        }
        return allowedPackages;
    }
+81 −12
Original line number Diff line number Diff line
@@ -25,7 +25,9 @@ import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.NotificationListenerService
        .HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
@@ -88,6 +90,7 @@ import android.app.usage.UsageStatsManagerInternal;
import android.companion.ICompanionDeviceManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -115,6 +118,7 @@ import android.os.IDeviceIdleController;
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -206,6 +210,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
@@ -288,6 +293,7 @@ public class NotificationManagerService extends SystemService {
    private ICompanionDeviceManager mCompanionManager;
    private AccessibilityManager mAccessibilityManager;
    private IDeviceIdleController mDeviceIdleController;
    private IBinder mPermissionOwner;

    final IBinder mForegroundToken = new Binder();
    private WorkerHandler mHandler;
@@ -524,7 +530,7 @@ public class NotificationManagerService extends SystemService {
            } catch (FileNotFoundException e) {
                // No data yet
                // Load default managed services approvals
                readDefaultApprovedServices(UserHandle.USER_SYSTEM);
                readDefaultApprovedServices(USER_SYSTEM);
            } catch (IOException e) {
                Log.wtf(TAG, "Unable to read notification policy", e);
            } catch (NumberFormatException e) {
@@ -974,7 +980,7 @@ public class NotificationManagerService extends SystemService {
                            final int enabled = mPackageManager.getApplicationEnabledSetting(
                                    pkgName,
                                    changeUserId != UserHandle.USER_ALL ? changeUserId :
                                            UserHandle.USER_SYSTEM);
                                            USER_SYSTEM);
                            if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                                    || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
                                cancelNotifications = false;
@@ -1061,6 +1067,7 @@ public class NotificationManagerService extends SystemService {
                }
            } else if (action.equals(Intent.ACTION_USER_REMOVED)) {
                final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                mUserProfiles.updateCache(context);
                mZenModeHelper.onUserRemoved(user);
                mRankingHelper.onUserRemoved(user);
                mListeners.onUserRemoved(user);
@@ -1268,7 +1275,7 @@ public class NotificationManagerService extends SystemService {
            NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
            ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
            NotificationUsageStats usageStats, AtomicFile policyFile,
            ActivityManager activityManager, GroupHelper groupHelper) {
            ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am) {
        Resources resources = getContext().getResources();
        mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
                Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1276,7 +1283,7 @@ public class NotificationManagerService extends SystemService {

        mAccessibilityManager =
                (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
        mAm = ActivityManager.getService();
        mAm = am;
        mPackageManager = packageManager;
        mPackageManagerClient = packageManagerClient;
        mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
@@ -1287,6 +1294,11 @@ public class NotificationManagerService extends SystemService {
        mActivityManager = activityManager;
        mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
        try {
            mPermissionOwner = mAm.newUriPermissionOwner("notification");
        } catch (RemoteException e) {
            Slog.w(TAG, "AM dead", e);
        }

        mHandler = new WorkerHandler(looper);
        mRankingThread.start();
@@ -1415,7 +1427,7 @@ public class NotificationManagerService extends SystemService {
                null, snoozeHelper, new NotificationUsageStats(getContext()),
                new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"),
                (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
                getGroupHelper());
                getGroupHelper(), ActivityManager.getService());

        // register for various Intents
        IntentFilter filter = new IntentFilter();
@@ -1749,7 +1761,7 @@ public class NotificationManagerService extends SystemService {
    protected void reportSeen(NotificationRecord r) {
        final int userId = r.sbn.getUserId();
        mAppUsageStats.reportEvent(r.sbn.getPackageName(),
                userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
                userId == UserHandle.USER_ALL ? USER_SYSTEM
                        : userId,
                UsageEvents.Event.NOTIFICATION_SEEN);
    }
@@ -2894,7 +2906,7 @@ public class NotificationManagerService extends SystemService {
            checkCallerIsSystem();
            if (DBG) Slog.d(TAG, "getBackupPayload u=" + user);
            //TODO: http://b/22388012
            if (user != UserHandle.USER_SYSTEM) {
            if (user != USER_SYSTEM) {
                Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
                return null;
            }
@@ -2920,7 +2932,7 @@ public class NotificationManagerService extends SystemService {
                return;
            }
            //TODO: http://b/22388012
            if (user != UserHandle.USER_SYSTEM) {
            if (user != USER_SYSTEM) {
                Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
                return;
            }
@@ -3678,7 +3690,7 @@ public class NotificationManagerService extends SystemService {
            sbn.getNotification().flags =
                    (r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE);
            mRankingHelper.sort(mNotificationList);
            mListeners.notifyPostedLocked(sbn, sbn /* oldSbn */);
            mListeners.notifyPostedLocked(r, sbn /* oldSbn */);
        }
    };

@@ -3707,7 +3719,7 @@ public class NotificationManagerService extends SystemService {
        try {
            final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                    pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                    (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
                    (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
            Notification.addFieldsFromContext(ai, notification);

            int canColorize = mPackageManagerClient.checkPermission(
@@ -4126,6 +4138,8 @@ public class NotificationManagerService extends SystemService {
                        // Make sure we don't lose the foreground service state.
                        notification.flags |=
                                old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
                        // revoke uri permissions for changed uris
                        revokeUriPermissions(r, old);
                        r.isUpdate = true;
                    }

@@ -4147,7 +4161,7 @@ public class NotificationManagerService extends SystemService {

                    if (notification.getSmallIcon() != null) {
                        StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                        mListeners.notifyPostedLocked(n, oldSbn);
                        mListeners.notifyPostedLocked(r, oldSbn);
                        if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) {
                            mHandler.post(new Runnable() {
                                @Override
@@ -4912,6 +4926,9 @@ public class NotificationManagerService extends SystemService {
            r.recordDismissalSurface(NotificationStats.DISMISSAL_OTHER);
        }

        // Revoke permissions
        revokeUriPermissions(null, r);

        // tell the app
        if (sendDelete) {
            if (r.getNotification().deleteIntent != null) {
@@ -5009,6 +5026,30 @@ public class NotificationManagerService extends SystemService {
                r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now), listenerName);
    }

    void revokeUriPermissions(NotificationRecord newRecord, NotificationRecord oldRecord) {
        Set<Uri> oldUris = oldRecord.getNotificationUris();
        Set<Uri> newUris = newRecord == null ? new HashSet<>() : newRecord.getNotificationUris();
        oldUris.removeAll(newUris);

        long ident = Binder.clearCallingIdentity();
        try {
            for (Uri uri : oldUris) {
                if (uri != null) {
                    int notiUserId = oldRecord.getUserId();
                    int sourceUserId = notiUserId == USER_ALL ? USER_SYSTEM
                            : ContentProvider.getUserIdFromUri(uri, notiUserId);
                    uri = ContentProvider.getUriWithoutUserId(uri);
                    mAm.revokeUriPermissionFromOwner(mPermissionOwner,
                            uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId);
                }
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Count not revoke uri permissions", e);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    /**
     * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
     * and none of the {@code mustNotHaveFlags}.
@@ -5851,10 +5892,13 @@ public class NotificationManagerService extends SystemService {
         * but isn't anymore.
         */
        @GuardedBy("mNotificationLock")
        public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
        public void notifyPostedLocked(NotificationRecord r, StatusBarNotification oldSbn) {
            // Lazily initialized snapshots of the notification.
            StatusBarNotification sbn = r.sbn;
            TrimCache trimCache = new TrimCache(sbn);

            Set<Uri> uris = r.getNotificationUris();

            for (final ManagedServiceInfo info : getServices()) {
                boolean sbnVisible = isVisibleToListener(sbn, info);
                boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
@@ -5877,6 +5921,9 @@ public class NotificationManagerService extends SystemService {
                    continue;
                }

                grantUriPermissions(uris, sbn.getUserId(), info.component.getPackageName(),
                        info.userid);

                final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
                mHandler.post(new Runnable() {
                    @Override
@@ -5887,6 +5934,28 @@ public class NotificationManagerService extends SystemService {
            }
        }

        private void grantUriPermissions(Set<Uri> uris, int notiUserId, String listenerPkg,
                int listenerUserId) {
            long ident = Binder.clearCallingIdentity();
            try {
                for (Uri uri : uris) {
                    if (uri != null) {
                        int sourceUserId = notiUserId == USER_ALL ? USER_SYSTEM
                                : ContentProvider.getUserIdFromUri(uri, notiUserId);
                        uri = ContentProvider.getUriWithoutUserId(uri);
                        mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(),
                                listenerPkg,
                                uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId,
                                listenerUserId == USER_ALL ? USER_SYSTEM : listenerUserId);
                    }
                }
            } catch (RemoteException e) {
                Log.e(TAG, "Count not grant uri permission to " + listenerPkg, e);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        /**
         * asynchronously notify all listeners about a removed notification
         */
+39 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.Adjustment;
@@ -47,6 +48,7 @@ import android.service.notification.NotificationStats;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
@@ -64,6 +66,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * Holds data about notifications that should not be shared with the
@@ -929,6 +932,42 @@ public final class NotificationRecord {
        mStats.setViewedSettings();
    }

    public Set<Uri> getNotificationUris() {
        Notification notification = getNotification();
        Set<Uri> uris = new ArraySet<>();

        if (notification.sound != null) {
            uris.add(notification.sound);
        }
        if (notification.getChannelId() != null) {
            NotificationChannel channel = getChannel();
            if (channel != null && channel.getSound() != null) {
                uris.add(channel.getSound());
            }
        }
        if (notification.extras.containsKey(Notification.EXTRA_AUDIO_CONTENTS_URI)) {
            uris.add(notification.extras.getParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI));
        }
        if (notification.extras.containsKey(Notification.EXTRA_BACKGROUND_IMAGE_URI)) {
            uris.add(notification.extras.getParcelable(Notification.EXTRA_BACKGROUND_IMAGE_URI));
        }
        if (Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) {
            Parcelable[] newMessages =
                    notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
            List<Notification.MessagingStyle.Message> messages
                    = Notification.MessagingStyle.Message.getMessagesFromBundleArray(newMessages);
            Parcelable[] histMessages =
                    notification.extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
            messages.addAll(
                    Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages));
            for (Notification.MessagingStyle.Message message : messages) {
                uris.add(message.getDataUri());
            }
        }

        return uris;
    }

    public LogMaker getLogMaker(long now) {
        if (mLogMaker == null) {
            // initialize fields that only change on update (so a new record)
Loading