Implement JS <-> TaskManager user-visible job link.
Implement the code required to inform Task Manager of user-visible jobs and respond to stops from the user. Bug: 255767350 Test: Manually start jobs and interact with the Task Manager Change-Id: I6f815134e896aaeb2a3682b73076723b3e99561f
This commit is contained in:
@@ -36,7 +36,7 @@ import java.util.List;
|
||||
* Note android.app.job is the better package to put this class, but we can't move it there
|
||||
* because that'd break robolectric. Grr.
|
||||
*
|
||||
* @hide
|
||||
* @hide
|
||||
*/
|
||||
public class JobSchedulerImpl extends JobScheduler {
|
||||
IJobScheduler mBinder;
|
||||
@@ -149,7 +149,10 @@ public class JobSchedulerImpl extends JobScheduler {
|
||||
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
|
||||
@Override
|
||||
public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
|
||||
// TODO(255767350): implement
|
||||
try {
|
||||
mBinder.registerUserVisibleJobObserver(observer);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(allOf = {
|
||||
@@ -157,7 +160,10 @@ public class JobSchedulerImpl extends JobScheduler {
|
||||
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
|
||||
@Override
|
||||
public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
|
||||
// TODO(255767350): implement
|
||||
try {
|
||||
mBinder.unregisterUserVisibleJobObserver(observer);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(allOf = {
|
||||
@@ -165,6 +171,9 @@ public class JobSchedulerImpl extends JobScheduler {
|
||||
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
|
||||
@Override
|
||||
public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
|
||||
// TODO(255767350): implement
|
||||
try {
|
||||
mBinder.stopUserVisibleJobsForUser(packageName, userId);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.app.job;
|
||||
|
||||
import android.app.job.IUserVisibleJobObserver;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobSnapshot;
|
||||
import android.app.job.JobWorkItem;
|
||||
@@ -37,4 +38,10 @@ interface IJobScheduler {
|
||||
boolean hasRunLongJobsPermission(String packageName, int userId);
|
||||
List<JobInfo> getStartedJobs();
|
||||
ParceledListSlice getAllJobSnapshots();
|
||||
@EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
|
||||
void registerUserVisibleJobObserver(in IUserVisibleJobObserver observer);
|
||||
@EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
|
||||
void unregisterUserVisibleJobObserver(in IUserVisibleJobObserver observer);
|
||||
@EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
|
||||
void stopUserVisibleJobsForUser(String packageName, int userId);
|
||||
}
|
||||
|
||||
@@ -98,6 +98,12 @@ public class JobParameters implements Parcelable {
|
||||
*/
|
||||
public static final int INTERNAL_STOP_REASON_SUCCESSFUL_FINISH =
|
||||
JobProtoEnums.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH; // 10.
|
||||
/**
|
||||
* The user stopped the job via some UI (eg. Task Manager).
|
||||
* @hide
|
||||
*/
|
||||
public static final int INTERNAL_STOP_REASON_USER_UI_STOP =
|
||||
JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11.
|
||||
|
||||
/**
|
||||
* All the stop reason codes. This should be regarded as an immutable array at runtime.
|
||||
@@ -121,6 +127,7 @@ public class JobParameters implements Parcelable {
|
||||
INTERNAL_STOP_REASON_DATA_CLEARED,
|
||||
INTERNAL_STOP_REASON_RTC_UPDATED,
|
||||
INTERNAL_STOP_REASON_SUCCESSFUL_FINISH,
|
||||
INTERNAL_STOP_REASON_USER_UI_STOP,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -141,6 +148,7 @@ public class JobParameters implements Parcelable {
|
||||
case INTERNAL_STOP_REASON_DATA_CLEARED: return "data_cleared";
|
||||
case INTERNAL_STOP_REASON_RTC_UPDATED: return "rtc_updated";
|
||||
case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish";
|
||||
case INTERNAL_STOP_REASON_USER_UI_STOP: return "user_ui_stop";
|
||||
default: return "unknown:" + reasonCode;
|
||||
}
|
||||
}
|
||||
@@ -230,7 +238,7 @@ public class JobParameters implements Parcelable {
|
||||
public static final int STOP_REASON_APP_STANDBY = 12;
|
||||
/**
|
||||
* The user stopped the job. This can happen either through force-stop, adb shell commands,
|
||||
* or uninstalling.
|
||||
* uninstalling, or some other UI.
|
||||
*/
|
||||
public static final int STOP_REASON_USER = 13;
|
||||
/** The system is doing some processing that requires stopping this job. */
|
||||
|
||||
@@ -1183,6 +1183,22 @@ class JobConcurrencyManager {
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
void stopUserVisibleJobsLocked(int userId, @NonNull String packageName,
|
||||
@JobParameters.StopReason int reason, int internalReasonCode) {
|
||||
for (int i = mActiveServices.size() - 1; i >= 0; --i) {
|
||||
final JobServiceContext jsc = mActiveServices.get(i);
|
||||
final JobStatus jobStatus = jsc.getRunningJobLocked();
|
||||
|
||||
if (jobStatus != null && userId == jobStatus.getSourceUserId()
|
||||
&& jobStatus.getSourcePackageName().equals(packageName)
|
||||
&& jobStatus.isUserVisibleJob()) {
|
||||
jsc.cancelExecutingJobLocked(reason, internalReasonCode,
|
||||
JobParameters.getInternalReasonCodeDescription(internalReasonCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
void stopNonReadyActiveJobsLocked() {
|
||||
for (int i = 0; i < mActiveServices.size(); i++) {
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
package com.android.server.job;
|
||||
|
||||
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
|
||||
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
|
||||
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import android.annotation.EnforcePermission;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
@@ -31,6 +34,7 @@ import android.app.AppGlobals;
|
||||
import android.app.IUidObserver;
|
||||
import android.app.compat.CompatChanges;
|
||||
import android.app.job.IJobScheduler;
|
||||
import android.app.job.IUserVisibleJobObserver;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobProtoEnums;
|
||||
@@ -38,6 +42,7 @@ import android.app.job.JobScheduler;
|
||||
import android.app.job.JobService;
|
||||
import android.app.job.JobSnapshot;
|
||||
import android.app.job.JobWorkItem;
|
||||
import android.app.job.UserVisibleJobSummary;
|
||||
import android.app.usage.UsageStatsManager;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.compat.annotation.ChangeId;
|
||||
@@ -68,6 +73,7 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
@@ -248,6 +254,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
static final int MSG_UID_IDLE = 7;
|
||||
static final int MSG_CHECK_CHANGED_JOB_LIST = 8;
|
||||
static final int MSG_CHECK_MEDIA_EXEMPTION = 9;
|
||||
static final int MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS = 10;
|
||||
static final int MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE = 11;
|
||||
|
||||
/** List of controllers that will notify this service of updates to jobs. */
|
||||
final List<StateController> mControllers;
|
||||
@@ -279,6 +287,9 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArray<String> mCloudMediaProviderPackages = new SparseArray<>();
|
||||
|
||||
private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers =
|
||||
new RemoteCallbackList<>();
|
||||
|
||||
private final CountQuotaTracker mQuotaTracker;
|
||||
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
|
||||
private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
|
||||
@@ -1370,6 +1381,14 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
}
|
||||
|
||||
private void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
|
||||
synchronized (mLock) {
|
||||
mConcurrencyManager.stopUserVisibleJobsLocked(userId, packageName,
|
||||
JobParameters.STOP_REASON_USER,
|
||||
JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
|
||||
}
|
||||
}
|
||||
|
||||
private final Consumer<JobStatus> mCancelJobDueToUserRemovalConsumer = (toRemove) -> {
|
||||
// There's no guarantee that the process has been stopped by the time we get
|
||||
// here, but since this is a user-initiated action, it should be fine to just
|
||||
@@ -2006,6 +2025,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
delayMillis =
|
||||
Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
|
||||
// TODO(255767350): demote all jobs to regular for user stops so they don't keep privileges
|
||||
JobStatus newJob = new JobStatus(failureToReschedule,
|
||||
elapsedNowMillis + delayMillis,
|
||||
JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
|
||||
@@ -2347,6 +2367,52 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
args.recycle();
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS: {
|
||||
final IUserVisibleJobObserver observer =
|
||||
(IUserVisibleJobObserver) message.obj;
|
||||
synchronized (mLock) {
|
||||
for (int i = mConcurrencyManager.mActiveServices.size() - 1; i >= 0;
|
||||
--i) {
|
||||
JobServiceContext context =
|
||||
mConcurrencyManager.mActiveServices.get(i);
|
||||
final JobStatus jobStatus = context.getRunningJobLocked();
|
||||
if (jobStatus != null && jobStatus.isUserVisibleJob()) {
|
||||
try {
|
||||
observer.onUserVisibleJobStateChanged(
|
||||
jobStatus.getUserVisibleJobSummary(),
|
||||
/* isRunning */ true);
|
||||
} catch (RemoteException e) {
|
||||
// Will be unregistered automatically by
|
||||
// RemoteCallbackList's dead-object tracking,
|
||||
// so don't need to remove it here.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE: {
|
||||
final SomeArgs args = (SomeArgs) message.obj;
|
||||
final JobServiceContext context = (JobServiceContext) args.arg1;
|
||||
final JobStatus jobStatus = (JobStatus) args.arg2;
|
||||
final UserVisibleJobSummary summary = jobStatus.getUserVisibleJobSummary();
|
||||
final boolean isRunning = args.argi1 == 1;
|
||||
for (int i = mUserVisibleJobObservers.beginBroadcast() - 1; i >= 0; --i) {
|
||||
try {
|
||||
mUserVisibleJobObservers.getBroadcastItem(i)
|
||||
.onUserVisibleJobStateChanged(summary, isRunning);
|
||||
} catch (RemoteException e) {
|
||||
// Will be unregistered automatically by RemoteCallbackList's
|
||||
// dead-object tracking, so nothing we need to do here.
|
||||
}
|
||||
}
|
||||
mUserVisibleJobObservers.finishBroadcast();
|
||||
args.recycle();
|
||||
break;
|
||||
}
|
||||
}
|
||||
maybeRunPendingJobsLocked();
|
||||
}
|
||||
@@ -2843,6 +2909,16 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
return adjustJobBias(bias, job);
|
||||
}
|
||||
|
||||
void informObserversOfUserVisibleJobChange(JobServiceContext context, JobStatus jobStatus,
|
||||
boolean isRunning) {
|
||||
SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = context;
|
||||
args.arg2 = jobStatus;
|
||||
args.argi1 = isRunning ? 1 : 0;
|
||||
mHandler.obtainMessage(MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE, args)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
private final class BatteryStateTracker extends BroadcastReceiver {
|
||||
/**
|
||||
* Track whether we're "charging", where charging means that we're ready to commit to
|
||||
@@ -3553,6 +3629,35 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
return new ParceledListSlice<>(snapshots);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
|
||||
public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
|
||||
if (observer == null) {
|
||||
throw new NullPointerException("observer");
|
||||
}
|
||||
mUserVisibleJobObservers.register(observer);
|
||||
mHandler.obtainMessage(MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS, observer)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
@EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
|
||||
public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
|
||||
if (observer == null) {
|
||||
throw new NullPointerException("observer");
|
||||
}
|
||||
mUserVisibleJobObservers.unregister(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@EnforcePermission(allOf = {"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
|
||||
public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
|
||||
if (packageName == null) {
|
||||
throw new NullPointerException("packageName");
|
||||
}
|
||||
JobSchedulerService.this.stopUserVisibleJobsInternal(packageName, userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Shell command infrastructure: run the given job immediately
|
||||
|
||||
@@ -884,6 +884,9 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
return;
|
||||
}
|
||||
scheduleOpTimeOutLocked();
|
||||
if (mRunningJob.isUserVisibleJob()) {
|
||||
mService.informObserversOfUserVisibleJobChange(this, mRunningJob, true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Handling started job but job wasn't starting! Was "
|
||||
@@ -1134,6 +1137,9 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
mPendingInternalStopReason = 0;
|
||||
mPendingDebugStopReason = null;
|
||||
removeOpTimeOutLocked();
|
||||
if (completedJob.isUserVisibleJob()) {
|
||||
mService.informObserversOfUserVisibleJobChange(this, completedJob, false);
|
||||
}
|
||||
mCompletedListener.onJobCompletedLocked(completedJob, internalStopReason, reschedule);
|
||||
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
|
||||
}
|
||||
|
||||
@@ -29,10 +29,12 @@ import static com.android.server.job.controllers.FlexibilityController.NUM_SYSTE
|
||||
import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
|
||||
|
||||
import android.annotation.ElapsedRealtimeLong;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobWorkItem;
|
||||
import android.app.job.UserVisibleJobSummary;
|
||||
import android.content.ClipData;
|
||||
import android.content.ComponentName;
|
||||
import android.net.Network;
|
||||
@@ -446,6 +448,12 @@ public final class JobStatus {
|
||||
*/
|
||||
private boolean mExpeditedTareApproved;
|
||||
|
||||
/**
|
||||
* Summary describing this job. Lazily created in {@link #getUserVisibleJobSummary()}
|
||||
* since not every job will need it.
|
||||
*/
|
||||
private UserVisibleJobSummary mUserVisibleJobSummary;
|
||||
|
||||
/////// Booleans that track if a job is ready to run. They should be updated whenever dependent
|
||||
/////// states change.
|
||||
|
||||
@@ -1328,6 +1336,27 @@ public final class JobStatus {
|
||||
return mExpeditedQuotaApproved && mExpeditedTareApproved && isRequestedExpeditedJob();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a summary that uniquely identifies the underlying job.
|
||||
*/
|
||||
@NonNull
|
||||
public UserVisibleJobSummary getUserVisibleJobSummary() {
|
||||
if (mUserVisibleJobSummary == null) {
|
||||
mUserVisibleJobSummary = new UserVisibleJobSummary(
|
||||
callingUid, getSourceUserId(), getSourcePackageName(), getJobId());
|
||||
}
|
||||
return mUserVisibleJobSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this is a job whose execution should be made visible to the user.
|
||||
*/
|
||||
public boolean isUserVisibleJob() {
|
||||
// TODO(255767350): limit to user-initiated jobs
|
||||
// Placeholder implementation until we have the code in
|
||||
return shouldTreatAsExpeditedJob();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the job is exempted from Doze restrictions and therefore allowed to run
|
||||
* in Doze.
|
||||
|
||||
Reference in New Issue
Block a user