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) {
317 cursor.moveToFirst();
321 boolean mustScan = false;
323 wakeUp = Long.MAX_VALUE;
325 boolean isAfterLast = cursor.isAfterLast();
327 int idColumn = cursor.getColumnIndexOrThrow(Downloads._ID);
330 * Walk the cursor and the local array to keep them in sync. The key
331 * to the algorithm is that the ids are unique and sorted both in
332 * the cursor and in the array, so that they can be processed in
333 * order in both sources at the same time: at each step, both
334 * sources point to the lowest id that hasn't been processed from
335 * that source, and the algorithm processes the lowest id from
336 * those two possibilities.
338 * -If the array contains an entry that's not in the cursor, remove the
339 * entry, move to next entry in the array.
340 * -If the array contains an entry that's in the cursor, nothing to do,
341 * move to next cursor row and next array entry.
342 * -If the cursor contains an entry that's not in the array, insert
343 * a new entry in the array, move to next cursor row and next
346 while (!isAfterLast || arrayPos < mDownloads.size()) {
348 // We're beyond the end of the cursor but there's still some
349 // stuff in the local array, which can only be junk
350 if (Constants.LOGVV) {
351 int arrayId = ((DownloadInfo) mDownloads.get(arrayPos)).mId;
352 Log.v(Constants.TAG, "Array update: trimming " +
353 arrayId + " @ " + arrayPos);
355 if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
356 scanFile(null, arrayPos);
358 deleteDownload(arrayPos); // this advances in the array
360 int id = cursor.getInt(idColumn);
362 if (arrayPos == mDownloads.size()) {
363 insertDownload(cursor, arrayPos, networkAvailable, networkRoaming, now);
364 if (Constants.LOGVV) {
365 Log.v(Constants.TAG, "Array update: inserting " +
366 id + " @ " + arrayPos);
368 if (shouldScanFile(arrayPos)
369 && (!mediaScannerConnected() || !scanFile(cursor, arrayPos))) {
373 if (visibleNotification(arrayPos)) {
376 long next = nextAction(arrayPos, now);
379 } else if (next > 0 && next < wakeUp) {
384 isAfterLast = cursor.isAfterLast();
386 int arrayId = mDownloads.get(arrayPos).mId;
389 // The array entry isn't in the cursor
390 if (Constants.LOGVV) {
391 Log.v(Constants.TAG, "Array update: removing " + arrayId
394 if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
395 scanFile(null, arrayPos);
397 deleteDownload(arrayPos); // this advances in the array
398 } else if (arrayId == id) {
399 // This cursor row already exists in the stored array
402 networkAvailable, networkRoaming, now);
403 if (shouldScanFile(arrayPos)
404 && (!mediaScannerConnected()
405 || !scanFile(cursor, arrayPos))) {
409 if (visibleNotification(arrayPos)) {
412 long next = nextAction(arrayPos, now);
415 } else if (next > 0 && next < wakeUp) {
420 isAfterLast = cursor.isAfterLast();
422 // This cursor entry didn't exist in the stored array
423 if (Constants.LOGVV) {
424 Log.v(Constants.TAG, "Array update: appending " +
425 id + " @ " + arrayPos);
429 networkAvailable, networkRoaming, now);
430 if (shouldScanFile(arrayPos)
431 && (!mediaScannerConnected()
432 || !scanFile(cursor, arrayPos))) {
436 if (visibleNotification(arrayPos)) {
439 long next = nextAction(arrayPos, now);
442 } else if (next > 0 && next < wakeUp) {
447 isAfterLast = cursor.isAfterLast();
453 mNotifier.updateNotification();
456 if (!mMediaScannerConnecting) {
457 Intent intent = new Intent();
458 intent.setClassName("com.android.providers.media",
459 "com.android.providers.media.MediaScannerService");
460 mMediaScannerConnecting = true;
461 bindService(intent, mMediaScannerConnection, BIND_AUTO_CREATE);
464 mMediaScannerConnection.disconnectMediaScanner();
473 * Removes files that may have been left behind in the cache directory
475 private void removeSpuriousFiles() {
476 File[] files = Environment.getDownloadCacheDirectory().listFiles();
478 // The cache folder doesn't appear to exist (this is likely the case
479 // when running the simulator).
482 HashSet<String> fileSet = new HashSet();
483 for (int i = 0; i < files.length; i++) {
484 if (files[i].getName().equals(Constants.KNOWN_SPURIOUS_FILENAME)) {
487 if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) {
490 fileSet.add(files[i].getPath());
493 Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
494 new String[] { Downloads._DATA }, null, null, null);
495 if (cursor != null) {
496 if (cursor.moveToFirst()) {
498 fileSet.remove(cursor.getString(0));
499 } while (cursor.moveToNext());
503 Iterator<String> iterator = fileSet.iterator();
504 while (iterator.hasNext()) {
505 String filename = iterator.next();
506 if (Constants.LOGV) {
507 Log.v(Constants.TAG, "deleting spurious file " + filename);
509 new File(filename).delete();
514 * Drops old rows from the database to prevent it from growing too large
516 private void trimDatabase() {
517 Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
518 new String[] { Downloads._ID },
519 Downloads.COLUMN_STATUS + " >= '200'", null,
520 Downloads.COLUMN_LAST_MODIFICATION);
521 if (cursor == null) {
522 // This isn't good - if we can't do basic queries in our database, nothing's gonna work
523 Log.e(Constants.TAG, "null cursor in trimDatabase");
526 if (cursor.moveToFirst()) {
527 int numDelete = cursor.getCount() - Constants.MAX_DOWNLOADS;
528 int columnId = cursor.getColumnIndexOrThrow(Downloads._ID);
529 while (numDelete > 0) {
530 getContentResolver().delete(
531 ContentUris.withAppendedId(Downloads.CONTENT_URI, cursor.getLong(columnId)),
533 if (!cursor.moveToNext()) {
543 * Keeps a local copy of the info about a download, and initiates the
544 * download if appropriate.
546 private void insertDownload(
547 Cursor cursor, int arrayPos,
548 boolean networkAvailable, boolean networkRoaming, long now) {
549 int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
550 int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
552 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
553 DownloadInfo info = new DownloadInfo(
554 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID)),
555 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_URI)),
556 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1,
557 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_FILE_NAME_HINT)),
558 cursor.getString(cursor.getColumnIndexOrThrow(Downloads._DATA)),
559 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE)),
560 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_DESTINATION)),
561 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY)),
562 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL)),
563 cursor.getInt(statusColumn),
564 cursor.getInt(failedColumn),
565 retryRedirect & 0xfffffff,
567 cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION)),
568 cursor.getString(cursor.getColumnIndexOrThrow(
569 Downloads.COLUMN_NOTIFICATION_PACKAGE)),
570 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NOTIFICATION_CLASS)),
571 cursor.getString(cursor.getColumnIndexOrThrow(
572 Downloads.COLUMN_NOTIFICATION_EXTRAS)),
573 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_COOKIE_DATA)),
574 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_USER_AGENT)),
575 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_REFERER)),
576 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES)),
577 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES)),
578 cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)),
579 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1);
581 if (Constants.LOGVV) {
582 Log.v(Constants.TAG, "Service adding new entry");
583 Log.v(Constants.TAG, "ID : " + info.mId);
584 Log.v(Constants.TAG, "URI : " + ((info.mUri != null) ? "yes" : "no"));
585 Log.v(Constants.TAG, "NO_INTEG: " + info.mNoIntegrity);
586 Log.v(Constants.TAG, "HINT : " + info.mHint);
587 Log.v(Constants.TAG, "FILENAME: " + info.mFileName);
588 Log.v(Constants.TAG, "MIMETYPE: " + info.mMimeType);
589 Log.v(Constants.TAG, "DESTINAT: " + info.mDestination);
590 Log.v(Constants.TAG, "VISIBILI: " + info.mVisibility);
591 Log.v(Constants.TAG, "CONTROL : " + info.mControl);
592 Log.v(Constants.TAG, "STATUS : " + info.mStatus);
593 Log.v(Constants.TAG, "FAILED_C: " + info.mNumFailed);
594 Log.v(Constants.TAG, "RETRY_AF: " + info.mRetryAfter);
595 Log.v(Constants.TAG, "REDIRECT: " + info.mRedirectCount);
596 Log.v(Constants.TAG, "LAST_MOD: " + info.mLastMod);
597 Log.v(Constants.TAG, "PACKAGE : " + info.mPackage);
598 Log.v(Constants.TAG, "CLASS : " + info.mClass);
599 Log.v(Constants.TAG, "COOKIES : " + ((info.mCookies != null) ? "yes" : "no"));
600 Log.v(Constants.TAG, "AGENT : " + info.mUserAgent);
601 Log.v(Constants.TAG, "REFERER : " + ((info.mReferer != null) ? "yes" : "no"));
602 Log.v(Constants.TAG, "TOTAL : " + info.mTotalBytes);
603 Log.v(Constants.TAG, "CURRENT : " + info.mCurrentBytes);
604 Log.v(Constants.TAG, "ETAG : " + info.mETag);
605 Log.v(Constants.TAG, "SCANNED : " + info.mMediaScanned);
608 mDownloads.add(arrayPos, info);
610 if (info.mStatus == 0
611 && (info.mDestination == Downloads.DESTINATION_EXTERNAL
612 || info.mDestination == Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE)
613 && info.mMimeType != null
614 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType)) {
615 // Check to see if we are allowed to download this file. Only files
616 // that can be handled by the platform can be downloaded.
617 // special case DRM files, which we should always allow downloading.
618 Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
620 // We can provide data as either content: or file: URIs,
621 // so allow both. (I think it would be nice if we just did
622 // everything as content: URIs)
623 // Actually, right now the download manager's UId restrictions
624 // prevent use from using content: so it's got to be file: or
627 mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mMimeType);
628 ResolveInfo ri = getPackageManager().resolveActivity(mimetypeIntent,
629 PackageManager.MATCH_DEFAULT_ONLY);
630 //Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list);
634 Log.d(Constants.TAG, "no application to handle MIME type " + info.mMimeType);
636 info.mStatus = Downloads.STATUS_NOT_ACCEPTABLE;
638 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId);
639 ContentValues values = new ContentValues();
640 values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_NOT_ACCEPTABLE);
641 getContentResolver().update(uri, values, null, null);
642 info.sendIntentIfRequested(uri, this);
647 if (info.canUseNetwork(networkAvailable, networkRoaming)) {
648 if (info.isReadyToStart(now)) {
649 if (Constants.LOGV) {
650 Log.v(Constants.TAG, "Service spawning thread to handle new download " +
653 if (info.mHasActiveThread) {
654 throw new IllegalStateException("Multiple threads on same download on insert");
656 if (info.mStatus != Downloads.STATUS_RUNNING) {
657 info.mStatus = Downloads.STATUS_RUNNING;
658 ContentValues values = new ContentValues();
659 values.put(Downloads.COLUMN_STATUS, info.mStatus);
660 getContentResolver().update(
661 ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId),
664 DownloadThread downloader = new DownloadThread(this, info);
665 info.mHasActiveThread = true;
669 if (info.mStatus == 0
670 || info.mStatus == Downloads.STATUS_PENDING
671 || info.mStatus == Downloads.STATUS_RUNNING) {
672 info.mStatus = Downloads.STATUS_RUNNING_PAUSED;
673 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId);
674 ContentValues values = new ContentValues();
675 values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_RUNNING_PAUSED);
676 getContentResolver().update(uri, values, null, null);
682 * Updates the local copy of the info about a download.
684 private void updateDownload(
685 Cursor cursor, int arrayPos,
686 boolean networkAvailable, boolean networkRoaming, long now) {
687 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
688 int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
689 int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
690 info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID));
691 info.mUri = stringFromCursor(info.mUri, cursor, Downloads.COLUMN_URI);
693 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1;
694 info.mHint = stringFromCursor(info.mHint, cursor, Downloads.COLUMN_FILE_NAME_HINT);
695 info.mFileName = stringFromCursor(info.mFileName, cursor, Downloads._DATA);
696 info.mMimeType = stringFromCursor(info.mMimeType, cursor, Downloads.COLUMN_MIME_TYPE);
697 info.mDestination = cursor.getInt(cursor.getColumnIndexOrThrow(
698 Downloads.COLUMN_DESTINATION));
699 int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(
700 Downloads.COLUMN_VISIBILITY));
701 if (info.mVisibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
702 && newVisibility != Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
703 && Downloads.isStatusCompleted(info.mStatus)) {
704 mNotifier.mNotificationMgr.cancel(info.mId);
706 info.mVisibility = newVisibility;
707 synchronized (info) {
708 info.mControl = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL));
710 int newStatus = cursor.getInt(statusColumn);
711 if (!Downloads.isStatusCompleted(info.mStatus) && Downloads.isStatusCompleted(newStatus)) {
712 mNotifier.mNotificationMgr.cancel(info.mId);
714 info.mStatus = newStatus;
715 info.mNumFailed = cursor.getInt(failedColumn);
717 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
718 info.mRetryAfter = retryRedirect & 0xfffffff;
719 info.mRedirectCount = retryRedirect >> 28;
720 info.mLastMod = cursor.getLong(cursor.getColumnIndexOrThrow(
721 Downloads.COLUMN_LAST_MODIFICATION));
722 info.mPackage = stringFromCursor(
723 info.mPackage, cursor, Downloads.COLUMN_NOTIFICATION_PACKAGE);
724 info.mClass = stringFromCursor(info.mClass, cursor, Downloads.COLUMN_NOTIFICATION_CLASS);
725 info.mCookies = stringFromCursor(info.mCookies, cursor, Downloads.COLUMN_COOKIE_DATA);
726 info.mUserAgent = stringFromCursor(info.mUserAgent, cursor, Downloads.COLUMN_USER_AGENT);
727 info.mReferer = stringFromCursor(info.mReferer, cursor, Downloads.COLUMN_REFERER);
728 info.mTotalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(
729 Downloads.COLUMN_TOTAL_BYTES));
730 info.mCurrentBytes = cursor.getInt(cursor.getColumnIndexOrThrow(
731 Downloads.COLUMN_CURRENT_BYTES));
732 info.mETag = stringFromCursor(info.mETag, cursor, Constants.ETAG);
734 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1;
736 if (info.canUseNetwork(networkAvailable, networkRoaming)) {
737 if (info.isReadyToRestart(now)) {
738 if (Constants.LOGV) {
739 Log.v(Constants.TAG, "Service spawning thread to handle updated download " +
742 if (info.mHasActiveThread) {
743 throw new IllegalStateException("Multiple threads on same download on update");
745 info.mStatus = Downloads.STATUS_RUNNING;
746 ContentValues values = new ContentValues();
747 values.put(Downloads.COLUMN_STATUS, info.mStatus);
748 getContentResolver().update(
749 ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId),
751 DownloadThread downloader = new DownloadThread(this, info);
752 info.mHasActiveThread = true;
759 * Returns a String that holds the current value of the column,
760 * optimizing for the case where the value hasn't changed.
762 private String stringFromCursor(String old, Cursor cursor, String column) {
763 int index = cursor.getColumnIndexOrThrow(column);
765 return cursor.getString(index);
767 if (mNewChars == null) {
768 mNewChars = new CharArrayBuffer(128);
770 cursor.copyStringToBuffer(index, mNewChars);
771 int length = mNewChars.sizeCopied;
772 if (length != old.length()) {
773 return cursor.getString(index);
775 if (oldChars == null || oldChars.sizeCopied < length) {
776 oldChars = new CharArrayBuffer(length);
778 char[] oldArray = oldChars.data;
779 char[] newArray = mNewChars.data;
780 old.getChars(0, length, oldArray, 0);
781 for (int i = length - 1; i >= 0; --i) {
782 if (oldArray[i] != newArray[i]) {
783 return new String(newArray, 0, length);
790 * Removes the local copy of the info about a download.
792 private void deleteDownload(int arrayPos) {
793 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
794 if (info.mStatus == Downloads.STATUS_RUNNING) {
795 info.mStatus = Downloads.STATUS_CANCELED;
796 } else if (info.mDestination != Downloads.DESTINATION_EXTERNAL && info.mFileName != null) {
797 new File(info.mFileName).delete();
799 mNotifier.mNotificationMgr.cancel(info.mId);
801 mDownloads.remove(arrayPos);
805 * Returns the amount of time (as measured from the "now" parameter)
806 * at which a download will be active.
807 * 0 = immediately - service should stick around to handle this download.
808 * -1 = never - service can go away without ever waking up.
809 * positive value - service must wake up in the future, as specified in ms from "now"
811 private long nextAction(int arrayPos, long now) {
812 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
813 if (Downloads.isStatusCompleted(info.mStatus)) {
816 if (info.mStatus != Downloads.STATUS_RUNNING_PAUSED) {
819 if (info.mNumFailed == 0) {
822 long when = info.restartTime();
830 * Returns whether there's a visible notification for this download
832 private boolean visibleNotification(int arrayPos) {
833 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
834 return info.hasCompletionNotification();
838 * Returns whether a file should be scanned
840 private boolean shouldScanFile(int arrayPos) {
841 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
842 return !info.mMediaScanned
843 && info.mDestination == Downloads.DESTINATION_EXTERNAL
844 && Downloads.isStatusSuccess(info.mStatus)
845 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType);
849 * Returns whether we have a live connection to the Media Scanner
851 private boolean mediaScannerConnected() {
852 return mMediaScannerService != null;
856 * Attempts to scan the file if necessary.
857 * Returns true if the file has been properly scanned.
859 private boolean scanFile(Cursor cursor, int arrayPos) {
860 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
861 synchronized (this) {
862 if (mMediaScannerService != null) {
864 if (Constants.LOGV) {
865 Log.v(Constants.TAG, "Scanning file " + info.mFileName);
867 mMediaScannerService.scanFile(info.mFileName, info.mMimeType);
868 if (cursor != null) {
869 ContentValues values = new ContentValues();
870 values.put(Constants.MEDIA_SCANNED, 1);
871 getContentResolver().update(
872 ContentUris.withAppendedId(Downloads.CONTENT_URI,
873 cursor.getLong(cursor.getColumnIndexOrThrow(Downloads._ID))),
877 } catch (RemoteException e) {
879 Log.d(Constants.TAG, "Failed to scan file " + info.mFileName);