diff options
7 files changed, 85 insertions, 29 deletions
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index b7af5445cae4..e4ed470e0cba 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -26,6 +26,9 @@ import android.os.PersistableBundle; * Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the * parameters required to schedule work against the calling application. These are constructed * using the {@link JobInfo.Builder}. + * You must specify at least one sort of constraint on the JobInfo object that you are creating. + * The goal here is to provide the scheduler with high-level semantics about the work you want to + * accomplish. Doing otherwise with throw an exception in your app. */ public class JobInfo implements Parcelable { public interface NetworkType { @@ -434,7 +437,7 @@ public class JobInfo implements Parcelable { * @return The job object to hand to the JobScheduler. This object is immutable. */ public JobInfo build() { - // Allow tasks with no constraints. What am I, a database? + // Allow jobs with no constraints - What am I, a database? if (!mHasEarlyConstraint && !mHasLateConstraint && !mRequiresCharging && !mRequiresDeviceIdle && mNetworkCapabilities == NetworkType.NONE) { throw new IllegalArgumentException("You're trying to build a job with no " + diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 6771ccece996..60f880c843bf 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -117,6 +117,8 @@ public class JobSchedulerService extends com.android.server.SystemService */ final ArrayList<JobStatus> mPendingJobs = new ArrayList<JobStatus>(); + final ArrayList<Integer> mStartedUsers = new ArrayList(); + final JobHandler mHandler; final JobSchedulerStub mJobSchedulerStub; @@ -151,6 +153,18 @@ public class JobSchedulerService extends com.android.server.SystemService } }; + @Override + public void onStartUser(int userHandle) { + mStartedUsers.add(userHandle); + // Let's kick any outstanding jobs for this user. + mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); + } + + @Override + public void onStopUser(int userHandle) { + mStartedUsers.remove(Integer.valueOf(userHandle)); + } + /** * Entry point from client to schedule the provided job. * This cancels the job if it's already been scheduled, and replaces it with the one provided. @@ -610,9 +624,20 @@ public class JobSchedulerService extends com.android.server.SystemService * - It's ready. * - It's not pending. * - It's not already running on a JSC. + * - The user that requested the job is running. */ private boolean isReadyToBeExecutedLocked(JobStatus job) { - return job.isReady() && !mPendingJobs.contains(job) && !isCurrentlyActiveLocked(job); + final boolean jobReady = job.isReady(); + final boolean jobPending = mPendingJobs.contains(job); + final boolean jobActive = isCurrentlyActiveLocked(job); + final boolean userRunning = mStartedUsers.contains(job.getUserId()); + + if (DEBUG) { + Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() + + " ready=" + jobReady + " pending=" + jobPending + + " active=" + jobActive + " userRunning=" + userRunning); + } + return userRunning && jobReady && !jobPending && !jobActive; } /** @@ -795,6 +820,11 @@ public class JobSchedulerService extends com.android.server.SystemService void dumpInternal(PrintWriter pw) { synchronized (mJobs) { + pw.print("Started users: "); + for (int i=0; i<mStartedUsers.size(); i++) { + pw.print("u" + mStartedUsers.get(i) + " "); + } + pw.println(); pw.println("Registered jobs:"); if (mJobs.size() > 0) { ArraySet<JobStatus> jobs = mJobs.getJobs(); diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index 652d8f8b4942..a257ea0a99ba 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.os.PersistableBundle; import android.os.SystemClock; import android.os.UserHandle; +import android.text.format.DateUtils; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; @@ -41,6 +42,7 @@ public class JobStatus { public static final long NO_EARLIEST_RUNTIME = 0L; final JobInfo job; + /** Uid of the package requesting this job. */ final int uId; final String name; final String tag; @@ -214,12 +216,39 @@ public class JobStatus { return String.valueOf(hashCode()).substring(0, 3) + ".." + ":[" + job.getService() + ",jId=" + job.getId() - + ",R=(" + earliestRunTimeElapsedMillis + "," + latestRunTimeElapsedMillis + ")" + + ",u" + getUserId() + + ",R=(" + formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME) + + "," + formatRunTime(latestRunTimeElapsedMillis, NO_LATEST_RUNTIME) + ")" + ",N=" + job.getNetworkCapabilities() + ",C=" + job.isRequireCharging() + ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures + + ",P=" + job.isPersisted() + (isReady() ? "(READY)" : "") + "]"; } + + private String formatRunTime(long runtime, long defaultValue) { + if (runtime == defaultValue) { + return "none"; + } else { + long elapsedNow = SystemClock.elapsedRealtime(); + long nextRuntime = runtime - elapsedNow; + if (nextRuntime > 0) { + return DateUtils.formatElapsedTime(nextRuntime / 1000); + } else { + return "-" + DateUtils.formatElapsedTime(nextRuntime / -1000); + } + } + } + + /** + * Convenience function to identify a job uniquely without pulling all the data that + * {@link #toString()} returns. + */ + public String toShortString() { + return job.getService().flattenToShortString() + " jId=" + job.getId() + + ", u" + getUserId(); + } + // Dumpsys infrastructure public void dump(PrintWriter pw, String prefix) { pw.println(this.toString()); diff --git a/tests/JobSchedulerTestApp/res/layout/activity_main.xml b/tests/JobSchedulerTestApp/res/layout/activity_main.xml index d3429ff3cf21..96e164103bce 100644 --- a/tests/JobSchedulerTestApp/res/layout/activity_main.xml +++ b/tests/JobSchedulerTestApp/res/layout/activity_main.xml @@ -141,6 +141,20 @@ android:id="@+id/checkbox_idle" android:text="@string/idle_mode_text"/> </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/persisted_caption" + android:layout_marginRight="15dp"/> + <CheckBox + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/checkbox_persisted" + android:text="@string/persisted_mode_text"/> + </LinearLayout> </LinearLayout> <Button diff --git a/tests/JobSchedulerTestApp/res/values/strings.xml b/tests/JobSchedulerTestApp/res/values/strings.xml index eebfb1968aea..90dd2b672fb2 100644 --- a/tests/JobSchedulerTestApp/res/values/strings.xml +++ b/tests/JobSchedulerTestApp/res/values/strings.xml @@ -27,6 +27,7 @@ limitations under the License. <string name="charging_caption">Charging:</string> <string name="charging_text">Requires device plugged in.</string> <string name="idle_caption">Idle:</string> + <string name="persisted_caption">Persisted:</string> <string name="constraints">Constraints</string> <string name="connectivity">Connectivity:</string> <string name="any">Any</string> @@ -34,4 +35,5 @@ limitations under the License. <string name="timing">Timing:</string> <string name="delay">Delay:</string> <string name="deadline">Deadline:</string> + <string name="persisted_mode_text">Persisted:</string> </resources> diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java index e15929d9026c..6e5484e0bea3 100644 --- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java +++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java @@ -65,6 +65,8 @@ public class MainActivity extends Activity { mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any); mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging); mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle); + mIsPersistedCheckbox = (CheckBox) findViewById(R.id.checkbox_persisted); + mServiceComponent = new ComponentName(this, TestJobService.class); // Start service and provide it a way to communicate with us. Intent startServiceIntent = new Intent(this, TestJobService.class); @@ -85,6 +87,7 @@ public class MainActivity extends Activity { RadioButton mAnyConnectivityRadioButton; CheckBox mRequiresChargingCheckBox; CheckBox mRequiresIdleCheckbox; + CheckBox mIsPersistedCheckbox; ComponentName mServiceComponent; /** Service object to interact scheduled jobs. */ @@ -146,7 +149,7 @@ public class MainActivity extends Activity { } builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked()); builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked()); - + builder.setIsPersisted(mIsPersistedCheckbox.isChecked()); mTestService.scheduleJob(builder.build()); } diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java index e2c3be03597c..a68e04ef1cb2 100644 --- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java +++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java @@ -90,31 +90,6 @@ public class TestJobService extends JobService { mActivity.onReceivedStartJob(params); } - // Spin off a new task on a separate thread for a couple seconds. - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... voids) { - try { - Log.d(TAG, "Sleeping for 3 seconds."); - Thread.sleep(3000L); - } catch (InterruptedException e) {} - final JobParameters params = jobParamsMap.get(currId); - Log.d(TAG, "Pulled :" + currId + " " + params); - jobFinished(params, false); - - Log.d(TAG, "Rescheduling new job: " + params.getJobId()); - scheduleJob( - new JobInfo.Builder(params.getJobId(), - new ComponentName(getBaseContext(), TestJobService.class)) - .setMinimumLatency(2000L) - .setOverrideDeadline(3000L) - .setRequiresCharging(true) - .build() - ); - - return null; - } - }.execute(); return true; } |