Merge "Remove job notifications on user stop." into udc-dev
This commit is contained in:
@@ -19,6 +19,7 @@ package android.app.job;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.usage.UsageStatsManager;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
@@ -102,6 +103,7 @@ public class JobParameters implements Parcelable {
|
||||
* The user stopped the job via some UI (eg. Task Manager).
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public static final int INTERNAL_STOP_REASON_USER_UI_STOP =
|
||||
JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11.
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_REMOVE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.Notification;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
import android.content.pm.UserPackage;
|
||||
import android.os.UserHandle;
|
||||
@@ -80,7 +81,7 @@ class JobNotificationCoordinator {
|
||||
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);
|
||||
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
|
||||
@@ -100,7 +101,8 @@ class JobNotificationCoordinator {
|
||||
mNotificationDetails.put(hostingContext, details);
|
||||
}
|
||||
|
||||
void removeNotificationAssociation(@NonNull JobServiceContext hostingContext) {
|
||||
void removeNotificationAssociation(@NonNull JobServiceContext hostingContext,
|
||||
@JobParameters.StopReason int stopReason) {
|
||||
final NotificationDetails details = mNotificationDetails.remove(hostingContext);
|
||||
if (details == null) {
|
||||
return;
|
||||
@@ -114,7 +116,10 @@ class JobNotificationCoordinator {
|
||||
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) {
|
||||
// If the user attempted to stop the job/app, then always remove the notification
|
||||
// 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,
|
||||
|
||||
@@ -670,6 +670,7 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
|
||||
+ " [PACKAGE] [JOB_ID]");
|
||||
pw.println(" Trigger immediate timeout of currently executing jobs, as if their");
|
||||
pw.println(" execution timeout had expired.");
|
||||
pw.println(" This is the equivalent of calling `stop -s 3 -i 3`.");
|
||||
pw.println(" Options:");
|
||||
pw.println(" -u or --user: specify which user's job is to be run; the default is");
|
||||
pw.println(" all users");
|
||||
|
||||
@@ -1373,7 +1373,7 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
|
||||
String.valueOf(mRunningJob.getJobId()));
|
||||
}
|
||||
mNotificationCoordinator.removeNotificationAssociation(this);
|
||||
mNotificationCoordinator.removeNotificationAssociation(this, reschedulingStopReason);
|
||||
if (mWakeLock != null) {
|
||||
mWakeLock.release();
|
||||
}
|
||||
|
||||
@@ -725,6 +725,14 @@ package android.app.contentsuggestions {
|
||||
|
||||
}
|
||||
|
||||
package android.app.job {
|
||||
|
||||
public class JobParameters implements android.os.Parcelable {
|
||||
field public static final int INTERNAL_STOP_REASON_USER_UI_STOP = 11; // 0xb
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.app.prediction {
|
||||
|
||||
public final class AppPredictor {
|
||||
|
||||
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.UserHandle;
|
||||
@@ -145,7 +146,7 @@ public class JobNotificationCoordinatorTest {
|
||||
.enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
|
||||
|
||||
coordinator.removeNotificationAssociation(jsc);
|
||||
coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED);
|
||||
verify(mNotificationManagerInternal, never())
|
||||
.cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
|
||||
anyInt(), anyInt());
|
||||
@@ -166,7 +167,7 @@ public class JobNotificationCoordinatorTest {
|
||||
.enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
|
||||
|
||||
coordinator.removeNotificationAssociation(jsc);
|
||||
coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED);
|
||||
verify(mNotificationManagerInternal)
|
||||
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(UserHandle.getUserId(uid)));
|
||||
@@ -288,7 +289,7 @@ public class JobNotificationCoordinatorTest {
|
||||
eq(notificationId2), eq(notification2), eq(UserHandle.getUserId(uid)));
|
||||
|
||||
// Remove the first job. Only the first notification should be removed.
|
||||
coordinator.removeNotificationAssociation(jsc1);
|
||||
coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
|
||||
inOrder.verify(mNotificationManagerInternal)
|
||||
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId1), eq(UserHandle.getUserId(uid)));
|
||||
@@ -296,7 +297,7 @@ public class JobNotificationCoordinatorTest {
|
||||
.cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
|
||||
eq(notificationId2), anyInt());
|
||||
|
||||
coordinator.removeNotificationAssociation(jsc2);
|
||||
coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
|
||||
inOrder.verify(mNotificationManagerInternal)
|
||||
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId2), eq(UserHandle.getUserId(uid)));
|
||||
@@ -331,12 +332,12 @@ public class JobNotificationCoordinatorTest {
|
||||
eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
|
||||
|
||||
// Remove the first job. The notification shouldn't be touched because of the 2nd job.
|
||||
coordinator.removeNotificationAssociation(jsc1);
|
||||
coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
|
||||
inOrder.verify(mNotificationManagerInternal, never())
|
||||
.cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
|
||||
anyInt(), anyInt());
|
||||
|
||||
coordinator.removeNotificationAssociation(jsc2);
|
||||
coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
|
||||
inOrder.verify(mNotificationManagerInternal)
|
||||
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(UserHandle.getUserId(uid)));
|
||||
@@ -372,7 +373,7 @@ public class JobNotificationCoordinatorTest {
|
||||
eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid2)));
|
||||
|
||||
// Remove the first job. Only the first notification should be removed.
|
||||
coordinator.removeNotificationAssociation(jsc1);
|
||||
coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
|
||||
inOrder.verify(mNotificationManagerInternal)
|
||||
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid1), eq(pid), any(),
|
||||
eq(notificationId), eq(UserHandle.getUserId(uid1)));
|
||||
@@ -380,7 +381,7 @@ public class JobNotificationCoordinatorTest {
|
||||
.cancelNotification(anyString(), anyString(), eq(uid2), anyInt(), any(),
|
||||
anyInt(), anyInt());
|
||||
|
||||
coordinator.removeNotificationAssociation(jsc2);
|
||||
coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
|
||||
inOrder.verify(mNotificationManagerInternal)
|
||||
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid2), eq(pid), any(),
|
||||
eq(notificationId), eq(UserHandle.getUserId(uid2)));
|
||||
@@ -417,7 +418,7 @@ public class JobNotificationCoordinatorTest {
|
||||
eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
|
||||
|
||||
// Remove the first job. Only the first notification should be removed.
|
||||
coordinator.removeNotificationAssociation(jsc1);
|
||||
coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
|
||||
inOrder.verify(mNotificationManagerInternal)
|
||||
.cancelNotification(eq(pkg1), eq(pkg1), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(UserHandle.getUserId(uid)));
|
||||
@@ -425,12 +426,73 @@ public class JobNotificationCoordinatorTest {
|
||||
.cancelNotification(anyString(), anyString(), eq(uid), anyInt(), any(),
|
||||
anyInt(), anyInt());
|
||||
|
||||
coordinator.removeNotificationAssociation(jsc2);
|
||||
coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
|
||||
inOrder.verify(mNotificationManagerInternal)
|
||||
.cancelNotification(eq(pkg2), eq(pkg2), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(UserHandle.getUserId(uid)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserStop_SingleJob_DetachOnStop() {
|
||||
final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
|
||||
final JobServiceContext jsc = mock(JobServiceContext.class);
|
||||
final Notification notification = createValidNotification();
|
||||
final int uid = 10123;
|
||||
final int pid = 42;
|
||||
final int notificationId = 23;
|
||||
|
||||
coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification,
|
||||
JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
|
||||
verify(mNotificationManagerInternal)
|
||||
.enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
|
||||
|
||||
coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_USER);
|
||||
verify(mNotificationManagerInternal)
|
||||
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(UserHandle.getUserId(uid)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserStop_MultipleJobs_sameApp_EnqueueSameNotificationId_DetachOnStop() {
|
||||
final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
|
||||
final JobServiceContext jsc1 = mock(JobServiceContext.class);
|
||||
final JobServiceContext jsc2 = mock(JobServiceContext.class);
|
||||
final Notification notification1 = createValidNotification();
|
||||
final Notification notification2 = createValidNotification();
|
||||
final int uid = 10123;
|
||||
final int pid = 42;
|
||||
final int notificationId = 23;
|
||||
|
||||
InOrder inOrder = inOrder(mNotificationManagerInternal);
|
||||
|
||||
coordinator.enqueueNotification(jsc1, TEST_PACKAGE, pid, uid, notificationId, notification1,
|
||||
JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
|
||||
inOrder.verify(mNotificationManagerInternal)
|
||||
.enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(notification1), eq(UserHandle.getUserId(uid)));
|
||||
|
||||
coordinator.enqueueNotification(jsc2, TEST_PACKAGE, pid, uid, notificationId, notification2,
|
||||
JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
|
||||
inOrder.verify(mNotificationManagerInternal, never())
|
||||
.cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
|
||||
anyInt(), anyInt());
|
||||
inOrder.verify(mNotificationManagerInternal)
|
||||
.enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
|
||||
|
||||
// Remove the first job. The notification shouldn't be touched because of the 2nd job.
|
||||
coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_USER);
|
||||
inOrder.verify(mNotificationManagerInternal, never())
|
||||
.cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
|
||||
anyInt(), anyInt());
|
||||
|
||||
coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_USER);
|
||||
inOrder.verify(mNotificationManagerInternal)
|
||||
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
|
||||
eq(notificationId), eq(UserHandle.getUserId(uid)));
|
||||
}
|
||||
|
||||
private Notification createValidNotification() {
|
||||
final Notification notification = mock(Notification.class);
|
||||
doReturn(mock(Icon.class)).when(notification).getSmallIcon();
|
||||
|
||||
Reference in New Issue
Block a user