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

Commit f1eb7928 authored by Varun Shah's avatar Varun Shah Committed by Android (Google) Code Review
Browse files

Merge "Introduce a finer lock for JobNotificationCoordinator." into udc-dev

parents 778e47bb f5932e1b
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -69,8 +69,8 @@ public interface JobSchedulerInternal {
     * @return {@code true} if the given notification channel is associated with any user-initiated
     * jobs.
     */
    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
            int userId, String packageName);
    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
            @NonNull String notificationChannel, int userId, @NonNull String packageName);

    /**
     * Report a snapshot of sync-related jobs back to the sync manager
+3 −5
Original line number Diff line number Diff line
@@ -1925,16 +1925,14 @@ class JobConcurrencyManager {
        return null;
    }

    @GuardedBy("mLock")
    boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, int userId,
            String packageName) {
            @NonNull String packageName) {
        return mNotificationCoordinator.isNotificationAssociatedWithAnyUserInitiatedJobs(
                notificationId, userId, packageName);
    }

    @GuardedBy("mLock")
    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
            int userId, String packageName) {
    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
            @NonNull String notificationChannel, int userId, @NonNull String packageName) {
        return mNotificationCoordinator.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
                notificationChannel, userId, packageName);
    }
+122 −15
Original line number Diff line number Diff line
@@ -27,9 +27,12 @@ import android.content.pm.UserPackage;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseSetArray;

import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus;
import com.android.server.notification.NotificationManagerInternal;
@@ -37,6 +40,14 @@ import com.android.server.notification.NotificationManagerInternal;
class JobNotificationCoordinator {
    private static final String TAG = "JobNotificationCoordinator";

    /**
     * Local lock for independent objects like mUijNotifications and mUijNotificationChannels which
     * don't depend on other JS objects such as JobServiceContext which require the global JS lock.
     *
     * Note: do <b>NOT</b> acquire the global lock while this one is held.
     */
    private final Object mUijLock = new Object();

    /**
     * Mapping of UserPackage -> {notificationId -> List<JobServiceContext>} to track which jobs
     * are associated with each app's notifications.
@@ -49,6 +60,27 @@ class JobNotificationCoordinator {
    private final ArrayMap<JobServiceContext, NotificationDetails> mNotificationDetails =
            new ArrayMap<>();

    /**
     * Mapping of userId -> {packageName, notificationIds} tracking which notifications
     * associated with each app belong to user-initiated jobs.
     *
     * Note: this map can be accessed without holding the main JS lock, so that other services like
     * NotificationManagerService can call into JS and verify associations.
     */
    @GuardedBy("mUijLock")
    private final SparseArrayMap<String, IntArray> mUijNotifications = new SparseArrayMap<>();

    /**
     * Mapping of userId -> {packageName, notificationChannels} tracking which notification channels
     * associated with each app are hosting a user-initiated job notification.
     *
     * Note: this map can be accessed without holding the main JS lock, so that other services like
     * NotificationManagerService can call into JS and verify associations.
     */
    @GuardedBy("mUijLock")
    private final SparseArrayMap<String, ArraySet<String>> mUijNotificationChannels =
            new SparseArrayMap<>();

    private static final class NotificationDetails {
        @NonNull
        public final UserPackage userPackage;
@@ -81,15 +113,24 @@ class JobNotificationCoordinator {
            int callingPid, int callingUid, int notificationId, @NonNull Notification notification,
            @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
        validateNotification(packageName, callingUid, notification, jobEndNotificationPolicy);
        final JobStatus jobStatus = hostingContext.getRunningJobLocked();
        final NotificationDetails oldDetails = mNotificationDetails.get(hostingContext);
        if (oldDetails != null && oldDetails.notificationId != notificationId) {
            // App is switching notification IDs. Remove association with the old one.
            removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED);
            removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED,
                    jobStatus);
        }
        final int userId = UserHandle.getUserId(callingUid);
        final JobStatus jobStatus = hostingContext.getRunningJobLocked();
        if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
            notification.flags |= Notification.FLAG_USER_INITIATED_JOB;
            synchronized (mUijLock) {
                maybeCreateUijNotificationSetsLocked(userId, packageName);
                final IntArray notificationIds = mUijNotifications.get(userId, packageName);
                if (notificationIds.indexOf(notificationId) == -1) {
                    notificationIds.add(notificationId);
                }
                mUijNotificationChannels.get(userId, packageName).add(notification.getChannelId());
            }
        }
        final UserPackage userPackage = UserPackage.of(userId, packageName);
        final NotificationDetails details = new NotificationDetails(
@@ -110,7 +151,7 @@ class JobNotificationCoordinator {
    }

    void removeNotificationAssociation(@NonNull JobServiceContext hostingContext,
            @JobParameters.StopReason int stopReason) {
            @JobParameters.StopReason int stopReason, JobStatus completedJob) {
        final NotificationDetails details = mNotificationDetails.remove(hostingContext);
        if (details == null) {
            return;
@@ -121,10 +162,11 @@ class JobNotificationCoordinator {
            Slog.wtf(TAG, "Association data structures not in sync");
            return;
        }
        final String packageName = details.userPackage.packageName;
        final int userId = UserHandle.getUserId(details.appUid);
        final String packageName = details.userPackage.packageName;
        final int notificationId = details.notificationId;
        boolean stripUijFlag = true;
        ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId);
        ArraySet<JobServiceContext> associatedContexts = associations.get(notificationId);
        if (associatedContexts == null || associatedContexts.isEmpty()) {
            // No more jobs using this notification. Apply the final job stop policy.
            // If the user attempted to stop the job/app, then always remove the notification
@@ -133,23 +175,50 @@ class JobNotificationCoordinator {
                    || stopReason == JobParameters.STOP_REASON_USER) {
                mNotificationManagerInternal.cancelNotification(
                        packageName, packageName, details.appUid, details.appPid, /* tag */ null,
                        details.notificationId, userId);
                        notificationId, userId);
                stripUijFlag = false;
            }
        } else {
            // Strip the UIJ flag only if there are no other UIJs associated with the notification
            stripUijFlag = !isNotificationAssociatedWithAnyUserInitiatedJobs(
                    details.notificationId, userId, packageName);
            stripUijFlag = !isNotificationUsedForAnyUij(userId, packageName, notificationId);
        }
        if (stripUijFlag) {
            // Strip the user-initiated job flag from the notification.
            mNotificationManagerInternal.removeUserInitiatedJobFlagFromNotification(
                    packageName, details.notificationId, userId);
                    packageName, notificationId, userId);
        }

        // Clean up UIJ related objects if the just completed job was a UIJ
        if (completedJob != null && completedJob.startedAsUserInitiatedJob) {
            maybeDeleteNotificationIdAssociation(userId, packageName, notificationId);
            maybeDeleteNotificationChannelAssociation(
                    userId, packageName, details.notificationChannel);
        }
    }

    boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
            int userId, String packageName) {
            int userId, @NonNull String packageName) {
        synchronized (mUijLock) {
            final IntArray notifications = mUijNotifications.get(userId, packageName);
            if (notifications != null) {
                return notifications.indexOf(notificationId) != -1;
            }
            return false;
        }
    }

    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
            @NonNull String notificationChannel, int userId, @NonNull String packageName) {
        synchronized (mUijLock) {
            final ArraySet<String> channels = mUijNotificationChannels.get(userId, packageName);
            if (channels != null) {
                return channels.contains(notificationChannel);
            }
            return false;
        }
    }

    private boolean isNotificationUsedForAnyUij(int userId, String packageName,
            int notificationId) {
        final UserPackage pkgDetails = UserPackage.of(userId, packageName);
        final SparseSetArray<JobServiceContext> associations = mCurrentAssociations.get(pkgDetails);
        if (associations == null) {
@@ -170,8 +239,26 @@ class JobNotificationCoordinator {
        return false;
    }

    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
            int userId, String packageName) {
    private void maybeDeleteNotificationIdAssociation(int userId, String packageName,
            int notificationId) {
        if (isNotificationUsedForAnyUij(userId, packageName, notificationId)) {
            return;
        }

        // Safe to delete - no UIJs for this package are using this notification id
        synchronized (mUijLock) {
            final IntArray notifications = mUijNotifications.get(userId, packageName);
            if (notifications != null) {
                notifications.remove(notifications.indexOf(notificationId));
                if (notifications.size() == 0) {
                    mUijNotifications.delete(userId, packageName);
                }
            }
        }
    }

    private void maybeDeleteNotificationChannelAssociation(int userId, String packageName,
            String notificationChannel) {
        for (int i = mNotificationDetails.size() - 1; i >= 0; i--) {
            final JobServiceContext jsc = mNotificationDetails.keyAt(i);
            final NotificationDetails details = mNotificationDetails.get(jsc);
@@ -183,11 +270,31 @@ class JobNotificationCoordinator {
                    && details.notificationChannel.equals(notificationChannel)) {
                final JobStatus jobStatus = jsc.getRunningJobLocked();
                if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
                    return true;
                    return;
                }
            }
        }
        return false;

        // Safe to delete - no UIJs for this package are using this notification channel
        synchronized (mUijLock) {
            ArraySet<String> channels = mUijNotificationChannels.get(userId, packageName);
            if (channels != null) {
                channels.remove(notificationChannel);
                if (channels.isEmpty()) {
                    mUijNotificationChannels.delete(userId, packageName);
                }
            }
        }
    }

    @GuardedBy("mUijLock")
    private void maybeCreateUijNotificationSetsLocked(int userId, String packageName) {
        if (mUijNotifications.get(userId, packageName) == null) {
            mUijNotifications.add(userId, packageName, new IntArray());
        }
        if (mUijNotificationChannels.get(userId, packageName) == null) {
            mUijNotificationChannels.add(userId, packageName, new ArraySet<>());
        }
    }

    private void validateNotification(@NonNull String packageName, int callingUid,
+6 −10
Original line number Diff line number Diff line
@@ -3713,27 +3713,23 @@ public class JobSchedulerService extends com.android.server.SystemService

        @Override
        public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
                int userId, String packageName) {
                int userId, @NonNull String packageName) {
            if (packageName == null) {
                return false;
            }
            synchronized (mLock) {
            return mConcurrencyManager.isNotificationAssociatedWithAnyUserInitiatedJobs(
                    notificationId, userId, packageName);
        }
        }

        @Override
        public boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
                String notificationChannel, int userId, String packageName) {
                @NonNull String notificationChannel, int userId, @NonNull String packageName) {
            if (packageName == null || notificationChannel == null) {
                return false;
            }
            synchronized (mLock) {
            return mConcurrencyManager.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
                    notificationChannel, userId, packageName);
        }
        }

        @Override
        public JobStorePersistStats getPersistStats() {
+2 −1
Original line number Diff line number Diff line
@@ -1464,7 +1464,8 @@ public final class JobServiceContext implements ServiceConnection {
                    JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
                    String.valueOf(mRunningJob.getJobId()));
        }
        mNotificationCoordinator.removeNotificationAssociation(this, reschedulingStopReason);
        mNotificationCoordinator.removeNotificationAssociation(this,
                reschedulingStopReason, completedJob);
        if (mWakeLock != null) {
            mWakeLock.release();
        }
Loading