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