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