diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml index 0d626d4f6..d9744047a 100644 --- a/samples/ApiDemos/AndroidManifest.xml +++ b/samples/ApiDemos/AndroidManifest.xml @@ -595,6 +595,18 @@ + + + + + + + + + diff --git a/samples/ApiDemos/res/layout/job_work_service_activity.xml b/samples/ApiDemos/res/layout/job_work_service_activity.xml new file mode 100644 index 000000000..9862689fb --- /dev/null +++ b/samples/ApiDemos/res/layout/job_work_service_activity.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml index 37e55f7b5..81df88925 100644 --- a/samples/ApiDemos/res/values/strings.xml +++ b/samples/ApiDemos/res/values/strings.xml @@ -302,6 +302,19 @@ Service created. Service destroyed. + Job Work Service + + App/Job/Job Work Service Controller + This demonstrates how + work can be enqueued and executed by a job, running work in the job until + everything is complete. + + Enqueue \"One\" + Enqueue \"Two\" + Enqueue \"Three\" + JobService created. + JobService destroyed. + Service is in the foreground Sample Foreground Service diff --git a/samples/ApiDemos/src/com/example/android/apis/app/JobWorkService.java b/samples/ApiDemos/src/com/example/android/apis/app/JobWorkService.java new file mode 100644 index 000000000..bd101aee2 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/JobWorkService.java @@ -0,0 +1,161 @@ +/* + * 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.example.android.apis.app; + +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.app.job.JobWorkItem; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.Toast; + +import com.example.android.apis.R; + +/** + * This is an example of implementing a {@link JobService} that dispatches work enqueued in + * to it. The {@link JobWorkServiceActivity} class shows how to interact with the service. + */ +//BEGIN_INCLUDE(service) +public class JobWorkService extends JobService { + private NotificationManager mNM; + private CommandProcessor mCurProcessor; + + /** + * This is a task to dequeue and process work in the background. + */ + final class CommandProcessor extends AsyncTask { + private final JobParameters mParams; + + CommandProcessor(JobParameters params) { + mParams = params; + } + + @Override + protected Void doInBackground(Void... params) { + boolean cancelled; + JobWorkItem work; + + /** + * Iterate over available work. Once dequeueWork() returns null, the + * job's work queue is empty and the job has stopped, so we can let this + * async task complete. + */ + while (!(cancelled=isCancelled()) && (work=mParams.dequeueWork()) != null) { + String txt = work.getIntent().getStringExtra("name"); + Log.i("JobWorkService", "Processing work: " + work + ", msg: " + txt); + showNotification(txt); + + // Process work here... we'll pretend by sleeping. + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + } + + hideNotification(); + + // Tell system we have finished processing the work. + Log.i("JobWorkService", "Done with: " + work); + mParams.completeWork(work); + } + + if (cancelled) { + Log.i("JobWorkService", "CANCELLED!"); + } + + return null; + } + } + + @Override + public void onCreate() { + mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + Toast.makeText(this, R.string.service_created, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onDestroy() { + hideNotification(); + Toast.makeText(this, R.string.service_destroyed, Toast.LENGTH_SHORT).show(); + } + + @Override + public boolean onStartJob(JobParameters params) { + // Start task to pull work out of the queue and process it. + mCurProcessor = new CommandProcessor(params); + mCurProcessor.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + + // Allow the job to continue running while we process work. + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + // Have the processor cancel its current work. + mCurProcessor.cancel(true); + + // Tell the system to reschedule the job -- the only reason we would be here is + // because the job needs to stop for some reason before it has completed all of + // its work, so we would like it to remain to finish that work in the future. + return true; + } + + /** + * Show a notification while this service is running. + */ + private void showNotification(String text) { + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, JobWorkServiceActivity.class), 0); + + // Set the info for the views that show in the notification panel. + Notification.Builder noteBuilder = new Notification.Builder(this) + .setSmallIcon(R.drawable.stat_sample) // the status icon + .setTicker(text) // the status text + .setWhen(System.currentTimeMillis()) // the time stamp + .setContentTitle(getText(R.string.service_start_arguments_label)) // the label + .setContentText(text) // the contents of the entry + .setContentIntent(contentIntent); // The intent to send when the entry is clicked + + // We show this for as long as our service is processing a command. + noteBuilder.setOngoing(true); + + // Send the notification. + // We use a string id because it is a unique number. We use it later to cancel. + mNM.notify(R.string.job_service_created, noteBuilder.build()); + } + + private void hideNotification() { + mNM.cancel(R.string.service_created); + } +} +//END_INCLUDE(service) diff --git a/samples/ApiDemos/src/com/example/android/apis/app/JobWorkServiceActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/JobWorkServiceActivity.java new file mode 100644 index 000000000..5155466b5 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/JobWorkServiceActivity.java @@ -0,0 +1,88 @@ +/* + * 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.example.android.apis.app; + +import android.app.Activity; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.app.job.JobWorkItem; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Bundle; +import android.os.Process; +import android.view.View; +import android.widget.Button; + +import com.example.android.apis.R; + +/** + * Example of interacting with {@link JobWorkService}. + */ +public class JobWorkServiceActivity extends Activity { + JobScheduler mJobScheduler; + JobInfo mJobInfo; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mJobScheduler = (JobScheduler)getSystemService(JOB_SCHEDULER_SERVICE); + mJobInfo = new JobInfo.Builder(R.string.job_service_created, + new ComponentName(this, JobWorkService.class)).setOverrideDeadline(0).build(); + + setContentView(R.layout.job_work_service_activity); + + // Watch for button clicks. + Button button = findViewById(R.id.enqueue1); + button.setOnClickListener(mEnqueue1Listener); + button = findViewById(R.id.enqueue2); + button.setOnClickListener(mEnqueue2Listener); + button = findViewById(R.id.enqueue3); + button.setOnClickListener(mEnqueue3Listener); + button = findViewById(R.id.kill); + button.setOnClickListener(mKillListener); + } + + private View.OnClickListener mEnqueue1Listener = new View.OnClickListener() { + public void onClick(View v) { + mJobScheduler.enqueue(mJobInfo, new JobWorkItem( + new Intent("com.example.android.apis.ONE").putExtra("name", "One"))); + } + }; + + private View.OnClickListener mEnqueue2Listener = new View.OnClickListener() { + public void onClick(View v) { + mJobScheduler.enqueue(mJobInfo, new JobWorkItem( + new Intent("com.example.android.apis.TWO").putExtra("name", "Two"))); + } + }; + + private View.OnClickListener mEnqueue3Listener = new View.OnClickListener() { + public void onClick(View v) { + mJobScheduler.enqueue(mJobInfo, new JobWorkItem( + new Intent("com.example.android.apis.THREE").putExtra("name", "Three"))); + } + }; + + private View.OnClickListener mKillListener = new View.OnClickListener() { + public void onClick(View v) { + // This is to simulate the service being killed while it is + // running in the background. + Process.killProcess(Process.myPid()); + } + }; +}