Loading apex/jobscheduler/framework/java/android/app/job/JobService.java +5 −0 Original line number Diff line number Diff line Loading @@ -416,6 +416,11 @@ public abstract class JobService extends Service { * JobScheduler will not remember this notification after the job has finished running, * so apps must call this every time the job is started (if required or desired). * * <p> * If separate jobs use the same notification ID with this API, the most recently provided * notification will be shown to the user, and the * {@code jobEndNotificationPolicy} of the last job to stop will be applied. * * @param params The parameters identifying this job, as supplied to * the job in the {@link #onStartJob(JobParameters)} callback. * @param notificationId The ID for this notification, as per Loading apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +9 −5 Original line number Diff line number Diff line Loading @@ -193,6 +193,7 @@ class JobConcurrencyManager { } private final Object mLock; private final JobNotificationCoordinator mNotificationCoordinator; private final JobSchedulerService mService; private final Context mContext; private final Handler mHandler; Loading Loading @@ -418,6 +419,7 @@ class JobConcurrencyManager { mLock = mService.getLock(); mContext = service.getTestableContext(); mInjector = injector; mNotificationCoordinator = new JobNotificationCoordinator(); mHandler = JobSchedulerBackgroundThread.getHandler(); Loading Loading @@ -451,7 +453,8 @@ class JobConcurrencyManager { ServiceManager.getService(BatteryStats.SERVICE_NAME)); for (int i = 0; i < STANDARD_CONCURRENCY_LIMIT; i++) { mIdleContexts.add( mInjector.createJobServiceContext(mService, this, batteryStats, mInjector.createJobServiceContext(mService, this, mNotificationCoordinator, batteryStats, mService.mJobPackageTracker, mContext.getMainLooper())); } } Loading Loading @@ -1687,7 +1690,7 @@ class JobConcurrencyManager { @NonNull private JobServiceContext createNewJobServiceContext() { return mInjector.createJobServiceContext(mService, this, return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator, IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)), mService.mJobPackageTracker, mContext.getMainLooper()); Loading Loading @@ -2612,10 +2615,11 @@ class JobConcurrencyManager { static class Injector { @NonNull JobServiceContext createJobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats, JobConcurrencyManager concurrencyManager, JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) { return new JobServiceContext(service, concurrencyManager, batteryStats, tracker, looper); return new JobServiceContext(service, concurrencyManager, notificationCoordinator, batteryStats, tracker, looper); } } } apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.job; import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_DETACH; import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_REMOVE; import android.annotation.NonNull; import android.app.Notification; import android.app.job.JobService; import android.content.pm.UserPackage; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.SparseSetArray; import com.android.server.LocalServices; import com.android.server.notification.NotificationManagerInternal; class JobNotificationCoordinator { private static final String TAG = "JobNotificationCoordinator"; /** * Mapping of UserPackage -> {notificationId -> List<JobServiceContext>} to track which jobs * are associated with each app's notifications. */ private final ArrayMap<UserPackage, SparseSetArray<JobServiceContext>> mCurrentAssociations = new ArrayMap<>(); /** * Set of NotificationDetails for each running job. */ private final ArrayMap<JobServiceContext, NotificationDetails> mNotificationDetails = new ArrayMap<>(); private static final class NotificationDetails { @NonNull public final UserPackage userPackage; public final int notificationId; public final int appPid; public final int appUid; @JobService.JobEndNotificationPolicy public final int jobEndNotificationPolicy; NotificationDetails(@NonNull UserPackage userPackage, int appPid, int appUid, int notificationId, @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) { this.userPackage = userPackage; this.notificationId = notificationId; this.appPid = appPid; this.appUid = appUid; this.jobEndNotificationPolicy = jobEndNotificationPolicy; } } private final NotificationManagerInternal mNotificationManagerInternal; JobNotificationCoordinator() { mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class); } void enqueueNotification(@NonNull JobServiceContext hostingContext, @NonNull String packageName, int callingPid, int callingUid, int notificationId, @NonNull Notification notification, @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) { validateNotification(packageName, callingUid, notification, jobEndNotificationPolicy); 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); } 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 UserPackage userPackage = UserPackage.of(userId, packageName); final NotificationDetails details = new NotificationDetails( userPackage, callingPid, callingUid, notificationId, jobEndNotificationPolicy); SparseSetArray<JobServiceContext> appNotifications = mCurrentAssociations.get(userPackage); if (appNotifications == null) { appNotifications = new SparseSetArray<>(); mCurrentAssociations.put(userPackage, appNotifications); } appNotifications.add(notificationId, hostingContext); mNotificationDetails.put(hostingContext, details); } void removeNotificationAssociation(@NonNull JobServiceContext hostingContext) { final NotificationDetails details = mNotificationDetails.remove(hostingContext); if (details == null) { return; } final SparseSetArray<JobServiceContext> associations = mCurrentAssociations.get(details.userPackage); if (associations == null || !associations.remove(details.notificationId, hostingContext)) { Slog.wtf(TAG, "Association data structures not in sync"); return; } ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId); if (associatedContexts == null || associatedContexts.isEmpty()) { // No more jobs using this notification. Apply the final job stop policy. if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) { final String packageName = details.userPackage.packageName; mNotificationManagerInternal.cancelNotification( packageName, packageName, details.appUid, details.appPid, /* tag */ null, details.notificationId, UserHandle.getUserId(details.appUid)); } } } private void validateNotification(@NonNull String packageName, int callingUid, @NonNull Notification notification, @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) { if (notification == null) { throw new NullPointerException("notification"); } if (notification.getSmallIcon() == null) { throw new IllegalArgumentException("small icon required"); } if (null == mNotificationManagerInternal.getNotificationChannel( packageName, callingUid, notification.getChannelId())) { throw new IllegalArgumentException("invalid notification channel"); } if (jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_DETACH && jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_REMOVE) { throw new IllegalArgumentException("invalid job end notification policy"); } } } apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +7 −40 Original line number Diff line number Diff line Loading @@ -17,8 +17,6 @@ package com.android.server.job; import static android.app.job.JobInfo.getPriorityString; import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_DETACH; import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_REMOVE; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; Loading Loading @@ -61,7 +59,6 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.job.controllers.JobStatus; import com.android.server.notification.NotificationManagerInternal; import com.android.server.tare.EconomicPolicy; import com.android.server.tare.EconomyManagerInternal; import com.android.server.tare.JobSchedulerEconomicPolicy; Loading Loading @@ -113,6 +110,7 @@ public final class JobServiceContext implements ServiceConnection { /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */ private final JobCompletedListener mCompletedListener; private final JobConcurrencyManager mJobConcurrencyManager; private final JobNotificationCoordinator mNotificationCoordinator; private final JobSchedulerService mService; /** Used for service binding, etc. */ private final Context mContext; Loading @@ -121,7 +119,6 @@ public final class JobServiceContext implements ServiceConnection { private final EconomyManagerInternal mEconomyManagerInternal; private final JobPackageTracker mJobPackageTracker; private final PowerManager mPowerManager; private final NotificationManagerInternal mNotificationManagerInternal; private PowerManager.WakeLock mWakeLock; // Execution state. Loading Loading @@ -174,11 +171,6 @@ public final class JobServiceContext implements ServiceConnection { /** The absolute maximum amount of time the job can run */ private long mMaxExecutionTimeMillis; private int mNotificationId; private Notification mNotification; private int mNotificationPid; private int mNotificationJobStopPolicy; /** * The stop reason for a pending cancel. If there's not pending cancel, then the value should be * {@link JobParameters#STOP_REASON_UNDEFINED}. Loading Loading @@ -254,16 +246,17 @@ public final class JobServiceContext implements ServiceConnection { } JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager, JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) { mContext = service.getContext(); mLock = service.getLock(); mService = service; mBatteryStats = batteryStats; mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class); mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class); mJobPackageTracker = tracker; mCallbackHandler = new JobServiceHandler(looper); mJobConcurrencyManager = concurrencyManager; mNotificationCoordinator = notificationCoordinator; mCompletedListener = service; mPowerManager = mContext.getSystemService(PowerManager.class); mAvailable = true; Loading Loading @@ -624,29 +617,10 @@ public final class JobServiceContext implements ServiceConnection { Slog.wtfStack(TAG, "Calling UID isn't the same as running job's UID..."); throw new SecurityException("Can't post notification on behalf of another app"); } if (notification == null) { throw new NullPointerException("notification"); } if (notification.getSmallIcon() == null) { throw new IllegalArgumentException("small icon required"); } final String callingPkgName = mRunningJob.getServiceComponent().getPackageName(); if (null == mNotificationManagerInternal.getNotificationChannel( callingPkgName, callingUid, notification.getChannelId())) { throw new IllegalArgumentException("invalid notification channel"); } if (jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_DETACH && jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_REMOVE) { throw new IllegalArgumentException("invalid job end notification policy"); } // TODO(260848384): ensure apps can't cancel the notification for user-initiated job mNotificationManagerInternal.enqueueNotification( callingPkgName, callingPkgName, callingUid, callingPid, /* tag */ null, notificationId, notification, UserHandle.getUserId(callingUid)); mNotificationId = notificationId; mNotification = notification; mNotificationPid = callingPid; mNotificationJobStopPolicy = jobEndNotificationPolicy; mNotificationCoordinator.enqueueNotification(this, callingPkgName, callingPid, callingUid, notificationId, notification, jobEndNotificationPolicy); } } finally { Binder.restoreCallingIdentity(ident); Loading Loading @@ -1179,13 +1153,7 @@ public final class JobServiceContext implements ServiceConnection { JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, String.valueOf(mRunningJob.getJobId())); } if (mNotification != null && mNotificationJobStopPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) { final String callingPkgName = completedJob.getServiceComponent().getPackageName(); mNotificationManagerInternal.cancelNotification( callingPkgName, callingPkgName, completedJob.getUid(), mNotificationPid, /* tag */ null, mNotificationId, UserHandle.getUserId(completedJob.getUid())); } mNotificationCoordinator.removeNotificationAssociation(this); if (mWakeLock != null) { mWakeLock.release(); } Loading @@ -1203,7 +1171,6 @@ public final class JobServiceContext implements ServiceConnection { mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; mPendingInternalStopReason = 0; mPendingDebugStopReason = null; mNotification = null; removeOpTimeOutLocked(); if (completedJob.isUserVisibleJob()) { mService.informObserversOfUserVisibleJobChange(this, completedJob, false); Loading services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -113,7 +113,8 @@ public final class JobConcurrencyManagerTest { @Override JobServiceContext createJobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats, JobConcurrencyManager concurrencyManager, JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) { final JobServiceContext context = mock(JobServiceContext.class); doAnswer((Answer<Boolean>) invocationOnMock -> { Loading Loading
apex/jobscheduler/framework/java/android/app/job/JobService.java +5 −0 Original line number Diff line number Diff line Loading @@ -416,6 +416,11 @@ public abstract class JobService extends Service { * JobScheduler will not remember this notification after the job has finished running, * so apps must call this every time the job is started (if required or desired). * * <p> * If separate jobs use the same notification ID with this API, the most recently provided * notification will be shown to the user, and the * {@code jobEndNotificationPolicy} of the last job to stop will be applied. * * @param params The parameters identifying this job, as supplied to * the job in the {@link #onStartJob(JobParameters)} callback. * @param notificationId The ID for this notification, as per Loading
apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +9 −5 Original line number Diff line number Diff line Loading @@ -193,6 +193,7 @@ class JobConcurrencyManager { } private final Object mLock; private final JobNotificationCoordinator mNotificationCoordinator; private final JobSchedulerService mService; private final Context mContext; private final Handler mHandler; Loading Loading @@ -418,6 +419,7 @@ class JobConcurrencyManager { mLock = mService.getLock(); mContext = service.getTestableContext(); mInjector = injector; mNotificationCoordinator = new JobNotificationCoordinator(); mHandler = JobSchedulerBackgroundThread.getHandler(); Loading Loading @@ -451,7 +453,8 @@ class JobConcurrencyManager { ServiceManager.getService(BatteryStats.SERVICE_NAME)); for (int i = 0; i < STANDARD_CONCURRENCY_LIMIT; i++) { mIdleContexts.add( mInjector.createJobServiceContext(mService, this, batteryStats, mInjector.createJobServiceContext(mService, this, mNotificationCoordinator, batteryStats, mService.mJobPackageTracker, mContext.getMainLooper())); } } Loading Loading @@ -1687,7 +1690,7 @@ class JobConcurrencyManager { @NonNull private JobServiceContext createNewJobServiceContext() { return mInjector.createJobServiceContext(mService, this, return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator, IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)), mService.mJobPackageTracker, mContext.getMainLooper()); Loading Loading @@ -2612,10 +2615,11 @@ class JobConcurrencyManager { static class Injector { @NonNull JobServiceContext createJobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats, JobConcurrencyManager concurrencyManager, JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) { return new JobServiceContext(service, concurrencyManager, batteryStats, tracker, looper); return new JobServiceContext(service, concurrencyManager, notificationCoordinator, batteryStats, tracker, looper); } } }
apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.job; import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_DETACH; import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_REMOVE; import android.annotation.NonNull; import android.app.Notification; import android.app.job.JobService; import android.content.pm.UserPackage; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.SparseSetArray; import com.android.server.LocalServices; import com.android.server.notification.NotificationManagerInternal; class JobNotificationCoordinator { private static final String TAG = "JobNotificationCoordinator"; /** * Mapping of UserPackage -> {notificationId -> List<JobServiceContext>} to track which jobs * are associated with each app's notifications. */ private final ArrayMap<UserPackage, SparseSetArray<JobServiceContext>> mCurrentAssociations = new ArrayMap<>(); /** * Set of NotificationDetails for each running job. */ private final ArrayMap<JobServiceContext, NotificationDetails> mNotificationDetails = new ArrayMap<>(); private static final class NotificationDetails { @NonNull public final UserPackage userPackage; public final int notificationId; public final int appPid; public final int appUid; @JobService.JobEndNotificationPolicy public final int jobEndNotificationPolicy; NotificationDetails(@NonNull UserPackage userPackage, int appPid, int appUid, int notificationId, @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) { this.userPackage = userPackage; this.notificationId = notificationId; this.appPid = appPid; this.appUid = appUid; this.jobEndNotificationPolicy = jobEndNotificationPolicy; } } private final NotificationManagerInternal mNotificationManagerInternal; JobNotificationCoordinator() { mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class); } void enqueueNotification(@NonNull JobServiceContext hostingContext, @NonNull String packageName, int callingPid, int callingUid, int notificationId, @NonNull Notification notification, @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) { validateNotification(packageName, callingUid, notification, jobEndNotificationPolicy); 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); } 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 UserPackage userPackage = UserPackage.of(userId, packageName); final NotificationDetails details = new NotificationDetails( userPackage, callingPid, callingUid, notificationId, jobEndNotificationPolicy); SparseSetArray<JobServiceContext> appNotifications = mCurrentAssociations.get(userPackage); if (appNotifications == null) { appNotifications = new SparseSetArray<>(); mCurrentAssociations.put(userPackage, appNotifications); } appNotifications.add(notificationId, hostingContext); mNotificationDetails.put(hostingContext, details); } void removeNotificationAssociation(@NonNull JobServiceContext hostingContext) { final NotificationDetails details = mNotificationDetails.remove(hostingContext); if (details == null) { return; } final SparseSetArray<JobServiceContext> associations = mCurrentAssociations.get(details.userPackage); if (associations == null || !associations.remove(details.notificationId, hostingContext)) { Slog.wtf(TAG, "Association data structures not in sync"); return; } ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId); if (associatedContexts == null || associatedContexts.isEmpty()) { // No more jobs using this notification. Apply the final job stop policy. if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) { final String packageName = details.userPackage.packageName; mNotificationManagerInternal.cancelNotification( packageName, packageName, details.appUid, details.appPid, /* tag */ null, details.notificationId, UserHandle.getUserId(details.appUid)); } } } private void validateNotification(@NonNull String packageName, int callingUid, @NonNull Notification notification, @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) { if (notification == null) { throw new NullPointerException("notification"); } if (notification.getSmallIcon() == null) { throw new IllegalArgumentException("small icon required"); } if (null == mNotificationManagerInternal.getNotificationChannel( packageName, callingUid, notification.getChannelId())) { throw new IllegalArgumentException("invalid notification channel"); } if (jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_DETACH && jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_REMOVE) { throw new IllegalArgumentException("invalid job end notification policy"); } } }
apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +7 −40 Original line number Diff line number Diff line Loading @@ -17,8 +17,6 @@ package com.android.server.job; import static android.app.job.JobInfo.getPriorityString; import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_DETACH; import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_REMOVE; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; Loading Loading @@ -61,7 +59,6 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.job.controllers.JobStatus; import com.android.server.notification.NotificationManagerInternal; import com.android.server.tare.EconomicPolicy; import com.android.server.tare.EconomyManagerInternal; import com.android.server.tare.JobSchedulerEconomicPolicy; Loading Loading @@ -113,6 +110,7 @@ public final class JobServiceContext implements ServiceConnection { /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */ private final JobCompletedListener mCompletedListener; private final JobConcurrencyManager mJobConcurrencyManager; private final JobNotificationCoordinator mNotificationCoordinator; private final JobSchedulerService mService; /** Used for service binding, etc. */ private final Context mContext; Loading @@ -121,7 +119,6 @@ public final class JobServiceContext implements ServiceConnection { private final EconomyManagerInternal mEconomyManagerInternal; private final JobPackageTracker mJobPackageTracker; private final PowerManager mPowerManager; private final NotificationManagerInternal mNotificationManagerInternal; private PowerManager.WakeLock mWakeLock; // Execution state. Loading Loading @@ -174,11 +171,6 @@ public final class JobServiceContext implements ServiceConnection { /** The absolute maximum amount of time the job can run */ private long mMaxExecutionTimeMillis; private int mNotificationId; private Notification mNotification; private int mNotificationPid; private int mNotificationJobStopPolicy; /** * The stop reason for a pending cancel. If there's not pending cancel, then the value should be * {@link JobParameters#STOP_REASON_UNDEFINED}. Loading Loading @@ -254,16 +246,17 @@ public final class JobServiceContext implements ServiceConnection { } JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager, JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) { mContext = service.getContext(); mLock = service.getLock(); mService = service; mBatteryStats = batteryStats; mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class); mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class); mJobPackageTracker = tracker; mCallbackHandler = new JobServiceHandler(looper); mJobConcurrencyManager = concurrencyManager; mNotificationCoordinator = notificationCoordinator; mCompletedListener = service; mPowerManager = mContext.getSystemService(PowerManager.class); mAvailable = true; Loading Loading @@ -624,29 +617,10 @@ public final class JobServiceContext implements ServiceConnection { Slog.wtfStack(TAG, "Calling UID isn't the same as running job's UID..."); throw new SecurityException("Can't post notification on behalf of another app"); } if (notification == null) { throw new NullPointerException("notification"); } if (notification.getSmallIcon() == null) { throw new IllegalArgumentException("small icon required"); } final String callingPkgName = mRunningJob.getServiceComponent().getPackageName(); if (null == mNotificationManagerInternal.getNotificationChannel( callingPkgName, callingUid, notification.getChannelId())) { throw new IllegalArgumentException("invalid notification channel"); } if (jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_DETACH && jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_REMOVE) { throw new IllegalArgumentException("invalid job end notification policy"); } // TODO(260848384): ensure apps can't cancel the notification for user-initiated job mNotificationManagerInternal.enqueueNotification( callingPkgName, callingPkgName, callingUid, callingPid, /* tag */ null, notificationId, notification, UserHandle.getUserId(callingUid)); mNotificationId = notificationId; mNotification = notification; mNotificationPid = callingPid; mNotificationJobStopPolicy = jobEndNotificationPolicy; mNotificationCoordinator.enqueueNotification(this, callingPkgName, callingPid, callingUid, notificationId, notification, jobEndNotificationPolicy); } } finally { Binder.restoreCallingIdentity(ident); Loading Loading @@ -1179,13 +1153,7 @@ public final class JobServiceContext implements ServiceConnection { JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, String.valueOf(mRunningJob.getJobId())); } if (mNotification != null && mNotificationJobStopPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) { final String callingPkgName = completedJob.getServiceComponent().getPackageName(); mNotificationManagerInternal.cancelNotification( callingPkgName, callingPkgName, completedJob.getUid(), mNotificationPid, /* tag */ null, mNotificationId, UserHandle.getUserId(completedJob.getUid())); } mNotificationCoordinator.removeNotificationAssociation(this); if (mWakeLock != null) { mWakeLock.release(); } Loading @@ -1203,7 +1171,6 @@ public final class JobServiceContext implements ServiceConnection { mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; mPendingInternalStopReason = 0; mPendingDebugStopReason = null; mNotification = null; removeOpTimeOutLocked(); if (completedJob.isUserVisibleJob()) { mService.informObserversOfUserVisibleJobChange(this, completedJob, false); Loading
services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -113,7 +113,8 @@ public final class JobConcurrencyManagerTest { @Override JobServiceContext createJobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats, JobConcurrencyManager concurrencyManager, JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) { final JobServiceContext context = mock(JobServiceContext.class); doAnswer((Answer<Boolean>) invocationOnMock -> { Loading