From 3a5f5eafb34eaa4963c801882148e8f61514a61b Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Wed, 20 Apr 2016 23:23:09 -0600 Subject: Move DownloadManager to use JobScheduler. JobScheduler is in a much better position to coordinate tasks across the platform to optimize battery and RAM usage. This change removes a bunch of manual scheduling logic by representing each download as a separate job with relevant scheduling constraints. Requested network types, retry backoff timing, and newly added charging and idle constraints are plumbed through as job parameters. When a job times out, we halt the download and schedule it to resume later. The majority of downloads should have ETag values to enable resuming like this. Remove local wakelocks, since the platform now acquires and blames our jobs on the requesting app. When an active download is pushing updates to the database, check for both paused and cancelled state to quickly halt an ongoing download. Shift DownloadNotifier to update directly based on a Cursor, since we no longer have the overhead of fully-parsed DownloadInfo objects. Unify a handful of worker threads into a single shared thread. Remove legacy "large download" activity that was thrown in the face of the user; the UX best-practice is to go through notification, and update that dialog to let the user override and continue if under the hard limit. Bug: 28098882, 26571724 Change-Id: I33ebe59b3c2ea9c89ec526f70b1950c734abc4a7 --- .../providers/downloads/DownloadJobService.java | 112 +++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/com/android/providers/downloads/DownloadJobService.java (limited to 'src/com/android/providers/downloads/DownloadJobService.java') diff --git a/src/com/android/providers/downloads/DownloadJobService.java b/src/com/android/providers/downloads/DownloadJobService.java new file mode 100644 index 00000000..0ce4266a --- /dev/null +++ b/src/com/android/providers/downloads/DownloadJobService.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 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.providers.downloads; + +import static android.provider.Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI; + +import static com.android.providers.downloads.Constants.TAG; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.database.ContentObserver; +import android.util.Log; +import android.util.SparseArray; + +/** + * Service that hosts download jobs. Each active download job is handled as a + * unique {@link DownloadThread} instance. + *

+ * The majority of downloads should have ETag values to enable resuming, so if a + * given download isn't able to finish in the normal job timeout (10 minutes), + * we just reschedule the job and resume again in the future. + */ +public class DownloadJobService extends JobService { + // @GuardedBy("mActiveThreads") + private SparseArray mActiveThreads = new SparseArray<>(); + + @Override + public void onCreate() { + super.onCreate(); + + // While someone is bound to us, watch for database changes that should + // trigger notification updates. + getContentResolver().registerContentObserver(ALL_DOWNLOADS_CONTENT_URI, true, mObserver); + } + + @Override + public void onDestroy() { + super.onDestroy(); + getContentResolver().unregisterContentObserver(mObserver); + } + + @Override + public boolean onStartJob(JobParameters params) { + final int id = params.getJobId(); + + // Spin up thread to handle this download + final DownloadInfo info = DownloadInfo.queryDownloadInfo(this, id); + if (info == null) { + Log.w(TAG, "Odd, no details found for download " + id); + return false; + } + + final DownloadThread thread; + synchronized (mActiveThreads) { + thread = new DownloadThread(this, params, info); + mActiveThreads.put(id, thread); + } + thread.start(); + + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + final int id = params.getJobId(); + + final DownloadThread thread; + synchronized (mActiveThreads) { + thread = mActiveThreads.removeReturnOld(id); + } + if (thread != null) { + // If the thread is still running, ask it to gracefully shutdown, + // and reschedule ourselves to resume in the future. + thread.requestShutdown(); + + Helpers.scheduleJob(this, DownloadInfo.queryDownloadInfo(this, id)); + } + return false; + } + + public void jobFinishedInternal(JobParameters params, boolean needsReschedule) { + synchronized (mActiveThreads) { + mActiveThreads.remove(params.getJobId()); + } + + // Update notifications one last time while job is protecting us + mObserver.onChange(false); + + jobFinished(params, needsReschedule); + } + + private ContentObserver mObserver = new ContentObserver(Helpers.getAsyncHandler()) { + @Override + public void onChange(boolean selfChange) { + Helpers.getDownloadNotifier(DownloadJobService.this).update(); + } + }; +} -- cgit v1.2.3