Transition the Download Service's cleanup work to a scheduled Job
Christopher Tate [Tue, 17 Jun 2014 00:13:09 +0000 (17:13 -0700)]
...preparatory to finally removing the scratchpad "idle maintenance"
infrastructure from the product.

Bug 14993295

Change-Id: I1e84247de19e616910db1781b2c399a8b15a805c

AndroidManifest.xml
src/com/android/providers/downloads/DownloadIdleService.java
src/com/android/providers/downloads/DownloadService.java

index e8e187b..579f8dd 100644 (file)
 
         <service
             android:name="com.android.providers.downloads.DownloadIdleService"
-            android:permission="android.permission.BIND_IDLE_SERVICE">
-            <intent-filter>
-                <action android:name="android.service.idle.IdleService" />
-            </intent-filter>
+            android:exported="true"
+            android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
         <receiver android:name=".DownloadReceiver" android:exported="false">
index 1bd30cb..ddfeba4 100644 (file)
@@ -20,7 +20,8 @@ import static com.android.providers.downloads.Constants.TAG;
 import static com.android.providers.downloads.StorageUtils.listFilesRecursive;
 
 import android.app.DownloadManager;
-import android.app.maintenance.IdleService;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.database.Cursor;
@@ -41,30 +42,37 @@ import java.util.ArrayList;
 import java.util.HashSet;
 
 /**
- * Idle maintenance service for {@link DownloadManager}. Reconciles database
+ * Idle-time service for {@link DownloadManager}. Reconciles database
  * metadata and files on disk, which can become inconsistent when files are
  * deleted directly on disk.
  */
-public class DownloadIdleService extends IdleService {
+public class DownloadIdleService extends JobService {
+
+    private class IdleRunnable implements Runnable {
+        private JobParameters mParams;
+
+        public IdleRunnable(JobParameters params) {
+            mParams = params;
+        }
 
-    private final Runnable mIdleRunnable = new Runnable() {
         @Override
         public void run() {
             cleanOrphans();
-            finishIdle();
+            jobFinished(mParams, false);
         }
     };
 
     @Override
-    public boolean onIdleStart() {
-        new Thread(mIdleRunnable).start();
+    public boolean onStartJob(JobParameters params) {
+        new Thread(new IdleRunnable(params)).start();
         return true;
     }
 
     @Override
-    public void onIdleStop() {
+    public boolean onStopJob(JobParameters params) {
         // We're okay being killed at any point, so we don't worry about
         // checkpointing before tearing down.
+        return false;
     }
 
     private interface DownloadQuery {
index 084a359..58cf380 100644 (file)
@@ -23,6 +23,9 @@ import android.app.AlarmManager;
 import android.app.DownloadManager;
 import android.app.PendingIntent;
 import android.app.Service;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -88,6 +91,15 @@ public class DownloadService extends Service {
     /** Class to handle Notification Manager updates */
     private DownloadNotifier mNotifier;
 
+    /** Scheduling of the periodic cleanup job */
+    private JobInfo mCleanupJob;
+
+    private static final int CLEANUP_JOB_ID = 1;
+    private static final long CLEANUP_JOB_PERIOD = 1000 * 60 * 60 * 24; // one day
+    private static ComponentName sCleanupServiceName = new ComponentName(
+            DownloadIdleService.class.getPackage().getName(),
+            DownloadIdleService.class.getName());
+
     /**
      * The Service's view of the list of downloads, mapping download IDs to the corresponding info
      * object. This is kept independently from the content provider, and the Service only initiates
@@ -193,6 +205,28 @@ public class DownloadService extends Service {
         mObserver = new DownloadManagerContentObserver();
         getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
                 true, mObserver);
+
+        JobScheduler js = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        if (needToScheduleCleanup(js)) {
+            final JobInfo job = new JobInfo.Builder(CLEANUP_JOB_ID, sCleanupServiceName)
+                    .setPeriodic(CLEANUP_JOB_PERIOD)
+                    .setRequiresCharging(true)
+                    .setRequiresDeviceIdle(true)
+                    .build();
+            js.schedule(job);
+        }
+    }
+
+    private boolean needToScheduleCleanup(JobScheduler js) {
+        List<JobInfo> myJobs = js.getAllPendingJobs();
+        final int N = myJobs.size();
+        for (int i = 0; i < N; i++) {
+            if (myJobs.get(i).getId() == CLEANUP_JOB_ID) {
+                // It's already been (persistently) scheduled; no need to do it again
+                return false;
+            }
+        }
+        return true;
     }
 
     @Override