1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.downloads; 18 19 import static android.provider.Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI; 20 21 import static com.android.providers.downloads.Constants.TAG; 22 23 import android.app.job.JobParameters; 24 import android.app.job.JobService; 25 import android.database.ContentObserver; 26 import android.util.Log; 27 import android.util.SparseArray; 28 29 /** 30 * Service that hosts download jobs. Each active download job is handled as a 31 * unique {@link DownloadThread} instance. 32 * <p> 33 * The majority of downloads should have ETag values to enable resuming, so if a 34 * given download isn't able to finish in the normal job timeout (10 minutes), 35 * we just reschedule the job and resume again in the future. 36 */ 37 public class DownloadJobService extends JobService { 38 // @GuardedBy("mActiveThreads") 39 private SparseArray<DownloadThread> mActiveThreads = new SparseArray<>(); 40 41 @Override onCreate()42 public void onCreate() { 43 super.onCreate(); 44 45 // While someone is bound to us, watch for database changes that should 46 // trigger notification updates. 47 getContentResolver().registerContentObserver(ALL_DOWNLOADS_CONTENT_URI, true, mObserver); 48 } 49 50 @Override onDestroy()51 public void onDestroy() { 52 super.onDestroy(); 53 getContentResolver().unregisterContentObserver(mObserver); 54 } 55 56 @Override onStartJob(JobParameters params)57 public boolean onStartJob(JobParameters params) { 58 final int id = params.getJobId(); 59 60 // Spin up thread to handle this download 61 final DownloadInfo info = DownloadInfo.queryDownloadInfo(this, id); 62 if (info == null) { 63 Log.w(TAG, "Odd, no details found for download " + id); 64 return false; 65 } 66 67 final DownloadThread thread; 68 synchronized (mActiveThreads) { 69 thread = new DownloadThread(this, params, info); 70 mActiveThreads.put(id, thread); 71 } 72 thread.start(); 73 74 return true; 75 } 76 77 @Override onStopJob(JobParameters params)78 public boolean onStopJob(JobParameters params) { 79 final int id = params.getJobId(); 80 81 final DownloadThread thread; 82 synchronized (mActiveThreads) { 83 thread = mActiveThreads.removeReturnOld(id); 84 } 85 if (thread != null) { 86 // If the thread is still running, ask it to gracefully shutdown, 87 // and reschedule ourselves to resume in the future. 88 thread.requestShutdown(); 89 90 Helpers.scheduleJob(this, DownloadInfo.queryDownloadInfo(this, id)); 91 } 92 return false; 93 } 94 jobFinishedInternal(JobParameters params, boolean needsReschedule)95 public void jobFinishedInternal(JobParameters params, boolean needsReschedule) { 96 synchronized (mActiveThreads) { 97 mActiveThreads.remove(params.getJobId()); 98 } 99 100 // Update notifications one last time while job is protecting us 101 mObserver.onChange(false); 102 103 jobFinished(params, needsReschedule); 104 } 105 106 private ContentObserver mObserver = new ContentObserver(Helpers.getAsyncHandler()) { 107 @Override 108 public void onChange(boolean selfChange) { 109 Helpers.getDownloadNotifier(DownloadJobService.this).update(); 110 } 111 }; 112 } 113