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

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

Merge changes from topic "b269533590" into udc-dev

* changes:
  Restrict apps from dismissing UIJ notifications.
  Add a new flag to represent a user-initiated job notification.
parents 2f1f3b65 5576565e
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.job;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -58,6 +59,19 @@ public interface JobSchedulerInternal {
     */
    void reportAppUsage(String packageName, int userId);

    /**
     * @return {@code true} if the given notification is associated with any user-initiated jobs.
     */
    boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
            int userId, @NonNull String packageName);

    /**
     * @return {@code true} if the given notification channel is associated with any user-initiated
     * jobs.
     */
    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
            int userId, String packageName);

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

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

    @GuardedBy("mLock")
    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
            int userId, String packageName) {
        return mNotificationCoordinator.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
                notificationChannel, userId, packageName);
    }

    @NonNull
    private JobServiceContext createNewJobServiceContext() {
        return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator,
+71 −9
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.util.Slog;
import android.util.SparseSetArray;

import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus;
import com.android.server.notification.NotificationManagerInternal;

class JobNotificationCoordinator {
@@ -52,16 +53,18 @@ class JobNotificationCoordinator {
        @NonNull
        public final UserPackage userPackage;
        public final int notificationId;
        public final String notificationChannel;
        public final int appPid;
        public final int appUid;
        @JobService.JobEndNotificationPolicy
        public final int jobEndNotificationPolicy;

        NotificationDetails(@NonNull UserPackage userPackage, int appPid, int appUid,
                int notificationId,
                int notificationId, String notificationChannel,
                @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
            this.userPackage = userPackage;
            this.notificationId = notificationId;
            this.notificationChannel = notificationChannel;
            this.appPid = appPid;
            this.appUid = appUid;
            this.jobEndNotificationPolicy = jobEndNotificationPolicy;
@@ -84,14 +87,14 @@ class JobNotificationCoordinator {
            removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED);
        }
        final int userId = UserHandle.getUserId(callingUid);
        // TODO(260848384): ensure apps can't cancel the notification for user-initiated job
        //       eg., by calling NotificationManager.cancel/All or deleting the notification channel
        mNotificationManagerInternal.enqueueNotification(
                packageName, packageName, callingUid, callingPid, /* tag */ null,
                notificationId, notification, userId);
        final JobStatus jobStatus = hostingContext.getRunningJobLocked();
        if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
            notification.flags |= Notification.FLAG_USER_INITIATED_JOB;
        }
        final UserPackage userPackage = UserPackage.of(userId, packageName);
        final NotificationDetails details = new NotificationDetails(
                userPackage, callingPid, callingUid, notificationId, jobEndNotificationPolicy);
                userPackage, callingPid, callingUid, notificationId, notification.getChannelId(),
                jobEndNotificationPolicy);
        SparseSetArray<JobServiceContext> appNotifications = mCurrentAssociations.get(userPackage);
        if (appNotifications == null) {
            appNotifications = new SparseSetArray<>();
@@ -99,6 +102,11 @@ class JobNotificationCoordinator {
        }
        appNotifications.add(notificationId, hostingContext);
        mNotificationDetails.put(hostingContext, details);
        // Call into NotificationManager after internal data structures have been updated since
        // NotificationManager calls into this class to check for any existing associations.
        mNotificationManagerInternal.enqueueNotification(
                packageName, packageName, callingUid, callingPid, /* tag */ null,
                notificationId, notification, userId);
    }

    void removeNotificationAssociation(@NonNull JobServiceContext hostingContext,
@@ -113,6 +121,9 @@ 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);
        boolean stripUijFlag = true;
        ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId);
        if (associatedContexts == null || associatedContexts.isEmpty()) {
            // No more jobs using this notification. Apply the final job stop policy.
@@ -120,14 +131,65 @@ class JobNotificationCoordinator {
            // so the user doesn't get confused about the app state.
            if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE
                    || stopReason == JobParameters.STOP_REASON_USER) {
                final String packageName = details.userPackage.packageName;
                mNotificationManagerInternal.cancelNotification(
                        packageName, packageName, details.appUid, details.appPid, /* tag */ null,
                        details.notificationId, UserHandle.getUserId(details.appUid));
                        details.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);
        }
        if (stripUijFlag) {
            // Strip the user-initiated job flag from the notification.
            mNotificationManagerInternal.removeUserInitiatedJobFlagFromNotification(
                    packageName, details.notificationId, userId);
        }
    }

    boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
            int userId, String packageName) {
        final UserPackage pkgDetails = UserPackage.of(userId, packageName);
        final SparseSetArray<JobServiceContext> associations = mCurrentAssociations.get(pkgDetails);
        if (associations == null) {
            return false;
        }
        final ArraySet<JobServiceContext> associatedContexts = associations.get(notificationId);
        if (associatedContexts == null) {
            return false;
        }

        // Check if any UIJs associated with this package are using the same notification
        for (int i = associatedContexts.size() - 1; i >= 0; i--) {
            final JobStatus jobStatus = associatedContexts.valueAt(i).getRunningJobLocked();
            if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
                return true;
            }
        }
        return false;
    }

    boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel,
            int userId, String packageName) {
        for (int i = mNotificationDetails.size() - 1; i >= 0; i--) {
            final JobServiceContext jsc = mNotificationDetails.keyAt(i);
            final NotificationDetails details = mNotificationDetails.get(jsc);
            // Check if the details for the given notification match and if the associated job
            // was started as a user initiated job
            if (details != null
                    && UserHandle.getUserId(details.appUid) == userId
                    && details.userPackage.packageName.equals(packageName)
                    && details.notificationChannel.equals(notificationChannel)) {
                final JobStatus jobStatus = jsc.getRunningJobLocked();
                if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) {
                    return true;
                }
            }
        }
        return false;
    }

    private void validateNotification(@NonNull String packageName, int callingUid,
            @NonNull Notification notification,
            @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
+24 −0
Original line number Diff line number Diff line
@@ -3711,6 +3711,30 @@ public class JobSchedulerService extends com.android.server.SystemService
            JobSchedulerService.this.reportAppUsage(packageName, userId);
        }

        @Override
        public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
                int userId, 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) {
            if (packageName == null || notificationChannel == null) {
                return false;
            }
            synchronized (mLock) {
                return mConcurrencyManager.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
                        notificationChannel, userId, packageName);
            }
        }

        @Override
        public JobStorePersistStats getPersistStats() {
            synchronized (mLock) {
+3 −1
Original line number Diff line number Diff line
@@ -338,10 +338,12 @@ package android.app {
  }

  public class Notification implements android.os.Parcelable {
    method public boolean isUserInitiatedJob();
    method public boolean shouldShowForegroundImmediately();
    field public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
    field public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
    field public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
    field public static final int FLAG_USER_INITIATED_JOB = 32768; // 0x8000
  }

  public final class NotificationChannel implements android.os.Parcelable {
@@ -351,10 +353,10 @@ package android.app {
    method public void setDeleted(boolean);
    method public void setDeletedTimeMs(long);
    method public void setDemoted(boolean);
    method public void setFgServiceShown(boolean);
    method public void setImportanceLockedByCriticalDeviceFunction(boolean);
    method public void setImportantConversation(boolean);
    method public void setOriginalImportance(int);
    method public void setUserVisibleTaskShown(boolean);
  }

  public final class NotificationChannelGroup implements android.os.Parcelable {
Loading