2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.providers.downloads;
19 import com.google.android.collect.Lists;
21 import android.app.AlarmManager;
22 import android.app.PendingIntent;
23 import android.app.Service;
24 import android.content.ComponentName;
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.ServiceConnection;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ResolveInfo;
32 import android.database.CharArrayBuffer;
33 import android.database.ContentObserver;
34 import android.database.Cursor;
35 import android.drm.mobile1.DrmRawContent;
36 import android.media.IMediaScannerService;
37 import android.net.Uri;
38 import android.os.Environment;
39 import android.os.Handler;
40 import android.os.IBinder;
41 import android.os.Process;
42 import android.os.RemoteException;
43 import android.provider.Downloads;
44 import android.util.Config;
45 import android.util.Log;
48 import java.util.ArrayList;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.List;
55 * Performs the background downloads requested by applications that use the Downloads provider.
57 public class DownloadService extends Service {
59 /* ------------ Constants ------------ */
61 /* ------------ Members ------------ */
63 /** Observer to get notified when the content observer's data changes */
64 private DownloadManagerContentObserver mObserver;
66 /** Class to handle Notification Manager updates */
67 private DownloadNotification mNotifier;
70 * The Service's view of the list of downloads. This is kept independently
71 * from the content provider, and the Service only initiates downloads
72 * based on this data, so that it can deal with situation where the data
73 * in the content provider changes or disappears.
75 private ArrayList<DownloadInfo> mDownloads;
78 * The thread that updates the internal download list from the content
81 private UpdateThread mUpdateThread;
84 * Whether the internal download list should be updated from the content
87 private boolean mPendingUpdate;
90 * The ServiceConnection object that tells us when we're connected to and disconnected from
93 private MediaScannerConnection mMediaScannerConnection;
95 private boolean mMediaScannerConnecting;
98 * The IPC interface to the Media Scanner
100 private IMediaScannerService mMediaScannerService;
103 * Array used when extracting strings from content provider
105 private CharArrayBuffer oldChars;
108 * Array used when extracting strings from content provider
110 private CharArrayBuffer mNewChars;
112 /* ------------ Inner Classes ------------ */
115 * Receives notifications when the data in the content provider changes
117 private class DownloadManagerContentObserver extends ContentObserver {
119 public DownloadManagerContentObserver() {
120 super(new Handler());
124 * Receives notification when the data in the observed content
127 public void onChange(final boolean selfChange) {
128 if (Constants.LOGVV) {
129 Log.v(Constants.TAG, "Service ContentObserver received notification");
131 updateFromProvider();
137 * Gets called back when the connection to the media
138 * scanner is established or lost.
140 public class MediaScannerConnection implements ServiceConnection {
141 public void onServiceConnected(ComponentName className, IBinder service) {
142 if (Constants.LOGVV) {
143 Log.v(Constants.TAG, "Connected to Media Scanner");
145 mMediaScannerConnecting = false;
146 synchronized (DownloadService.this) {
147 mMediaScannerService = IMediaScannerService.Stub.asInterface(service);
148 if (mMediaScannerService != null) {
149 updateFromProvider();
154 public void disconnectMediaScanner() {
155 synchronized (DownloadService.this) {
156 if (mMediaScannerService != null) {
157 mMediaScannerService = null;
158 if (Constants.LOGVV) {
159 Log.v(Constants.TAG, "Disconnecting from Media Scanner");
163 } catch (IllegalArgumentException ex) {
164 if (Constants.LOGV) {
165 Log.v(Constants.TAG, "unbindService threw up: " + ex);
172 public void onServiceDisconnected(ComponentName className) {
173 if (Constants.LOGVV) {
174 Log.v(Constants.TAG, "Disconnected from Media Scanner");
176 synchronized (DownloadService.this) {
177 mMediaScannerService = null;
182 /* ------------ Methods ------------ */
185 * Returns an IBinder instance when someone wants to connect to this
186 * service. Binding to this service is not allowed.
188 * @throws UnsupportedOperationException
190 public IBinder onBind(Intent i) {
191 throw new UnsupportedOperationException("Cannot bind to Download Manager Service");
195 * Initializes the service when it is first created
197 public void onCreate() {
199 if (Constants.LOGVV) {
200 Log.v(Constants.TAG, "Service onCreate");
203 mDownloads = Lists.newArrayList();
205 mObserver = new DownloadManagerContentObserver();
206 getContentResolver().registerContentObserver(Downloads.CONTENT_URI,
209 mMediaScannerService = null;
210 mMediaScannerConnecting = false;
211 mMediaScannerConnection = new MediaScannerConnection();
213 mNotifier = new DownloadNotification(this);
214 mNotifier.mNotificationMgr.cancelAll();
215 mNotifier.updateNotification();
218 removeSpuriousFiles();
219 updateFromProvider();
223 * Responds to a call to startService
225 public void onStart(Intent intent, int startId) {
226 super.onStart(intent, startId);
227 if (Constants.LOGVV) {
228 Log.v(Constants.TAG, "Service onStart");
231 updateFromProvider();
235 * Cleans up when the service is destroyed
237 public void onDestroy() {
238 getContentResolver().unregisterContentObserver(mObserver);
239 if (Constants.LOGVV) {
240 Log.v(Constants.TAG, "Service onDestroy");
246 * Parses data from the content provider into private array
248 private void updateFromProvider() {
249 synchronized (this) {
250 mPendingUpdate = true;
251 if (mUpdateThread == null) {
252 mUpdateThread = new UpdateThread();
253 mUpdateThread.start();
258 private class UpdateThread extends Thread {
259 public UpdateThread() {
260 super("Download Service");
264 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
266 boolean keepService = false;
267 // for each update from the database, remember which download is
268 // supposed to get restarted soonest in the future
269 long wakeUp = Long.MAX_VALUE;
271 synchronized (DownloadService.this) {
272 if (mUpdateThread != this) {
273 throw new IllegalStateException(
274 "multiple UpdateThreads in DownloadService");
276 if (!mPendingUpdate) {
277 mUpdateThread = null;
281 if (wakeUp != Long.MAX_VALUE) {
282 AlarmManager alarms =
283 (AlarmManager) getSystemService(Context.ALARM_SERVICE);
284 if (alarms == null) {
285 Log.e(Constants.TAG, "couldn't get alarm manager");
287 if (Constants.LOGV) {
288 Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms");
290 Intent intent = new Intent(Constants.ACTION_RETRY);
291 intent.setClassName("com.android.providers.downloads",
292 DownloadReceiver.class.getName());
294 AlarmManager.RTC_WAKEUP,
295 System.currentTimeMillis() + wakeUp,
296 PendingIntent.getBroadcast(DownloadService.this, 0, intent,
297 PendingIntent.FLAG_ONE_SHOT));
304 mPendingUpdate = false;
306 boolean networkAvailable = Helpers.isNetworkAvailable(DownloadService.this);
307 boolean networkRoaming = Helpers.isNetworkRoaming(DownloadService.this);
308 long now = System.currentTimeMillis();
310 Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
311 null, null, null, Downloads._ID);
313 if (cursor == null) {
314 // TODO: this doesn't look right, it'd leave the loop in an inconsistent state
318 cursor.moveToFirst();
322 boolean mustScan = false;
324 wakeUp = Long.MAX_VALUE;
326 boolean isAfterLast = cursor.isAfterLast();
328 int idColumn = cursor.getColumnIndexOrThrow(Downloads._ID);
331 * Walk the cursor and the local array to keep them in sync. The key
332 * to the algorithm is that the ids are unique and sorted both in
333 * the cursor and in the array, so that they can be processed in
334 * order in both sources at the same time: at each step, both
335 * sources point to the lowest id that hasn't been processed from
336 * that source, and the algorithm processes the lowest id from
337 * those two possibilities.
339 * -If the array contains an entry that's not in the cursor, remove the
340 * entry, move to next entry in the array.
341 * -If the array contains an entry that's in the cursor, nothing to do,
342 * move to next cursor row and next array entry.
343 * -If the cursor contains an entry that's not in the array, insert
344 * a new entry in the array, move to next cursor row and next
347 while (!isAfterLast || arrayPos < mDownloads.size()) {
349 // We're beyond the end of the cursor but there's still some
350 // stuff in the local array, which can only be junk
351 if (Constants.LOGVV) {
352 int arrayId = ((DownloadInfo) mDownloads.get(arrayPos)).mId;
353 Log.v(Constants.TAG, "Array update: trimming " +
354 arrayId + " @ " + arrayPos);
356 if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
357 scanFile(null, arrayPos);
359 deleteDownload(arrayPos); // this advances in the array
361 int id = cursor.getInt(idColumn);
363 if (arrayPos == mDownloads.size()) {
364 insertDownload(cursor, arrayPos, networkAvailable, networkRoaming, now);
365 if (Constants.LOGVV) {
366 Log.v(Constants.TAG, "Array update: appending " +
367 id + " @ " + arrayPos);
369 if (shouldScanFile(arrayPos)
370 && (!mediaScannerConnected() || !scanFile(cursor, arrayPos))) {
374 if (visibleNotification(arrayPos)) {
377 long next = nextAction(arrayPos, now);
380 } else if (next > 0 && next < wakeUp) {
385 isAfterLast = cursor.isAfterLast();
387 int arrayId = mDownloads.get(arrayPos).mId;
390 // The array entry isn't in the cursor
391 if (Constants.LOGVV) {
392 Log.v(Constants.TAG, "Array update: removing " + arrayId
395 if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
396 scanFile(null, arrayPos);
398 deleteDownload(arrayPos); // this advances in the array
399 } else if (arrayId == id) {
400 // This cursor row already exists in the stored array
403 networkAvailable, networkRoaming, now);
404 if (shouldScanFile(arrayPos)
405 && (!mediaScannerConnected()
406 || !scanFile(cursor, arrayPos))) {
410 if (visibleNotification(arrayPos)) {
413 long next = nextAction(arrayPos, now);
416 } else if (next > 0 && next < wakeUp) {
421 isAfterLast = cursor.isAfterLast();
423 // This cursor entry didn't exist in the stored array
424 if (Constants.LOGVV) {
425 Log.v(Constants.TAG, "Array update: inserting " +
426 id + " @ " + arrayPos);
430 networkAvailable, networkRoaming, now);
431 if (shouldScanFile(arrayPos)
432 && (!mediaScannerConnected()
433 || !scanFile(cursor, arrayPos))) {
437 if (visibleNotification(arrayPos)) {
440 long next = nextAction(arrayPos, now);
443 } else if (next > 0 && next < wakeUp) {
448 isAfterLast = cursor.isAfterLast();
454 mNotifier.updateNotification();
457 if (!mMediaScannerConnecting) {
458 Intent intent = new Intent();
459 intent.setClassName("com.android.providers.media",
460 "com.android.providers.media.MediaScannerService");
461 mMediaScannerConnecting = true;
462 bindService(intent, mMediaScannerConnection, BIND_AUTO_CREATE);
465 mMediaScannerConnection.disconnectMediaScanner();
474 * Removes files that may have been left behind in the cache directory
476 private void removeSpuriousFiles() {
477 File[] files = Environment.getDownloadCacheDirectory().listFiles();
479 // The cache folder doesn't appear to exist (this is likely the case
480 // when running the simulator).
483 HashSet<String> fileSet = new HashSet();
484 for (int i = 0; i < files.length; i++) {
485 if (files[i].getName().equals(Constants.KNOWN_SPURIOUS_FILENAME)) {
488 if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) {
491 fileSet.add(files[i].getPath());
494 Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
495 new String[] { Downloads._DATA }, null, null, null);
496 if (cursor != null) {
497 if (cursor.moveToFirst()) {
499 fileSet.remove(cursor.getString(0));
500 } while (cursor.moveToNext());
504 Iterator<String> iterator = fileSet.iterator();
505 while (iterator.hasNext()) {
506 String filename = iterator.next();
507 if (Constants.LOGV) {
508 Log.v(Constants.TAG, "deleting spurious file " + filename);
510 new File(filename).delete();
515 * Drops old rows from the database to prevent it from growing too large
517 private void trimDatabase() {
518 Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
519 new String[] { Downloads._ID },
520 Downloads.COLUMN_STATUS + " >= '200'", null,
521 Downloads.COLUMN_LAST_MODIFICATION);
522 if (cursor == null) {
523 // This isn't good - if we can't do basic queries in our database, nothing's gonna work
524 Log.e(Constants.TAG, "null cursor in trimDatabase");
527 if (cursor.moveToFirst()) {
528 int numDelete = cursor.getCount() - Constants.MAX_DOWNLOADS;
529 int columnId = cursor.getColumnIndexOrThrow(Downloads._ID);
530 while (numDelete > 0) {
531 getContentResolver().delete(
532 ContentUris.withAppendedId(Downloads.CONTENT_URI, cursor.getLong(columnId)),
534 if (!cursor.moveToNext()) {
544 * Keeps a local copy of the info about a download, and initiates the
545 * download if appropriate.
547 private void insertDownload(
548 Cursor cursor, int arrayPos,
549 boolean networkAvailable, boolean networkRoaming, long now) {
550 int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
551 int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
553 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
554 DownloadInfo info = new DownloadInfo(
555 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID)),
556 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_URI)),
557 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1,
558 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_FILE_NAME_HINT)),
559 cursor.getString(cursor.getColumnIndexOrThrow(Downloads._DATA)),
560 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE)),
561 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_DESTINATION)),
562 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY)),
563 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL)),
564 cursor.getInt(statusColumn),
565 cursor.getInt(failedColumn),
566 retryRedirect & 0xfffffff,
568 cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION)),
569 cursor.getString(cursor.getColumnIndexOrThrow(
570 Downloads.COLUMN_NOTIFICATION_PACKAGE)),
571 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NOTIFICATION_CLASS)),
572 cursor.getString(cursor.getColumnIndexOrThrow(
573 Downloads.COLUMN_NOTIFICATION_EXTRAS)),
574 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_COOKIE_DATA)),
575 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_USER_AGENT)),
576 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_REFERER)),
577 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES)),
578 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES)),
579 cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)),
580 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1);
582 if (Constants.LOGVV) {
583 Log.v(Constants.TAG, "Service adding new entry");
584 Log.v(Constants.TAG, "ID : " + info.mId);
585 Log.v(Constants.TAG, "URI : " + ((info.mUri != null) ? "yes" : "no"));
586 Log.v(Constants.TAG, "NO_INTEG: " + info.mNoIntegrity);
587 Log.v(Constants.TAG, "HINT : " + info.mHint);
588 Log.v(Constants.TAG, "FILENAME: " + info.mFileName);
589 Log.v(Constants.TAG, "MIMETYPE: " + info.mMimeType);
590 Log.v(Constants.TAG, "DESTINAT: " + info.mDestination);
591 Log.v(Constants.TAG, "VISIBILI: " + info.mVisibility);
592 Log.v(Constants.TAG, "CONTROL : " + info.mControl);
593 Log.v(Constants.TAG, "STATUS : " + info.mStatus);
594 Log.v(Constants.TAG, "FAILED_C: " + info.mNumFailed);
595 Log.v(Constants.TAG, "RETRY_AF: " + info.mRetryAfter);
596 Log.v(Constants.TAG, "REDIRECT: " + info.mRedirectCount);
597 Log.v(Constants.TAG, "LAST_MOD: " + info.mLastMod);
598 Log.v(Constants.TAG, "PACKAGE : " + info.mPackage);
599 Log.v(Constants.TAG, "CLASS : " + info.mClass);
600 Log.v(Constants.TAG, "COOKIES : " + ((info.mCookies != null) ? "yes" : "no"));
601 Log.v(Constants.TAG, "AGENT : " + info.mUserAgent);
602 Log.v(Constants.TAG, "REFERER : " + ((info.mReferer != null) ? "yes" : "no"));
603 Log.v(Constants.TAG, "TOTAL : " + info.mTotalBytes);
604 Log.v(Constants.TAG, "CURRENT : " + info.mCurrentBytes);
605 Log.v(Constants.TAG, "ETAG : " + info.mETag);
606 Log.v(Constants.TAG, "SCANNED : " + info.mMediaScanned);
609 mDownloads.add(arrayPos, info);
611 if (info.mStatus == 0
612 && (info.mDestination == Downloads.DESTINATION_EXTERNAL
613 || info.mDestination == Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE)
614 && info.mMimeType != null
615 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType)) {
616 // Check to see if we are allowed to download this file. Only files
617 // that can be handled by the platform can be downloaded.
618 // special case DRM files, which we should always allow downloading.
619 Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
621 // We can provide data as either content: or file: URIs,
622 // so allow both. (I think it would be nice if we just did
623 // everything as content: URIs)
624 // Actually, right now the download manager's UId restrictions
625 // prevent use from using content: so it's got to be file: or
628 mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mMimeType);
629 ResolveInfo ri = getPackageManager().resolveActivity(mimetypeIntent,
630 PackageManager.MATCH_DEFAULT_ONLY);
631 //Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list);
635 Log.d(Constants.TAG, "no application to handle MIME type " + info.mMimeType);
637 info.mStatus = Downloads.STATUS_NOT_ACCEPTABLE;
639 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId);
640 ContentValues values = new ContentValues();
641 values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_NOT_ACCEPTABLE);
642 getContentResolver().update(uri, values, null, null);
643 info.sendIntentIfRequested(uri, this);
648 if (info.canUseNetwork(networkAvailable, networkRoaming)) {
649 if (info.isReadyToStart(now)) {
650 if (Constants.LOGV) {
651 Log.v(Constants.TAG, "Service spawning thread to handle new download " +
654 if (info.mHasActiveThread) {
655 throw new IllegalStateException("Multiple threads on same download on insert");
657 if (info.mStatus != Downloads.STATUS_RUNNING) {
658 info.mStatus = Downloads.STATUS_RUNNING;
659 ContentValues values = new ContentValues();
660 values.put(Downloads.COLUMN_STATUS, info.mStatus);
661 getContentResolver().update(
662 ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId),
665 DownloadThread downloader = new DownloadThread(this, info);
666 info.mHasActiveThread = true;
670 if (info.mStatus == 0
671 || info.mStatus == Downloads.STATUS_PENDING
672 || info.mStatus == Downloads.STATUS_RUNNING) {
673 info.mStatus = Downloads.STATUS_RUNNING_PAUSED;
674 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId);
675 ContentValues values = new ContentValues();
676 values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_RUNNING_PAUSED);
677 getContentResolver().update(uri, values, null, null);
683 * Updates the local copy of the info about a download.
685 private void updateDownload(
686 Cursor cursor, int arrayPos,
687 boolean networkAvailable, boolean networkRoaming, long now) {
688 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
689 int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
690 int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
691 info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID));
692 info.mUri = stringFromCursor(info.mUri, cursor, Downloads.COLUMN_URI);
694 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1;
695 info.mHint = stringFromCursor(info.mHint, cursor, Downloads.COLUMN_FILE_NAME_HINT);
696 info.mFileName = stringFromCursor(info.mFileName, cursor, Downloads._DATA);
697 info.mMimeType = stringFromCursor(info.mMimeType, cursor, Downloads.COLUMN_MIME_TYPE);
698 info.mDestination = cursor.getInt(cursor.getColumnIndexOrThrow(
699 Downloads.COLUMN_DESTINATION));
700 int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(
701 Downloads.COLUMN_VISIBILITY));
702 if (info.mVisibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
703 && newVisibility != Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
704 && Downloads.isStatusCompleted(info.mStatus)) {
705 mNotifier.mNotificationMgr.cancel(info.mId);
707 info.mVisibility = newVisibility;
708 synchronized (info) {
709 info.mControl = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL));
711 int newStatus = cursor.getInt(statusColumn);
712 if (!Downloads.isStatusCompleted(info.mStatus) && Downloads.isStatusCompleted(newStatus)) {
713 mNotifier.mNotificationMgr.cancel(info.mId);
715 info.mStatus = newStatus;
716 info.mNumFailed = cursor.getInt(failedColumn);
718 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
719 info.mRetryAfter = retryRedirect & 0xfffffff;
720 info.mRedirectCount = retryRedirect >> 28;
721 info.mLastMod = cursor.getLong(cursor.getColumnIndexOrThrow(
722 Downloads.COLUMN_LAST_MODIFICATION));
723 info.mPackage = stringFromCursor(
724 info.mPackage, cursor, Downloads.COLUMN_NOTIFICATION_PACKAGE);
725 info.mClass = stringFromCursor(info.mClass, cursor, Downloads.COLUMN_NOTIFICATION_CLASS);
726 info.mCookies = stringFromCursor(info.mCookies, cursor, Downloads.COLUMN_COOKIE_DATA);
727 info.mUserAgent = stringFromCursor(info.mUserAgent, cursor, Downloads.COLUMN_USER_AGENT);
728 info.mReferer = stringFromCursor(info.mReferer, cursor, Downloads.COLUMN_REFERER);
729 info.mTotalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(
730 Downloads.COLUMN_TOTAL_BYTES));
731 info.mCurrentBytes = cursor.getInt(cursor.getColumnIndexOrThrow(
732 Downloads.COLUMN_CURRENT_BYTES));
733 info.mETag = stringFromCursor(info.mETag, cursor, Constants.ETAG);
735 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1;
737 if (info.canUseNetwork(networkAvailable, networkRoaming)) {
738 if (info.isReadyToRestart(now)) {
739 if (Constants.LOGV) {
740 Log.v(Constants.TAG, "Service spawning thread to handle updated download " +
743 if (info.mHasActiveThread) {
744 throw new IllegalStateException("Multiple threads on same download on update");
746 info.mStatus = Downloads.STATUS_RUNNING;
747 ContentValues values = new ContentValues();
748 values.put(Downloads.COLUMN_STATUS, info.mStatus);
749 getContentResolver().update(
750 ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId),
752 DownloadThread downloader = new DownloadThread(this, info);
753 info.mHasActiveThread = true;
760 * Returns a String that holds the current value of the column,
761 * optimizing for the case where the value hasn't changed.
763 private String stringFromCursor(String old, Cursor cursor, String column) {
764 int index = cursor.getColumnIndexOrThrow(column);
766 return cursor.getString(index);
768 if (mNewChars == null) {
769 mNewChars = new CharArrayBuffer(128);
771 cursor.copyStringToBuffer(index, mNewChars);
772 int length = mNewChars.sizeCopied;
773 if (length != old.length()) {
774 return cursor.getString(index);
776 if (oldChars == null || oldChars.sizeCopied < length) {
777 oldChars = new CharArrayBuffer(length);
779 char[] oldArray = oldChars.data;
780 char[] newArray = mNewChars.data;
781 old.getChars(0, length, oldArray, 0);
782 for (int i = length - 1; i >= 0; --i) {
783 if (oldArray[i] != newArray[i]) {
784 return new String(newArray, 0, length);
791 * Removes the local copy of the info about a download.
793 private void deleteDownload(int arrayPos) {
794 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
795 if (info.mStatus == Downloads.STATUS_RUNNING) {
796 info.mStatus = Downloads.STATUS_CANCELED;
797 } else if (info.mDestination != Downloads.DESTINATION_EXTERNAL && info.mFileName != null) {
798 new File(info.mFileName).delete();
800 mNotifier.mNotificationMgr.cancel(info.mId);
802 mDownloads.remove(arrayPos);
806 * Returns the amount of time (as measured from the "now" parameter)
807 * at which a download will be active.
808 * 0 = immediately - service should stick around to handle this download.
809 * -1 = never - service can go away without ever waking up.
810 * positive value - service must wake up in the future, as specified in ms from "now"
812 private long nextAction(int arrayPos, long now) {
813 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
814 if (Downloads.isStatusCompleted(info.mStatus)) {
817 if (info.mStatus != Downloads.STATUS_RUNNING_PAUSED) {
820 if (info.mNumFailed == 0) {
823 long when = info.restartTime();
831 * Returns whether there's a visible notification for this download
833 private boolean visibleNotification(int arrayPos) {
834 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
835 return info.hasCompletionNotification();
839 * Returns whether a file should be scanned
841 private boolean shouldScanFile(int arrayPos) {
842 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
843 return !info.mMediaScanned
844 && info.mDestination == Downloads.DESTINATION_EXTERNAL
845 && Downloads.isStatusSuccess(info.mStatus)
846 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType);
850 * Returns whether we have a live connection to the Media Scanner
852 private boolean mediaScannerConnected() {
853 return mMediaScannerService != null;
857 * Attempts to scan the file if necessary.
858 * Returns true if the file has been properly scanned.
860 private boolean scanFile(Cursor cursor, int arrayPos) {
861 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
862 synchronized (this) {
863 if (mMediaScannerService != null) {
865 if (Constants.LOGV) {
866 Log.v(Constants.TAG, "Scanning file " + info.mFileName);
868 mMediaScannerService.scanFile(info.mFileName, info.mMimeType);
869 if (cursor != null) {
870 ContentValues values = new ContentValues();
871 values.put(Constants.MEDIA_SCANNED, 1);
872 getContentResolver().update(
873 ContentUris.withAppendedId(Downloads.CONTENT_URI,
874 cursor.getLong(cursor.getColumnIndexOrThrow(Downloads._ID))),
878 } catch (RemoteException e) {
880 Log.d(Constants.TAG, "Failed to scan file " + info.mFileName);