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 if (Constants.LOGX) {
227 if (Helpers.isNetworkAvailable(this)) {
228 Log.i(Constants.TAG, "Service Start, Net Up");
230 Log.i(Constants.TAG, "Service Start, Net Down");
233 super.onStart(intent, startId);
234 if (Constants.LOGVV) {
235 Log.v(Constants.TAG, "Service onStart");
238 updateFromProvider();
242 * Cleans up when the service is destroyed
244 public void onDestroy() {
245 getContentResolver().unregisterContentObserver(mObserver);
246 if (Constants.LOGVV) {
247 Log.v(Constants.TAG, "Service onDestroy");
253 * Parses data from the content provider into private array
255 private void updateFromProvider() {
256 synchronized (this) {
257 mPendingUpdate = true;
258 if (mUpdateThread == null) {
259 mUpdateThread = new UpdateThread();
260 mUpdateThread.start();
265 private class UpdateThread extends Thread {
266 public UpdateThread() {
267 super("Download Service");
271 if (Constants.LOGX) {
272 if (Helpers.isNetworkAvailable(DownloadService.this)) {
273 Log.i(Constants.TAG, "Update, Net Up");
275 Log.i(Constants.TAG, "Update, Net Down");
278 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
280 boolean keepService = false;
281 // for each update from the database, remember which download is
282 // supposed to get restarted soonest in the future
283 long wakeUp = Long.MAX_VALUE;
285 synchronized (DownloadService.this) {
286 if (mUpdateThread != this) {
287 throw new IllegalStateException(
288 "multiple UpdateThreads in DownloadService");
290 if (!mPendingUpdate) {
291 mUpdateThread = null;
295 if (wakeUp != Long.MAX_VALUE) {
296 AlarmManager alarms =
297 (AlarmManager) getSystemService(Context.ALARM_SERVICE);
298 if (alarms == null) {
299 Log.e(Constants.TAG, "couldn't get alarm manager");
301 if (Constants.LOGV) {
302 Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms");
304 if (Constants.LOGX) {
305 if (Helpers.isNetworkAvailable(DownloadService.this)) {
306 Log.i(Constants.TAG, "Alarm in " + wakeUp + "ms, Net Up");
308 Log.i(Constants.TAG, "Alarm in " + wakeUp + "ms, Net Down");
311 Intent intent = new Intent(Constants.ACTION_RETRY);
312 intent.setClassName("com.android.providers.downloads",
313 DownloadReceiver.class.getName());
315 AlarmManager.RTC_WAKEUP,
316 System.currentTimeMillis() + wakeUp,
317 PendingIntent.getBroadcast(DownloadService.this, 0, intent,
318 PendingIntent.FLAG_ONE_SHOT));
325 mPendingUpdate = false;
327 boolean networkAvailable = Helpers.isNetworkAvailable(DownloadService.this);
328 boolean networkRoaming = Helpers.isNetworkRoaming(DownloadService.this);
329 long now = System.currentTimeMillis();
331 Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
332 null, null, null, Downloads._ID);
334 if (cursor == null) {
338 cursor.moveToFirst();
342 boolean mustScan = false;
344 wakeUp = Long.MAX_VALUE;
346 boolean isAfterLast = cursor.isAfterLast();
348 int idColumn = cursor.getColumnIndexOrThrow(Downloads._ID);
351 * Walk the cursor and the local array to keep them in sync. The key
352 * to the algorithm is that the ids are unique and sorted both in
353 * the cursor and in the array, so that they can be processed in
354 * order in both sources at the same time: at each step, both
355 * sources point to the lowest id that hasn't been processed from
356 * that source, and the algorithm processes the lowest id from
357 * those two possibilities.
359 * -If the array contains an entry that's not in the cursor, remove the
360 * entry, move to next entry in the array.
361 * -If the array contains an entry that's in the cursor, nothing to do,
362 * move to next cursor row and next array entry.
363 * -If the cursor contains an entry that's not in the array, insert
364 * a new entry in the array, move to next cursor row and next
367 while (!isAfterLast || arrayPos < mDownloads.size()) {
369 // We're beyond the end of the cursor but there's still some
370 // stuff in the local array, which can only be junk
371 if (Constants.LOGVV) {
372 int arrayId = ((DownloadInfo) mDownloads.get(arrayPos)).mId;
373 Log.v(Constants.TAG, "Array update: trimming " +
374 arrayId + " @ " + arrayPos);
376 if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
377 scanFile(null, arrayPos);
379 deleteDownload(arrayPos); // this advances in the array
381 int id = cursor.getInt(idColumn);
383 if (arrayPos == mDownloads.size()) {
384 insertDownload(cursor, arrayPos, networkAvailable, networkRoaming, now);
385 if (Constants.LOGVV) {
386 Log.v(Constants.TAG, "Array update: inserting " +
387 id + " @ " + arrayPos);
389 if (shouldScanFile(arrayPos)
390 && (!mediaScannerConnected() || !scanFile(cursor, arrayPos))) {
394 if (visibleNotification(arrayPos)) {
397 long next = nextAction(arrayPos, now);
400 } else if (next > 0 && next < wakeUp) {
405 isAfterLast = cursor.isAfterLast();
407 int arrayId = mDownloads.get(arrayPos).mId;
410 // The array entry isn't in the cursor
411 if (Constants.LOGVV) {
412 Log.v(Constants.TAG, "Array update: removing " + arrayId
415 if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
416 scanFile(null, arrayPos);
418 deleteDownload(arrayPos); // this advances in the array
419 } else if (arrayId == id) {
420 // This cursor row already exists in the stored array
423 networkAvailable, networkRoaming, now);
424 if (shouldScanFile(arrayPos)
425 && (!mediaScannerConnected()
426 || !scanFile(cursor, arrayPos))) {
430 if (visibleNotification(arrayPos)) {
433 long next = nextAction(arrayPos, now);
436 } else if (next > 0 && next < wakeUp) {
441 isAfterLast = cursor.isAfterLast();
443 // This cursor entry didn't exist in the stored array
444 if (Constants.LOGVV) {
445 Log.v(Constants.TAG, "Array update: appending " +
446 id + " @ " + arrayPos);
450 networkAvailable, networkRoaming, now);
451 if (shouldScanFile(arrayPos)
452 && (!mediaScannerConnected()
453 || !scanFile(cursor, arrayPos))) {
457 if (visibleNotification(arrayPos)) {
460 long next = nextAction(arrayPos, now);
463 } else if (next > 0 && next < wakeUp) {
468 isAfterLast = cursor.isAfterLast();
474 mNotifier.updateNotification();
477 if (!mMediaScannerConnecting) {
478 Intent intent = new Intent();
479 intent.setClassName("com.android.providers.media",
480 "com.android.providers.media.MediaScannerService");
481 mMediaScannerConnecting = true;
482 bindService(intent, mMediaScannerConnection, BIND_AUTO_CREATE);
485 mMediaScannerConnection.disconnectMediaScanner();
494 * Removes files that may have been left behind in the cache directory
496 private void removeSpuriousFiles() {
497 File[] files = Environment.getDownloadCacheDirectory().listFiles();
499 // The cache folder doesn't appear to exist (this is likely the case
500 // when running the simulator).
503 HashSet<String> fileSet = new HashSet();
504 for (int i = 0; i < files.length; i++) {
505 if (files[i].getName().equals(Constants.KNOWN_SPURIOUS_FILENAME)) {
508 if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) {
511 fileSet.add(files[i].getPath());
514 Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
515 new String[] { Downloads._DATA }, null, null, null);
516 if (cursor != null) {
517 if (cursor.moveToFirst()) {
519 fileSet.remove(cursor.getString(0));
520 } while (cursor.moveToNext());
524 Iterator<String> iterator = fileSet.iterator();
525 while (iterator.hasNext()) {
526 String filename = iterator.next();
527 if (Constants.LOGV) {
528 Log.v(Constants.TAG, "deleting spurious file " + filename);
530 new File(filename).delete();
535 * Drops old rows from the database to prevent it from growing too large
537 private void trimDatabase() {
538 Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
539 new String[] { Downloads._ID },
540 Downloads.COLUMN_STATUS + " >= '200'", null,
541 Downloads.COLUMN_LAST_MODIFICATION);
542 if (cursor == null) {
543 // This isn't good - if we can't do basic queries in our database, nothing's gonna work
544 Log.e(Constants.TAG, "null cursor in trimDatabase");
547 if (cursor.moveToFirst()) {
548 int numDelete = cursor.getCount() - Constants.MAX_DOWNLOADS;
549 int columnId = cursor.getColumnIndexOrThrow(Downloads._ID);
550 while (numDelete > 0) {
551 getContentResolver().delete(
552 ContentUris.withAppendedId(Downloads.CONTENT_URI, cursor.getLong(columnId)),
554 if (!cursor.moveToNext()) {
564 * Keeps a local copy of the info about a download, and initiates the
565 * download if appropriate.
567 private void insertDownload(
568 Cursor cursor, int arrayPos,
569 boolean networkAvailable, boolean networkRoaming, long now) {
570 int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
571 int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
573 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
574 DownloadInfo info = new DownloadInfo(
575 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID)),
576 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_URI)),
577 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1,
578 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_FILE_NAME_HINT)),
579 cursor.getString(cursor.getColumnIndexOrThrow(Downloads._DATA)),
580 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE)),
581 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_DESTINATION)),
582 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY)),
583 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL)),
584 cursor.getInt(statusColumn),
585 cursor.getInt(failedColumn),
586 retryRedirect & 0xfffffff,
588 cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION)),
589 cursor.getString(cursor.getColumnIndexOrThrow(
590 Downloads.COLUMN_NOTIFICATION_PACKAGE)),
591 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NOTIFICATION_CLASS)),
592 cursor.getString(cursor.getColumnIndexOrThrow(
593 Downloads.COLUMN_NOTIFICATION_EXTRAS)),
594 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_COOKIE_DATA)),
595 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_USER_AGENT)),
596 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_REFERER)),
597 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES)),
598 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES)),
599 cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)),
600 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1);
602 if (Constants.LOGVV) {
603 Log.v(Constants.TAG, "Service adding new entry");
604 Log.v(Constants.TAG, "ID : " + info.mId);
605 Log.v(Constants.TAG, "URI : " + ((info.mUri != null) ? "yes" : "no"));
606 Log.v(Constants.TAG, "NO_INTEG: " + info.mNoIntegrity);
607 Log.v(Constants.TAG, "HINT : " + info.mHint);
608 Log.v(Constants.TAG, "FILENAME: " + info.mFileName);
609 Log.v(Constants.TAG, "MIMETYPE: " + info.mMimeType);
610 Log.v(Constants.TAG, "DESTINAT: " + info.mDestination);
611 Log.v(Constants.TAG, "VISIBILI: " + info.mVisibility);
612 Log.v(Constants.TAG, "CONTROL : " + info.mControl);
613 Log.v(Constants.TAG, "STATUS : " + info.mStatus);
614 Log.v(Constants.TAG, "FAILED_C: " + info.mNumFailed);
615 Log.v(Constants.TAG, "RETRY_AF: " + info.mRetryAfter);
616 Log.v(Constants.TAG, "REDIRECT: " + info.mRedirectCount);
617 Log.v(Constants.TAG, "LAST_MOD: " + info.mLastMod);
618 Log.v(Constants.TAG, "PACKAGE : " + info.mPackage);
619 Log.v(Constants.TAG, "CLASS : " + info.mClass);
620 Log.v(Constants.TAG, "COOKIES : " + ((info.mCookies != null) ? "yes" : "no"));
621 Log.v(Constants.TAG, "AGENT : " + info.mUserAgent);
622 Log.v(Constants.TAG, "REFERER : " + ((info.mReferer != null) ? "yes" : "no"));
623 Log.v(Constants.TAG, "TOTAL : " + info.mTotalBytes);
624 Log.v(Constants.TAG, "CURRENT : " + info.mCurrentBytes);
625 Log.v(Constants.TAG, "ETAG : " + info.mETag);
626 Log.v(Constants.TAG, "SCANNED : " + info.mMediaScanned);
629 mDownloads.add(arrayPos, info);
631 if (info.mStatus == 0
632 && (info.mDestination == Downloads.DESTINATION_EXTERNAL
633 || info.mDestination == Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE)
634 && info.mMimeType != null
635 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType)) {
636 // Check to see if we are allowed to download this file. Only files
637 // that can be handled by the platform can be downloaded.
638 // special case DRM files, which we should always allow downloading.
639 Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
641 // We can provide data as either content: or file: URIs,
642 // so allow both. (I think it would be nice if we just did
643 // everything as content: URIs)
644 // Actually, right now the download manager's UId restrictions
645 // prevent use from using content: so it's got to be file: or
648 mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mMimeType);
649 ResolveInfo ri = getPackageManager().resolveActivity(mimetypeIntent,
650 PackageManager.MATCH_DEFAULT_ONLY);
651 //Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list);
655 Log.d(Constants.TAG, "no application to handle MIME type " + info.mMimeType);
657 info.mStatus = Downloads.STATUS_NOT_ACCEPTABLE;
659 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId);
660 ContentValues values = new ContentValues();
661 values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_NOT_ACCEPTABLE);
662 getContentResolver().update(uri, values, null, null);
663 info.sendIntentIfRequested(uri, this);
668 if (info.canUseNetwork(networkAvailable, networkRoaming)) {
669 if (info.isReadyToStart(now)) {
670 if (Constants.LOGV) {
671 Log.v(Constants.TAG, "Service spawning thread to handle new download " +
674 if (info.mHasActiveThread) {
675 throw new IllegalStateException("Multiple threads on same download on insert");
677 if (info.mStatus != Downloads.STATUS_RUNNING) {
678 info.mStatus = Downloads.STATUS_RUNNING;
679 ContentValues values = new ContentValues();
680 values.put(Downloads.COLUMN_STATUS, info.mStatus);
681 getContentResolver().update(
682 ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId),
685 if (Constants.LOGX) {
686 if (Helpers.isNetworkAvailable(DownloadService.this)) {
687 Log.i(Constants.TAG, "Thread for " + info.mId + ", Net Up");
689 Log.i(Constants.TAG, "Thread for " + info.mId + ", Net Down");
692 DownloadThread downloader = new DownloadThread(this, info);
693 info.mHasActiveThread = true;
697 if (info.mStatus == 0
698 || info.mStatus == Downloads.STATUS_PENDING
699 || info.mStatus == Downloads.STATUS_RUNNING) {
700 info.mStatus = Downloads.STATUS_RUNNING_PAUSED;
701 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId);
702 ContentValues values = new ContentValues();
703 values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_RUNNING_PAUSED);
704 getContentResolver().update(uri, values, null, null);
710 * Updates the local copy of the info about a download.
712 private void updateDownload(
713 Cursor cursor, int arrayPos,
714 boolean networkAvailable, boolean networkRoaming, long now) {
715 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
716 int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
717 int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
718 info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID));
719 info.mUri = stringFromCursor(info.mUri, cursor, Downloads.COLUMN_URI);
721 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1;
722 info.mHint = stringFromCursor(info.mHint, cursor, Downloads.COLUMN_FILE_NAME_HINT);
723 info.mFileName = stringFromCursor(info.mFileName, cursor, Downloads._DATA);
724 info.mMimeType = stringFromCursor(info.mMimeType, cursor, Downloads.COLUMN_MIME_TYPE);
725 info.mDestination = cursor.getInt(cursor.getColumnIndexOrThrow(
726 Downloads.COLUMN_DESTINATION));
727 int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(
728 Downloads.COLUMN_VISIBILITY));
729 if (info.mVisibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
730 && newVisibility != Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
731 && Downloads.isStatusCompleted(info.mStatus)) {
732 mNotifier.mNotificationMgr.cancel(info.mId);
734 info.mVisibility = newVisibility;
735 synchronized (info) {
736 info.mControl = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL));
738 int newStatus = cursor.getInt(statusColumn);
739 if (!Downloads.isStatusCompleted(info.mStatus) && Downloads.isStatusCompleted(newStatus)) {
740 mNotifier.mNotificationMgr.cancel(info.mId);
742 info.mStatus = newStatus;
743 info.mNumFailed = cursor.getInt(failedColumn);
745 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
746 info.mRetryAfter = retryRedirect & 0xfffffff;
747 info.mRedirectCount = retryRedirect >> 28;
748 info.mLastMod = cursor.getLong(cursor.getColumnIndexOrThrow(
749 Downloads.COLUMN_LAST_MODIFICATION));
750 info.mPackage = stringFromCursor(
751 info.mPackage, cursor, Downloads.COLUMN_NOTIFICATION_PACKAGE);
752 info.mClass = stringFromCursor(info.mClass, cursor, Downloads.COLUMN_NOTIFICATION_CLASS);
753 info.mCookies = stringFromCursor(info.mCookies, cursor, Downloads.COLUMN_COOKIE_DATA);
754 info.mUserAgent = stringFromCursor(info.mUserAgent, cursor, Downloads.COLUMN_USER_AGENT);
755 info.mReferer = stringFromCursor(info.mReferer, cursor, Downloads.COLUMN_REFERER);
756 info.mTotalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(
757 Downloads.COLUMN_TOTAL_BYTES));
758 info.mCurrentBytes = cursor.getInt(cursor.getColumnIndexOrThrow(
759 Downloads.COLUMN_CURRENT_BYTES));
760 info.mETag = stringFromCursor(info.mETag, cursor, Constants.ETAG);
762 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1;
764 if (info.canUseNetwork(networkAvailable, networkRoaming)) {
765 if (info.isReadyToRestart(now)) {
766 if (Constants.LOGV) {
767 Log.v(Constants.TAG, "Service spawning thread to handle updated download " +
770 if (info.mHasActiveThread) {
771 throw new IllegalStateException("Multiple threads on same download on update");
773 info.mStatus = Downloads.STATUS_RUNNING;
774 ContentValues values = new ContentValues();
775 values.put(Downloads.COLUMN_STATUS, info.mStatus);
776 getContentResolver().update(
777 ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId),
779 if (Constants.LOGX) {
780 if (Helpers.isNetworkAvailable(DownloadService.this)) {
781 Log.i(Constants.TAG, "Thread for " + info.mId + ", Net Up");
783 Log.i(Constants.TAG, "Thread for " + info.mId + ", Net Down");
786 DownloadThread downloader = new DownloadThread(this, info);
787 info.mHasActiveThread = true;
794 * Returns a String that holds the current value of the column,
795 * optimizing for the case where the value hasn't changed.
797 private String stringFromCursor(String old, Cursor cursor, String column) {
798 int index = cursor.getColumnIndexOrThrow(column);
800 return cursor.getString(index);
802 if (mNewChars == null) {
803 mNewChars = new CharArrayBuffer(128);
805 cursor.copyStringToBuffer(index, mNewChars);
806 int length = mNewChars.sizeCopied;
807 if (length != old.length()) {
808 return cursor.getString(index);
810 if (oldChars == null || oldChars.sizeCopied < length) {
811 oldChars = new CharArrayBuffer(length);
813 char[] oldArray = oldChars.data;
814 char[] newArray = mNewChars.data;
815 old.getChars(0, length, oldArray, 0);
816 for (int i = length - 1; i >= 0; --i) {
817 if (oldArray[i] != newArray[i]) {
818 return new String(newArray, 0, length);
825 * Removes the local copy of the info about a download.
827 private void deleteDownload(int arrayPos) {
828 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
829 if (info.mStatus == Downloads.STATUS_RUNNING) {
830 info.mStatus = Downloads.STATUS_CANCELED;
831 } else if (info.mDestination != Downloads.DESTINATION_EXTERNAL && info.mFileName != null) {
832 new File(info.mFileName).delete();
834 mNotifier.mNotificationMgr.cancel(info.mId);
836 mDownloads.remove(arrayPos);
840 * Returns the amount of time (as measured from the "now" parameter)
841 * at which a download will be active.
842 * 0 = immediately - service should stick around to handle this download.
843 * -1 = never - service can go away without ever waking up.
844 * positive value - service must wake up in the future, as specified in ms from "now"
846 private long nextAction(int arrayPos, long now) {
847 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
848 if (Downloads.isStatusCompleted(info.mStatus)) {
851 if (info.mStatus != Downloads.STATUS_RUNNING_PAUSED) {
854 if (info.mNumFailed == 0) {
857 long when = info.restartTime();
865 * Returns whether there's a visible notification for this download
867 private boolean visibleNotification(int arrayPos) {
868 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
869 return info.hasCompletionNotification();
873 * Returns whether a file should be scanned
875 private boolean shouldScanFile(int arrayPos) {
876 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
877 return !info.mMediaScanned
878 && info.mDestination == Downloads.DESTINATION_EXTERNAL
879 && Downloads.isStatusSuccess(info.mStatus)
880 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType);
884 * Returns whether we have a live connection to the Media Scanner
886 private boolean mediaScannerConnected() {
887 return mMediaScannerService != null;
891 * Attempts to scan the file if necessary.
892 * Returns true if the file has been properly scanned.
894 private boolean scanFile(Cursor cursor, int arrayPos) {
895 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
896 synchronized (this) {
897 if (mMediaScannerService != null) {
899 if (Constants.LOGV) {
900 Log.v(Constants.TAG, "Scanning file " + info.mFileName);
902 mMediaScannerService.scanFile(info.mFileName, info.mMimeType);
903 if (cursor != null) {
904 ContentValues values = new ContentValues();
905 values.put(Constants.MEDIA_SCANNED, 1);
906 getContentResolver().update(
907 ContentUris.withAppendedId(Downloads.CONTENT_URI,
908 cursor.getLong(cursor.getColumnIndexOrThrow(Downloads._ID))),
912 } catch (RemoteException e) {
914 Log.d(Constants.TAG, "Failed to scan file " + info.mFileName);