153e0454ef2b5189b76c9cbf06ec98f23dd718f0
[android/platform/packages/providers/DownloadProvider.git] / src / com / android / providers / downloads / DownloadInfo.java
1 /*
2  * Copyright (C) 2008 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 android.content.ContentUris;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.database.Cursor;
24 import android.net.ConnectivityManager;
25 import android.net.Uri;
26 import android.provider.Downloads;
27 import android.provider.Downloads.Impl;
28 import android.util.Log;
29
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.Map;
33
34 /**
35  * Stores information about an individual download.
36  */
37 public class DownloadInfo {
38     public int mId;
39     public String mUri;
40     public boolean mNoIntegrity;
41     public String mHint;
42     public String mFileName;
43     public String mMimeType;
44     public int mDestination;
45     public int mVisibility;
46     public int mControl;
47     public int mStatus;
48     public int mNumFailed;
49     public int mRetryAfter;
50     public int mRedirectCount;
51     public long mLastMod;
52     public String mPackage;
53     public String mClass;
54     public String mExtras;
55     public String mCookies;
56     public String mUserAgent;
57     public String mReferer;
58     public int mTotalBytes;
59     public int mCurrentBytes;
60     public String mETag;
61     public boolean mMediaScanned;
62
63     public int mFuzz;
64
65     public volatile boolean mHasActiveThread;
66
67     private Map<String, String> mRequestHeaders = new HashMap<String, String>();
68     private SystemFacade mSystemFacade;
69     private Context mContext;
70
71     public DownloadInfo(Context context, SystemFacade systemFacade, Cursor cursor) {
72         mContext = context;
73         mSystemFacade = systemFacade;
74
75         int retryRedirect =
76             cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
77         mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl._ID));
78         mUri = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_URI));
79         mNoIntegrity = cursor.getInt(cursor.getColumnIndexOrThrow(
80                                         Downloads.Impl.COLUMN_NO_INTEGRITY)) == 1;
81         mHint = cursor.getString(cursor.getColumnIndexOrThrow(
82                                         Downloads.Impl.COLUMN_FILE_NAME_HINT));
83         mFileName = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl._DATA));
84         mMimeType = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE));
85         mDestination =
86                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESTINATION));
87         mVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_VISIBILITY));
88         mControl = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_CONTROL));
89         mStatus = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS));
90         mNumFailed = cursor.getInt(cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS));
91         mRetryAfter = retryRedirect & 0xfffffff;
92         mRedirectCount = retryRedirect >> 28;
93         mLastMod = cursor.getLong(cursor.getColumnIndexOrThrow(
94                                         Downloads.Impl.COLUMN_LAST_MODIFICATION));
95         mPackage = cursor.getString(cursor.getColumnIndexOrThrow(
96                                         Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE));
97         mClass = cursor.getString(cursor.getColumnIndexOrThrow(
98                                         Downloads.Impl.COLUMN_NOTIFICATION_CLASS));
99         mExtras = cursor.getString(cursor.getColumnIndexOrThrow(
100                                         Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS));
101         mCookies =
102                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_COOKIE_DATA));
103         mUserAgent =
104                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_USER_AGENT));
105         mReferer = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_REFERER));
106         mTotalBytes =
107                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TOTAL_BYTES));
108         mCurrentBytes =
109                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_CURRENT_BYTES));
110         mETag = cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG));
111         mMediaScanned = cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1;
112         mFuzz = Helpers.sRandom.nextInt(1001);
113
114         readRequestHeaders(mId);
115     }
116
117     private void readRequestHeaders(long downloadId) {
118         Uri headerUri = Downloads.Impl.CONTENT_URI.buildUpon()
119                         .appendPath(Long.toString(downloadId))
120                         .appendPath(Downloads.Impl.RequestHeaders.URI_SEGMENT).build();
121         Cursor cursor = mContext.getContentResolver().query(headerUri, null, null, null, null);
122         try {
123             int headerIndex =
124                     cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_HEADER);
125             int valueIndex =
126                     cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_VALUE);
127             for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
128                 mRequestHeaders.put(cursor.getString(headerIndex), cursor.getString(valueIndex));
129             }
130         } finally {
131             cursor.close();
132         }
133
134         if (mCookies != null) {
135             mRequestHeaders.put("Cookie", mCookies);
136         }
137         if (mReferer != null) {
138             mRequestHeaders.put("Referer", mReferer);
139         }
140     }
141
142     public Map<String, String> getHeaders() {
143         return Collections.unmodifiableMap(mRequestHeaders);
144     }
145
146     public void sendIntentIfRequested(Uri contentUri) {
147         if (mPackage != null && mClass != null) {
148             Intent intent = new Intent(Downloads.Impl.ACTION_DOWNLOAD_COMPLETED);
149             intent.setClassName(mPackage, mClass);
150             if (mExtras != null) {
151                 intent.putExtra(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, mExtras);
152             }
153             // We only send the content: URI, for security reasons. Otherwise, malicious
154             //     applications would have an easier time spoofing download results by
155             //     sending spoofed intents.
156             intent.setData(contentUri);
157             mContext.sendBroadcast(intent);
158         }
159     }
160
161     /**
162      * Returns the time when a download should be restarted. Must only
163      * be called when numFailed > 0.
164      */
165     public long restartTime() {
166         if (mRetryAfter > 0) {
167             return mLastMod + mRetryAfter;
168         }
169         return mLastMod +
170                 Constants.RETRY_FIRST_DELAY *
171                     (1000 + mFuzz) * (1 << (mNumFailed - 1));
172     }
173
174     /**
175      * Returns whether this download (which the download manager hasn't seen yet)
176      * should be started.
177      */
178     public boolean isReadyToStart(long now) {
179         if (mControl == Downloads.Impl.CONTROL_PAUSED) {
180             // the download is paused, so it's not going to start
181             return false;
182         }
183         if (mStatus == 0) {
184             // status hasn't been initialized yet, this is a new download
185             return true;
186         }
187         if (mStatus == Downloads.Impl.STATUS_PENDING) {
188             // download is explicit marked as ready to start
189             return true;
190         }
191         if (mStatus == Downloads.Impl.STATUS_RUNNING) {
192             // download was interrupted (process killed, loss of power) while it was running,
193             //     without a chance to update the database
194             return true;
195         }
196         if (mStatus == Downloads.Impl.STATUS_RUNNING_PAUSED) {
197             if (mNumFailed == 0) {
198                 // download is waiting for network connectivity to return before it can resume
199                 return true;
200             }
201             if (restartTime() < now) {
202                 // download was waiting for a delayed restart, and the delay has expired
203                 return true;
204             }
205         }
206         return false;
207     }
208
209     /**
210      * Returns whether this download (which the download manager has already seen
211      * and therefore potentially started) should be restarted.
212      *
213      * In a nutshell, this returns true if the download isn't already running
214      * but should be, and it can know whether the download is already running
215      * by checking the status.
216      */
217     public boolean isReadyToRestart(long now) {
218         if (mControl == Downloads.Impl.CONTROL_PAUSED) {
219             // the download is paused, so it's not going to restart
220             return false;
221         }
222         if (mStatus == 0) {
223             // download hadn't been initialized yet
224             return true;
225         }
226         if (mStatus == Downloads.Impl.STATUS_PENDING) {
227             // download is explicit marked as ready to start
228             return true;
229         }
230         if (mStatus == Downloads.Impl.STATUS_RUNNING_PAUSED) {
231             if (mNumFailed == 0) {
232                 // download is waiting for network connectivity to return before it can resume
233                 return true;
234             }
235             if (restartTime() < now) {
236                 // download was waiting for a delayed restart, and the delay has expired
237                 return true;
238             }
239         }
240         return false;
241     }
242
243     /**
244      * Returns whether this download has a visible notification after
245      * completion.
246      */
247     public boolean hasCompletionNotification() {
248         if (!Downloads.Impl.isStatusCompleted(mStatus)) {
249             return false;
250         }
251         if (mVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
252             return true;
253         }
254         return false;
255     }
256
257     /**
258      * Returns whether this download is allowed to use the network.
259      */
260     public boolean canUseNetwork() {
261         Integer networkType = mSystemFacade.getActiveNetworkType();
262         if (networkType == null) {
263             return false;
264         }
265         if (!isSizeAllowedForNetwork(networkType)) {
266             return false;
267         }
268         if (mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING
269                 && mSystemFacade.isNetworkRoaming()) {
270             return false;
271         }
272         return true;
273     }
274
275     /**
276      * Check if the download's size prohibits it from running over the current network.
277      */
278     private boolean isSizeAllowedForNetwork(int networkType) {
279         if (mTotalBytes <= 0) {
280             return true; // we don't know the size yet
281         }
282         if (networkType == ConnectivityManager.TYPE_WIFI) {
283             return true; // anything goes over wifi
284         }
285         Integer maxBytesOverMobile = mSystemFacade.getMaxBytesOverMobile();
286         if (maxBytesOverMobile == null) {
287             return true; // no limit
288         }
289         return mTotalBytes <= maxBytesOverMobile;
290     }
291
292     void start(long now) {
293         if (Constants.LOGV) {
294             Log.v(Constants.TAG, "Service spawning thread to handle download " + mId);
295         }
296         if (mHasActiveThread) {
297             throw new IllegalStateException("Multiple threads on same download");
298         }
299         if (mStatus != Impl.STATUS_RUNNING) {
300             mStatus = Impl.STATUS_RUNNING;
301             ContentValues values = new ContentValues();
302             values.put(Impl.COLUMN_STATUS, mStatus);
303             mContext.getContentResolver().update(
304                     ContentUris.withAppendedId(Impl.CONTENT_URI, mId),
305                     values, null, null);
306         }
307         DownloadThread downloader = new DownloadThread(mContext, mSystemFacade, this);
308         mHasActiveThread = true;
309         downloader.start();
310     }
311 }