Merge "Finish impl of job queue: handle URI permissions." into oc-dev
am: 6e8f116647
Change-Id: I9d053f2e96712646fe2935ddc2c3729690a06be1
This commit is contained in:
@@ -27,6 +27,7 @@ import android.os.Parcelable;
|
||||
final public class JobWorkItem implements Parcelable {
|
||||
final Intent mIntent;
|
||||
int mWorkId;
|
||||
Object mGrants;
|
||||
|
||||
/**
|
||||
* Create a new piece of work.
|
||||
@@ -57,6 +58,20 @@ final public class JobWorkItem implements Parcelable {
|
||||
return mWorkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void setGrants(Object grants) {
|
||||
mGrants = grants;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public Object getGrants() {
|
||||
return mGrants;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "JobWorkItem{id=" + mWorkId + " intent=" + mIntent + "}";
|
||||
}
|
||||
|
||||
@@ -1787,7 +1787,7 @@ public class IntentFilter implements Parcelable {
|
||||
sb.append(", mHasPartialTypes="); sb.append(mHasPartialTypes);
|
||||
du.println(sb.toString());
|
||||
}
|
||||
{
|
||||
if (getAutoVerify()) {
|
||||
sb.setLength(0);
|
||||
sb.append(prefix); sb.append("AutoVerify="); sb.append(getAutoVerify());
|
||||
du.println(sb.toString());
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 android.app.IActivityManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GrantedUriPermissions {
|
||||
private final int mGrantFlags;
|
||||
private final int mSourceUserId;
|
||||
private final String mTag;
|
||||
private final IBinder mPermissionOwner;
|
||||
private final ArrayList<Uri> mUris = new ArrayList<>();
|
||||
|
||||
private GrantedUriPermissions(IActivityManager am, int grantFlags, int uid, String tag)
|
||||
throws RemoteException {
|
||||
mGrantFlags = grantFlags;
|
||||
mSourceUserId = UserHandle.getUserId(uid);
|
||||
mTag = tag;
|
||||
mPermissionOwner = am.newUriPermissionOwner("job: " + tag);
|
||||
}
|
||||
|
||||
public void revoke(IActivityManager am) {
|
||||
for (int i = mUris.size()-1; i >= 0; i--) {
|
||||
try {
|
||||
am.revokeUriPermissionFromOwner(mPermissionOwner, mUris.get(i),
|
||||
mGrantFlags, mSourceUserId);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
mUris.clear();
|
||||
}
|
||||
|
||||
public static boolean checkGrantFlags(int grantFlags) {
|
||||
return (grantFlags & (Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
|Intent.FLAG_GRANT_READ_URI_PERMISSION)) != 0;
|
||||
}
|
||||
|
||||
public static GrantedUriPermissions createFromIntent(IActivityManager am, Intent intent,
|
||||
int sourceUid, String targetPackage, int targetUserId, String tag) {
|
||||
int grantFlags = intent.getFlags();
|
||||
if (!checkGrantFlags(grantFlags)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
GrantedUriPermissions perms = null;
|
||||
|
||||
Uri data = intent.getData();
|
||||
if (data != null) {
|
||||
perms = grantUri(am, data, sourceUid, targetPackage, targetUserId, grantFlags, tag,
|
||||
perms);
|
||||
}
|
||||
|
||||
ClipData clip = intent.getClipData();
|
||||
if (clip != null) {
|
||||
perms = grantClip(am, clip, sourceUid, targetPackage, targetUserId, grantFlags, tag,
|
||||
perms);
|
||||
}
|
||||
|
||||
return perms;
|
||||
}
|
||||
|
||||
public static GrantedUriPermissions createFromClip(IActivityManager am, ClipData clip,
|
||||
int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag) {
|
||||
if (!checkGrantFlags(grantFlags)) {
|
||||
return null;
|
||||
}
|
||||
GrantedUriPermissions perms = null;
|
||||
if (clip != null) {
|
||||
perms = grantClip(am, clip, sourceUid, targetPackage, targetUserId, grantFlags,
|
||||
tag, perms);
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
|
||||
private static GrantedUriPermissions grantClip(IActivityManager am, ClipData clip,
|
||||
int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag,
|
||||
GrantedUriPermissions curPerms) {
|
||||
final int N = clip.getItemCount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
curPerms = grantItem(am, clip.getItemAt(i), sourceUid, targetPackage, targetUserId,
|
||||
grantFlags, tag, curPerms);
|
||||
}
|
||||
return curPerms;
|
||||
}
|
||||
|
||||
private static GrantedUriPermissions grantUri(IActivityManager am, Uri uri,
|
||||
int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag,
|
||||
GrantedUriPermissions curPerms) {
|
||||
try {
|
||||
int sourceUserId = ContentProvider.getUserIdFromUri(uri,
|
||||
UserHandle.getUserId(sourceUid));
|
||||
uri = ContentProvider.getUriWithoutUserId(uri);
|
||||
if (curPerms == null) {
|
||||
curPerms = new GrantedUriPermissions(am, grantFlags, sourceUid, tag);
|
||||
}
|
||||
am.grantUriPermissionFromOwner(curPerms.mPermissionOwner, sourceUid, targetPackage,
|
||||
uri, grantFlags, sourceUserId, targetUserId);
|
||||
curPerms.mUris.add(uri);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e("JobScheduler", "AM dead");
|
||||
}
|
||||
return curPerms;
|
||||
}
|
||||
|
||||
private static GrantedUriPermissions grantItem(IActivityManager am, ClipData.Item item,
|
||||
int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag,
|
||||
GrantedUriPermissions curPerms) {
|
||||
if (item.getUri() != null) {
|
||||
curPerms = grantUri(am, item.getUri(), sourceUid, targetPackage, targetUserId,
|
||||
grantFlags, tag, curPerms);
|
||||
}
|
||||
Intent intent = item.getIntent();
|
||||
if (intent != null && intent.getData() != null) {
|
||||
curPerms = grantUri(am, intent.getData(), sourceUid, targetPackage, targetUserId,
|
||||
grantFlags, tag, curPerms);
|
||||
}
|
||||
return curPerms;
|
||||
}
|
||||
|
||||
// Dumpsys infrastructure
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
pw.print(prefix); pw.print("mGrantFlags=0x"); pw.print(Integer.toHexString(mGrantFlags));
|
||||
pw.print(" mSourceUserId="); pw.println(mSourceUserId);
|
||||
pw.print(prefix); pw.print("mTag="); pw.println(mTag);
|
||||
pw.print(prefix); pw.print("mPermissionOwner="); pw.println(mPermissionOwner);
|
||||
for (int i = 0; i < mUris.size(); i++) {
|
||||
pw.print(prefix); pw.print("#"); pw.print(i); pw.print(": ");
|
||||
pw.println(mUris.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -601,7 +601,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
// Fast path: we are adding work to an existing job, and the JobInfo is not
|
||||
// changing. We can just directly enqueue this work in to the job.
|
||||
if (toCancel.getJob().equals(job)) {
|
||||
toCancel.enqueueWorkLocked(work);
|
||||
toCancel.enqueueWorkLocked(ActivityManager.getService(), work);
|
||||
return JobScheduler.RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -625,7 +625,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
if (work != null) {
|
||||
// If work has been supplied, enqueue it into the new job.
|
||||
jobStatus.enqueueWorkLocked(work);
|
||||
jobStatus.enqueueWorkLocked(ActivityManager.getService(), work);
|
||||
}
|
||||
startTrackingJobLocked(jobStatus, toCancel);
|
||||
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
|
||||
@@ -758,7 +758,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
final JobStatus executing = jsc.getRunningJob();
|
||||
if (executing != null
|
||||
&& (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
|
||||
jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
|
||||
jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -921,7 +921,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
private boolean stopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
|
||||
boolean writeBack) {
|
||||
// Deal with any remaining work items in the old job.
|
||||
jobStatus.stopTrackingJobLocked(incomingJob);
|
||||
jobStatus.stopTrackingJobLocked(ActivityManager.getService(), incomingJob);
|
||||
|
||||
// Remove from store as well as controllers.
|
||||
final boolean removed = mJobs.remove(jobStatus, writeBack);
|
||||
@@ -939,7 +939,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
JobServiceContext jsc = mActiveServices.get(i);
|
||||
final JobStatus executing = jsc.getRunningJob();
|
||||
if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
|
||||
jsc.cancelExecutingJob(reason);
|
||||
jsc.cancelExecutingJobLocked(reason);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1564,7 +1564,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
|
||||
}
|
||||
// preferredUid will be set to uid of currently running job.
|
||||
mActiveServices.get(i).preemptExecutingJob();
|
||||
mActiveServices.get(i).preemptExecutingJobLocked();
|
||||
preservePreferredUid = true;
|
||||
} else {
|
||||
final JobStatus pendingJob = contextIdToJobMap[i];
|
||||
|
||||
@@ -44,8 +44,6 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.IBatteryStats;
|
||||
import com.android.server.job.controllers.JobStatus;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Handles client binding and lifecycle of a job. Jobs execute one at a time on an instance of this
|
||||
* class.
|
||||
@@ -56,19 +54,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* job lands, and again when it is complete.
|
||||
* - Cancelling is trickier, because there are also interactions from the client. It's possible
|
||||
* the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a
|
||||
* {@link #MSG_CANCEL} after the client has already finished. This is handled by having
|
||||
* {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelH} check whether
|
||||
* {@link #doCancelLocked(int)} after the client has already finished. This is handled by having
|
||||
* {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether
|
||||
* the context is still valid.
|
||||
* To mitigate this, tearing down the context removes all messages from the handler, including any
|
||||
* tardy {@link #MSG_CANCEL}s. Additionally, we avoid sending duplicate onStopJob()
|
||||
* To mitigate this, we avoid sending duplicate onStopJob()
|
||||
* calls to the client after they've specified jobFinished().
|
||||
*/
|
||||
public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
|
||||
private static final boolean DEBUG = JobSchedulerService.DEBUG;
|
||||
private static final String TAG = "JobServiceContext";
|
||||
/** Define the maximum # of jobs allowed to run on a service at once. */
|
||||
private static final int defaultMaxActiveJobsPerService =
|
||||
ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
|
||||
/** Amount of time a job is allowed to execute for before being considered timed-out. */
|
||||
private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins.
|
||||
/** Amount of time the JobScheduler waits for the initial service launch+bind. */
|
||||
@@ -90,14 +84,6 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
// Messages that result from interactions with the client service.
|
||||
/** System timed out waiting for a response. */
|
||||
private static final int MSG_TIMEOUT = 0;
|
||||
/** Received a callback from client. */
|
||||
private static final int MSG_CALLBACK = 1;
|
||||
/** Run through list and start any ready jobs.*/
|
||||
private static final int MSG_SERVICE_BOUND = 2;
|
||||
/** Cancel a job. */
|
||||
private static final int MSG_CANCEL = 3;
|
||||
/** Shutdown the job. Used when the client crashes and we can't die gracefully.*/
|
||||
private static final int MSG_SHUTDOWN_EXECUTION = 4;
|
||||
|
||||
public static final int NO_PREFERRED_UID = -1;
|
||||
|
||||
@@ -115,7 +101,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
private JobParameters mParams;
|
||||
@VisibleForTesting
|
||||
int mVerb;
|
||||
private AtomicBoolean mCancelled = new AtomicBoolean();
|
||||
private boolean mCancelled;
|
||||
|
||||
/**
|
||||
* All the information maintained about the job currently being executed.
|
||||
@@ -245,14 +231,12 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
}
|
||||
|
||||
/** Called externally when a job that was scheduled for execution should be cancelled. */
|
||||
void cancelExecutingJob(int reason) {
|
||||
mCallbackHandler.obtainMessage(MSG_CANCEL, reason, 0 /* unused */).sendToTarget();
|
||||
void cancelExecutingJobLocked(int reason) {
|
||||
doCancelLocked(reason);
|
||||
}
|
||||
|
||||
void preemptExecutingJob() {
|
||||
Message m = mCallbackHandler.obtainMessage(MSG_CANCEL);
|
||||
m.arg1 = JobParameters.REASON_PREEMPT;
|
||||
m.sendToTarget();
|
||||
void preemptExecutingJobLocked() {
|
||||
doCancelLocked(JobParameters.REASON_PREEMPT);
|
||||
}
|
||||
|
||||
int getPreferredUid() {
|
||||
@@ -273,59 +257,54 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
|
||||
@Override
|
||||
public void jobFinished(int jobId, boolean reschedule) {
|
||||
if (!verifyCallingUid()) {
|
||||
return;
|
||||
}
|
||||
mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
|
||||
.sendToTarget();
|
||||
doCallback(reschedule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acknowledgeStopMessage(int jobId, boolean reschedule) {
|
||||
if (!verifyCallingUid()) {
|
||||
return;
|
||||
}
|
||||
mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
|
||||
.sendToTarget();
|
||||
doCallback(reschedule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acknowledgeStartMessage(int jobId, boolean ongoing) {
|
||||
if (!verifyCallingUid()) {
|
||||
return;
|
||||
}
|
||||
mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, ongoing ? 1 : 0).sendToTarget();
|
||||
doCallback(ongoing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobWorkItem dequeueWork(int jobId) {
|
||||
if (!verifyCallingUid()) {
|
||||
throw new SecurityException("Bad calling uid: " + Binder.getCallingUid());
|
||||
}
|
||||
JobWorkItem work = null;
|
||||
boolean stillWorking = false;
|
||||
synchronized (mLock) {
|
||||
if (mRunningJob != null) {
|
||||
work = mRunningJob.dequeueWorkLocked();
|
||||
stillWorking = mRunningJob.hasExecutingWorkLocked();
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
if (!verifyCallingUidLocked(callingUid)) {
|
||||
throw new SecurityException("Bad calling uid: " + callingUid);
|
||||
}
|
||||
|
||||
final JobWorkItem work = mRunningJob.dequeueWorkLocked();
|
||||
if (work == null && !mRunningJob.hasExecutingWorkLocked()) {
|
||||
// This will finish the job.
|
||||
doCallbackLocked(false);
|
||||
}
|
||||
return work;
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
if (work == null && !stillWorking) {
|
||||
jobFinished(jobId, false);
|
||||
}
|
||||
return work;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean completeWork(int jobId, int workId) {
|
||||
if (!verifyCallingUid()) {
|
||||
throw new SecurityException("Bad calling uid: " + Binder.getCallingUid());
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (mRunningJob != null) {
|
||||
return mRunningJob.completeWorkLocked(workId);
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
if (!verifyCallingUidLocked(callingUid)) {
|
||||
throw new SecurityException("Bad calling uid: " + callingUid);
|
||||
}
|
||||
return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId);
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,20 +323,20 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
// looper and at this point we can't get any binder callbacks from the client. Better
|
||||
// safe than sorry.
|
||||
runningJob = mRunningJob;
|
||||
}
|
||||
if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
|
||||
mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
|
||||
return;
|
||||
}
|
||||
this.service = IJobService.Stub.asInterface(service);
|
||||
final PowerManager pm =
|
||||
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
|
||||
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||
runningJob.getTag());
|
||||
wl.setWorkSource(new WorkSource(runningJob.getSourceUid()));
|
||||
wl.setReferenceCounted(false);
|
||||
wl.acquire();
|
||||
synchronized (mLock) {
|
||||
|
||||
if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
|
||||
closeAndCleanupJobLocked(true /* needsReschedule */);
|
||||
return;
|
||||
}
|
||||
this.service = IJobService.Stub.asInterface(service);
|
||||
final PowerManager pm =
|
||||
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
|
||||
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||
runningJob.getTag());
|
||||
wl.setWorkSource(new WorkSource(runningJob.getSourceUid()));
|
||||
wl.setReferenceCounted(false);
|
||||
wl.acquire();
|
||||
|
||||
// We use a new wakelock instance per job. In rare cases there is a race between
|
||||
// teardown following job completion/cancellation and new job service spin-up
|
||||
// such that if we simply assign mWakeLock to be the new instance, we orphan
|
||||
@@ -369,14 +348,16 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
mWakeLock.release();
|
||||
}
|
||||
mWakeLock = wl;
|
||||
doServiceBoundLocked();
|
||||
}
|
||||
mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
|
||||
}
|
||||
|
||||
/** If the client service crashes we reschedule this job and clean up. */
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
|
||||
synchronized (mLock) {
|
||||
closeAndCleanupJobLocked(true /* needsReschedule */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,22 +365,18 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
* whether the client exercising the callback is the client we expect.
|
||||
* @return True if the binder calling is coming from the client we expect.
|
||||
*/
|
||||
private boolean verifyCallingUid() {
|
||||
synchronized (mLock) {
|
||||
if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Stale callback received, ignoring.");
|
||||
}
|
||||
return false;
|
||||
private boolean verifyCallingUidLocked(final int callingUid) {
|
||||
if (mRunningJob == null || callingUid != mRunningJob.getUid()) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Stale callback received, ignoring.");
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the lifecycle of the JobService binding/callbacks, etc. The convention within this
|
||||
* class is to append 'H' to each function name that can only be called on this handler. This
|
||||
* isn't strictly necessary because all of these functions are private, but helps clarity.
|
||||
* Scheduling of async messages (basically timeouts at this point).
|
||||
*/
|
||||
private class JobServiceHandler extends Handler {
|
||||
JobServiceHandler(Looper looper) {
|
||||
@@ -409,295 +386,279 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case MSG_SERVICE_BOUND:
|
||||
doServiceBound();
|
||||
break;
|
||||
case MSG_CALLBACK:
|
||||
doCallback(message.arg2);
|
||||
break;
|
||||
case MSG_CANCEL:
|
||||
doCancel(message.arg1);
|
||||
break;
|
||||
case MSG_TIMEOUT:
|
||||
synchronized (mLock) {
|
||||
handleOpTimeoutH();
|
||||
handleOpTimeoutLocked();
|
||||
}
|
||||
break;
|
||||
case MSG_SHUTDOWN_EXECUTION:
|
||||
closeAndCleanupJobH(true /* needsReschedule */);
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Unrecognised message: " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void doServiceBound() {
|
||||
void doServiceBoundLocked() {
|
||||
removeOpTimeOutLocked();
|
||||
handleServiceBoundLocked();
|
||||
}
|
||||
|
||||
void doCallback(boolean reschedule) {
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
removeOpTimeOutLocked();
|
||||
handleServiceBoundH();
|
||||
}
|
||||
}
|
||||
|
||||
void doCallback(int arg2) {
|
||||
synchronized (mLock) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob
|
||||
+ " v:" + VERB_STRINGS[mVerb]);
|
||||
}
|
||||
removeOpTimeOutLocked();
|
||||
|
||||
if (mVerb == VERB_STARTING) {
|
||||
final boolean workOngoing = arg2 == 1;
|
||||
handleStartedH(workOngoing);
|
||||
} else if (mVerb == VERB_EXECUTING ||
|
||||
mVerb == VERB_STOPPING) {
|
||||
final boolean reschedule = arg2 == 1;
|
||||
handleFinishedH(reschedule);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void doCancel(int arg1) {
|
||||
synchronized (mLock) {
|
||||
if (mVerb == VERB_FINISHED) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG,
|
||||
"Trying to process cancel for torn-down context, ignoring.");
|
||||
}
|
||||
if (!verifyCallingUidLocked(callingUid)) {
|
||||
return;
|
||||
}
|
||||
mParams.setStopReason(arg1);
|
||||
if (arg1 == JobParameters.REASON_PREEMPT) {
|
||||
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
|
||||
NO_PREFERRED_UID;
|
||||
}
|
||||
handleCancelH();
|
||||
doCallbackLocked(reschedule);
|
||||
}
|
||||
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
}
|
||||
|
||||
/** Start the job on the service. */
|
||||
private void handleServiceBoundH() {
|
||||
void doCallbackLocked(boolean reschedule) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "doCallback of : " + mRunningJob
|
||||
+ " v:" + VERB_STRINGS[mVerb]);
|
||||
}
|
||||
removeOpTimeOutLocked();
|
||||
|
||||
if (mVerb == VERB_STARTING) {
|
||||
handleStartedLocked(reschedule);
|
||||
} else if (mVerb == VERB_EXECUTING ||
|
||||
mVerb == VERB_STOPPING) {
|
||||
handleFinishedLocked(reschedule);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "MSG_SERVICE_BOUND for " + mRunningJob.toShortString());
|
||||
}
|
||||
if (mVerb != VERB_BINDING) {
|
||||
Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
|
||||
+ VERB_STRINGS[mVerb]);
|
||||
closeAndCleanupJobH(false /* reschedule */);
|
||||
return;
|
||||
}
|
||||
if (mCancelled.get()) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
|
||||
+ mRunningJob);
|
||||
}
|
||||
closeAndCleanupJobH(true /* reschedule */);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mVerb = VERB_STARTING;
|
||||
scheduleOpTimeOutLocked();
|
||||
service.startJob(mParams);
|
||||
} catch (Exception e) {
|
||||
// We catch 'Exception' because client-app malice or bugs might induce a wide
|
||||
// range of possible exception-throw outcomes from startJob() and its handling
|
||||
// of the client's ParcelableBundle extras.
|
||||
Slog.e(TAG, "Error sending onStart message to '" +
|
||||
mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
|
||||
Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* State behaviours.
|
||||
* VERB_STARTING -> Successful start, change job to VERB_EXECUTING and post timeout.
|
||||
* _PENDING -> Error
|
||||
* _EXECUTING -> Error
|
||||
* _STOPPING -> Error
|
||||
*/
|
||||
private void handleStartedH(boolean workOngoing) {
|
||||
switch (mVerb) {
|
||||
case VERB_STARTING:
|
||||
mVerb = VERB_EXECUTING;
|
||||
if (!workOngoing) {
|
||||
// Job is finished already so fast-forward to handleFinished.
|
||||
handleFinishedH(false);
|
||||
return;
|
||||
}
|
||||
if (mCancelled.get()) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete.");
|
||||
}
|
||||
// Cancelled *while* waiting for acknowledgeStartMessage from client.
|
||||
handleCancelH();
|
||||
return;
|
||||
}
|
||||
scheduleOpTimeOutLocked();
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Handling started job but job wasn't starting! Was "
|
||||
+ VERB_STRINGS[mVerb] + ".");
|
||||
return;
|
||||
void doCancelLocked(int arg1) {
|
||||
if (mVerb == VERB_FINISHED) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG,
|
||||
"Trying to process cancel for torn-down context, ignoring.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* VERB_EXECUTING -> Client called jobFinished(), clean up and notify done.
|
||||
* _STOPPING -> Successful finish, clean up and notify done.
|
||||
* _STARTING -> Error
|
||||
* _PENDING -> Error
|
||||
*/
|
||||
private void handleFinishedH(boolean reschedule) {
|
||||
switch (mVerb) {
|
||||
case VERB_EXECUTING:
|
||||
case VERB_STOPPING:
|
||||
closeAndCleanupJobH(reschedule);
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
|
||||
"executed. Was " + VERB_STRINGS[mVerb] + ".");
|
||||
}
|
||||
mParams.setStopReason(arg1);
|
||||
if (arg1 == JobParameters.REASON_PREEMPT) {
|
||||
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
|
||||
NO_PREFERRED_UID;
|
||||
}
|
||||
handleCancelLocked();
|
||||
}
|
||||
|
||||
/**
|
||||
* A job can be in various states when a cancel request comes in:
|
||||
* VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for
|
||||
* {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
|
||||
* _STARTING -> Mark as cancelled and wait for
|
||||
* {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
|
||||
* _EXECUTING -> call {@link #sendStopMessageH}}, but only if there are no callbacks
|
||||
* in the message queue.
|
||||
* _ENDING -> No point in doing anything here, so we ignore.
|
||||
*/
|
||||
private void handleCancelH() {
|
||||
if (JobSchedulerService.DEBUG) {
|
||||
Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
|
||||
+ VERB_STRINGS[mVerb]);
|
||||
}
|
||||
switch (mVerb) {
|
||||
case VERB_BINDING:
|
||||
case VERB_STARTING:
|
||||
mCancelled.set(true);
|
||||
break;
|
||||
case VERB_EXECUTING:
|
||||
if (hasMessages(MSG_CALLBACK)) {
|
||||
// If the client has called jobFinished, ignore this cancel.
|
||||
return;
|
||||
}
|
||||
sendStopMessageH();
|
||||
break;
|
||||
case VERB_STOPPING:
|
||||
// Nada.
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
|
||||
break;
|
||||
}
|
||||
/** Start the job on the service. */
|
||||
private void handleServiceBoundLocked() {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "handleServiceBound for " + mRunningJob.toShortString());
|
||||
}
|
||||
|
||||
/** Process MSG_TIMEOUT here. */
|
||||
private void handleOpTimeoutH() {
|
||||
switch (mVerb) {
|
||||
case VERB_BINDING:
|
||||
Slog.e(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() +
|
||||
", dropping.");
|
||||
closeAndCleanupJobH(false /* needsReschedule */);
|
||||
break;
|
||||
case VERB_STARTING:
|
||||
// Client unresponsive - wedged or failed to respond in time. We don't really
|
||||
// know what happened so let's log it and notify the JobScheduler
|
||||
// FINISHED/NO-RETRY.
|
||||
Slog.e(TAG, "No response from client for onStartJob '" +
|
||||
mRunningJob.toShortString());
|
||||
closeAndCleanupJobH(false /* needsReschedule */);
|
||||
break;
|
||||
case VERB_STOPPING:
|
||||
// At least we got somewhere, so fail but ask the JobScheduler to reschedule.
|
||||
Slog.e(TAG, "No response from client for onStopJob, '" +
|
||||
mRunningJob.toShortString());
|
||||
closeAndCleanupJobH(true /* needsReschedule */);
|
||||
break;
|
||||
case VERB_EXECUTING:
|
||||
// Not an error - client ran out of time.
|
||||
Slog.i(TAG, "Client timed out while executing (no jobFinished received)." +
|
||||
" sending onStop. " + mRunningJob.toShortString());
|
||||
mParams.setStopReason(JobParameters.REASON_TIMEOUT);
|
||||
sendStopMessageH();
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Handling timeout for an invalid job state: " +
|
||||
mRunningJob.toShortString() + ", dropping.");
|
||||
closeAndCleanupJobH(false /* needsReschedule */);
|
||||
}
|
||||
if (mVerb != VERB_BINDING) {
|
||||
Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
|
||||
+ VERB_STRINGS[mVerb]);
|
||||
closeAndCleanupJobLocked(false /* reschedule */);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
|
||||
* VERB_STOPPING.
|
||||
*/
|
||||
private void sendStopMessageH() {
|
||||
removeOpTimeOutLocked();
|
||||
if (mVerb != VERB_EXECUTING) {
|
||||
Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
|
||||
closeAndCleanupJobH(false /* reschedule */);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mVerb = VERB_STOPPING;
|
||||
scheduleOpTimeOutLocked();
|
||||
service.stopJob(mParams);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Error sending onStopJob to client.", e);
|
||||
// The job's host app apparently crashed during the job, so we should reschedule.
|
||||
closeAndCleanupJobH(true /* reschedule */);
|
||||
if (mCancelled) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
|
||||
+ mRunningJob);
|
||||
}
|
||||
closeAndCleanupJobLocked(true /* reschedule */);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mVerb = VERB_STARTING;
|
||||
scheduleOpTimeOutLocked();
|
||||
service.startJob(mParams);
|
||||
} catch (Exception e) {
|
||||
// We catch 'Exception' because client-app malice or bugs might induce a wide
|
||||
// range of possible exception-throw outcomes from startJob() and its handling
|
||||
// of the client's ParcelableBundle extras.
|
||||
Slog.e(TAG, "Error sending onStart message to '" +
|
||||
mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The provided job has finished, either by calling
|
||||
* {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
|
||||
* or from acknowledging the stop message we sent. Either way, we're done tracking it and
|
||||
* we want to clean up internally.
|
||||
*/
|
||||
private void closeAndCleanupJobH(boolean reschedule) {
|
||||
final JobStatus completedJob;
|
||||
synchronized (mLock) {
|
||||
if (mVerb == VERB_FINISHED) {
|
||||
/**
|
||||
* State behaviours.
|
||||
* VERB_STARTING -> Successful start, change job to VERB_EXECUTING and post timeout.
|
||||
* _PENDING -> Error
|
||||
* _EXECUTING -> Error
|
||||
* _STOPPING -> Error
|
||||
*/
|
||||
private void handleStartedLocked(boolean workOngoing) {
|
||||
switch (mVerb) {
|
||||
case VERB_STARTING:
|
||||
mVerb = VERB_EXECUTING;
|
||||
if (!workOngoing) {
|
||||
// Job is finished already so fast-forward to handleFinished.
|
||||
handleFinishedLocked(false);
|
||||
return;
|
||||
}
|
||||
completedJob = mRunningJob;
|
||||
mJobPackageTracker.noteInactive(completedJob);
|
||||
try {
|
||||
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
|
||||
mRunningJob.getSourceUid());
|
||||
} catch (RemoteException e) {
|
||||
// Whatever.
|
||||
if (mCancelled) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete.");
|
||||
}
|
||||
// Cancelled *while* waiting for acknowledgeStartMessage from client.
|
||||
handleCancelLocked();
|
||||
return;
|
||||
}
|
||||
if (mWakeLock != null) {
|
||||
mWakeLock.release();
|
||||
}
|
||||
mContext.unbindService(JobServiceContext.this);
|
||||
mWakeLock = null;
|
||||
mRunningJob = null;
|
||||
mParams = null;
|
||||
mVerb = VERB_FINISHED;
|
||||
mCancelled.set(false);
|
||||
service = null;
|
||||
mAvailable = true;
|
||||
removeOpTimeOutLocked();
|
||||
removeMessages(MSG_CALLBACK);
|
||||
removeMessages(MSG_SERVICE_BOUND);
|
||||
removeMessages(MSG_CANCEL);
|
||||
removeMessages(MSG_SHUTDOWN_EXECUTION);
|
||||
mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
|
||||
}
|
||||
scheduleOpTimeOutLocked();
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Handling started job but job wasn't starting! Was "
|
||||
+ VERB_STRINGS[mVerb] + ".");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* VERB_EXECUTING -> Client called jobFinished(), clean up and notify done.
|
||||
* _STOPPING -> Successful finish, clean up and notify done.
|
||||
* _STARTING -> Error
|
||||
* _PENDING -> Error
|
||||
*/
|
||||
private void handleFinishedLocked(boolean reschedule) {
|
||||
switch (mVerb) {
|
||||
case VERB_EXECUTING:
|
||||
case VERB_STOPPING:
|
||||
closeAndCleanupJobLocked(reschedule);
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
|
||||
"executed. Was " + VERB_STRINGS[mVerb] + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A job can be in various states when a cancel request comes in:
|
||||
* VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for
|
||||
* {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
|
||||
* _STARTING -> Mark as cancelled and wait for
|
||||
* {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
|
||||
* _EXECUTING -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks
|
||||
* in the message queue.
|
||||
* _ENDING -> No point in doing anything here, so we ignore.
|
||||
*/
|
||||
private void handleCancelLocked() {
|
||||
if (JobSchedulerService.DEBUG) {
|
||||
Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
|
||||
+ VERB_STRINGS[mVerb]);
|
||||
}
|
||||
switch (mVerb) {
|
||||
case VERB_BINDING:
|
||||
case VERB_STARTING:
|
||||
mCancelled = true;
|
||||
break;
|
||||
case VERB_EXECUTING:
|
||||
sendStopMessageLocked();
|
||||
break;
|
||||
case VERB_STOPPING:
|
||||
// Nada.
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Process MSG_TIMEOUT here. */
|
||||
private void handleOpTimeoutLocked() {
|
||||
switch (mVerb) {
|
||||
case VERB_BINDING:
|
||||
Slog.e(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() +
|
||||
", dropping.");
|
||||
closeAndCleanupJobLocked(false /* needsReschedule */);
|
||||
break;
|
||||
case VERB_STARTING:
|
||||
// Client unresponsive - wedged or failed to respond in time. We don't really
|
||||
// know what happened so let's log it and notify the JobScheduler
|
||||
// FINISHED/NO-RETRY.
|
||||
Slog.e(TAG, "No response from client for onStartJob '" +
|
||||
mRunningJob.toShortString());
|
||||
closeAndCleanupJobLocked(false /* needsReschedule */);
|
||||
break;
|
||||
case VERB_STOPPING:
|
||||
// At least we got somewhere, so fail but ask the JobScheduler to reschedule.
|
||||
Slog.e(TAG, "No response from client for onStopJob, '" +
|
||||
mRunningJob.toShortString());
|
||||
closeAndCleanupJobLocked(true /* needsReschedule */);
|
||||
break;
|
||||
case VERB_EXECUTING:
|
||||
// Not an error - client ran out of time.
|
||||
Slog.i(TAG, "Client timed out while executing (no jobFinished received)." +
|
||||
" sending onStop. " + mRunningJob.toShortString());
|
||||
mParams.setStopReason(JobParameters.REASON_TIMEOUT);
|
||||
sendStopMessageLocked();
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Handling timeout for an invalid job state: " +
|
||||
mRunningJob.toShortString() + ", dropping.");
|
||||
closeAndCleanupJobLocked(false /* needsReschedule */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
|
||||
* VERB_STOPPING.
|
||||
*/
|
||||
private void sendStopMessageLocked() {
|
||||
removeOpTimeOutLocked();
|
||||
if (mVerb != VERB_EXECUTING) {
|
||||
Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
|
||||
closeAndCleanupJobLocked(false /* reschedule */);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mVerb = VERB_STOPPING;
|
||||
scheduleOpTimeOutLocked();
|
||||
service.stopJob(mParams);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Error sending onStopJob to client.", e);
|
||||
// The job's host app apparently crashed during the job, so we should reschedule.
|
||||
closeAndCleanupJobLocked(true /* reschedule */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The provided job has finished, either by calling
|
||||
* {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
|
||||
* or from acknowledging the stop message we sent. Either way, we're done tracking it and
|
||||
* we want to clean up internally.
|
||||
*/
|
||||
private void closeAndCleanupJobLocked(boolean reschedule) {
|
||||
final JobStatus completedJob;
|
||||
if (mVerb == VERB_FINISHED) {
|
||||
return;
|
||||
}
|
||||
completedJob = mRunningJob;
|
||||
mJobPackageTracker.noteInactive(completedJob);
|
||||
try {
|
||||
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
|
||||
mRunningJob.getSourceUid());
|
||||
} catch (RemoteException e) {
|
||||
// Whatever.
|
||||
}
|
||||
if (mWakeLock != null) {
|
||||
mWakeLock.release();
|
||||
}
|
||||
mContext.unbindService(JobServiceContext.this);
|
||||
mWakeLock = null;
|
||||
mRunningJob = null;
|
||||
mParams = null;
|
||||
mVerb = VERB_FINISHED;
|
||||
mCancelled = false;
|
||||
service = null;
|
||||
mAvailable = true;
|
||||
removeOpTimeOutLocked();
|
||||
mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when sending a message to the client, over whose execution we have no control. If
|
||||
* we haven't received a response in a certain amount of time, we want to give up and carry
|
||||
|
||||
@@ -26,7 +26,6 @@ import android.content.ContentProvider;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
@@ -35,6 +34,8 @@ import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
import android.util.TimeUtils;
|
||||
|
||||
import com.android.server.job.GrantedUriPermissions;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -103,7 +104,7 @@ public final class JobStatus {
|
||||
|
||||
final String tag;
|
||||
|
||||
private IBinder permissionOwner;
|
||||
private GrantedUriPermissions uriPerms;
|
||||
private boolean prepared;
|
||||
|
||||
/**
|
||||
@@ -284,12 +285,17 @@ public final class JobStatus {
|
||||
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis);
|
||||
}
|
||||
|
||||
public void enqueueWorkLocked(JobWorkItem work) {
|
||||
public void enqueueWorkLocked(IActivityManager am, JobWorkItem work) {
|
||||
if (pendingWork == null) {
|
||||
pendingWork = new ArrayList<>();
|
||||
}
|
||||
work.setWorkId(nextPendingWorkId);
|
||||
nextPendingWorkId++;
|
||||
if (work.getIntent() != null
|
||||
&& GrantedUriPermissions.checkGrantFlags(work.getIntent().getFlags())) {
|
||||
work.setGrants(GrantedUriPermissions.createFromIntent(am, work.getIntent(), sourceUid,
|
||||
sourcePackageName, sourceUserId, toShortString()));
|
||||
}
|
||||
pendingWork.add(work);
|
||||
}
|
||||
|
||||
@@ -311,12 +317,20 @@ public final class JobStatus {
|
||||
return executingWork != null && executingWork.size() > 0;
|
||||
}
|
||||
|
||||
public boolean completeWorkLocked(int workId) {
|
||||
private static void ungrantWorkItem(IActivityManager am, JobWorkItem work) {
|
||||
if (work.getGrants() != null) {
|
||||
((GrantedUriPermissions)work.getGrants()).revoke(am);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean completeWorkLocked(IActivityManager am, int workId) {
|
||||
if (executingWork != null) {
|
||||
final int N = executingWork.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (executingWork.get(i).getWorkId() == workId) {
|
||||
JobWorkItem work = executingWork.get(i);
|
||||
if (work.getWorkId() == workId) {
|
||||
executingWork.remove(i);
|
||||
ungrantWorkItem(am, work);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -324,7 +338,16 @@ public final class JobStatus {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void stopTrackingJobLocked(JobStatus incomingJob) {
|
||||
private static void ungrantWorkList(IActivityManager am, ArrayList<JobWorkItem> list) {
|
||||
if (list != null) {
|
||||
final int N = list.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
ungrantWorkItem(am, list.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stopTrackingJobLocked(IActivityManager am, JobStatus incomingJob) {
|
||||
if (incomingJob != null) {
|
||||
// We are replacing with a new job -- transfer the work! We do any executing
|
||||
// work first, since that was originally at the front of the pending work.
|
||||
@@ -341,7 +364,10 @@ public final class JobStatus {
|
||||
incomingJob.nextPendingWorkId = nextPendingWorkId;
|
||||
} else {
|
||||
// We are completely stopping the job... need to clean up work.
|
||||
// XXX remove perms when that is impl.
|
||||
ungrantWorkList(am, pendingWork);
|
||||
pendingWork = null;
|
||||
ungrantWorkList(am, executingWork);
|
||||
executingWork = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,10 +379,8 @@ public final class JobStatus {
|
||||
prepared = true;
|
||||
final ClipData clip = job.getClipData();
|
||||
if (clip != null) {
|
||||
final int N = clip.getItemCount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
grantItemLocked(am, clip.getItemAt(i), sourceUid, sourcePackageName, sourceUserId);
|
||||
}
|
||||
uriPerms = GrantedUriPermissions.createFromClip(am, clip, sourceUid, sourcePackageName,
|
||||
sourceUserId, job.getClipGrantFlags(), toShortString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,14 +390,9 @@ public final class JobStatus {
|
||||
return;
|
||||
}
|
||||
prepared = false;
|
||||
if (permissionOwner != null) {
|
||||
final ClipData clip = job.getClipData();
|
||||
if (clip != null) {
|
||||
final int N = clip.getItemCount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
revokeItemLocked(am, clip.getItemAt(i));
|
||||
}
|
||||
}
|
||||
if (uriPerms != null) {
|
||||
uriPerms.revoke(am);
|
||||
uriPerms = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,57 +400,6 @@ public final class JobStatus {
|
||||
return prepared;
|
||||
}
|
||||
|
||||
private final void grantUriLocked(IActivityManager am, Uri uri, int sourceUid,
|
||||
String targetPackage, int targetUserId) {
|
||||
try {
|
||||
int sourceUserId = ContentProvider.getUserIdFromUri(uri,
|
||||
UserHandle.getUserId(sourceUid));
|
||||
uri = ContentProvider.getUriWithoutUserId(uri);
|
||||
if (permissionOwner == null) {
|
||||
permissionOwner = am.newUriPermissionOwner("job: " + toShortString());
|
||||
}
|
||||
am.grantUriPermissionFromOwner(permissionOwner, sourceUid, targetPackage,
|
||||
uri, job.getClipGrantFlags(), sourceUserId, targetUserId);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e("JobScheduler", "AM dead");
|
||||
}
|
||||
}
|
||||
|
||||
private final void grantItemLocked(IActivityManager am, ClipData.Item item, int sourceUid,
|
||||
String targetPackage, int targetUserId) {
|
||||
if (item.getUri() != null) {
|
||||
grantUriLocked(am, item.getUri(), sourceUid, targetPackage, targetUserId);
|
||||
}
|
||||
Intent intent = item.getIntent();
|
||||
if (intent != null && intent.getData() != null) {
|
||||
grantUriLocked(am, intent.getData(), sourceUid, targetPackage, targetUserId);
|
||||
}
|
||||
}
|
||||
|
||||
private final void revokeUriLocked(IActivityManager am, Uri uri) {
|
||||
int userId = ContentProvider.getUserIdFromUri(uri,
|
||||
UserHandle.getUserId(Binder.getCallingUid()));
|
||||
long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
uri = ContentProvider.getUriWithoutUserId(uri);
|
||||
am.revokeUriPermissionFromOwner(permissionOwner, uri,
|
||||
job.getClipGrantFlags(), userId);
|
||||
} catch (RemoteException e) {
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
}
|
||||
|
||||
private final void revokeItemLocked(IActivityManager am, ClipData.Item item) {
|
||||
if (item.getUri() != null) {
|
||||
revokeUriLocked(am, item.getUri());
|
||||
}
|
||||
Intent intent = item.getIntent();
|
||||
if (intent != null && intent.getData() != null) {
|
||||
revokeUriLocked(am, intent.getData());
|
||||
}
|
||||
}
|
||||
|
||||
public JobInfo getJob() {
|
||||
return job;
|
||||
}
|
||||
@@ -842,6 +810,15 @@ public final class JobStatus {
|
||||
}
|
||||
}
|
||||
|
||||
private void dumpJobWorkItem(PrintWriter pw, String prefix, JobWorkItem work, int index) {
|
||||
pw.print(prefix); pw.print(" #"); pw.print(index); pw.print(": #");
|
||||
pw.print(work.getWorkId()); pw.print(" "); pw.println(work.getIntent());
|
||||
if (work.getGrants() != null) {
|
||||
pw.print(prefix); pw.println(" URI grants:");
|
||||
((GrantedUriPermissions)work.getGrants()).dump(pw, prefix + " ");
|
||||
}
|
||||
}
|
||||
|
||||
// Dumpsys infrastructure
|
||||
public void dump(PrintWriter pw, String prefix, boolean full) {
|
||||
pw.print(prefix); UserHandle.formatUid(pw, callingUid);
|
||||
@@ -907,6 +884,10 @@ public final class JobStatus {
|
||||
job.getClipData().toShortString(b);
|
||||
pw.println(b);
|
||||
}
|
||||
if (uriPerms != null) {
|
||||
pw.print(prefix); pw.println(" Granted URI permissions:");
|
||||
uriPerms.dump(pw, prefix + " ");
|
||||
}
|
||||
if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
|
||||
pw.print(prefix); pw.print(" Network type: "); pw.println(job.getNetworkType());
|
||||
}
|
||||
@@ -959,17 +940,13 @@ public final class JobStatus {
|
||||
if (pendingWork != null && pendingWork.size() > 0) {
|
||||
pw.print(prefix); pw.println("Pending work:");
|
||||
for (int i = 0; i < pendingWork.size(); i++) {
|
||||
JobWorkItem work = pendingWork.get(i);
|
||||
pw.print(prefix); pw.print(" #"); pw.print(i); pw.print(": #");
|
||||
pw.print(work.getWorkId()); pw.print(" "); pw.println(work.getIntent());
|
||||
dumpJobWorkItem(pw, prefix, pendingWork.get(i), i);
|
||||
}
|
||||
}
|
||||
if (executingWork != null && executingWork.size() > 0) {
|
||||
pw.print(prefix); pw.println("Executing work:");
|
||||
for (int i = 0; i < executingWork.size(); i++) {
|
||||
JobWorkItem work = executingWork.get(i);
|
||||
pw.print(prefix); pw.print(" #"); pw.print(i); pw.print(": #");
|
||||
pw.print(work.getWorkId()); pw.print(" "); pw.println(work.getIntent());
|
||||
dumpJobWorkItem(pw, prefix, executingWork.get(i), i);
|
||||
}
|
||||
}
|
||||
pw.print(prefix); pw.print("Earliest run time: ");
|
||||
|
||||
Reference in New Issue
Block a user