0600cfb67966730595acd2309b9bf9a931dac0ba
[android/platform/packages/providers/DownloadProvider.git] / src / com / android / providers / downloads / DownloadService.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 com.google.android.collect.Lists;
20
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.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.content.ServiceConnection;
32 import android.database.ContentObserver;
33 import android.database.Cursor;
34 import android.database.CharArrayBuffer;
35 import android.drm.mobile1.DrmRawContent;
36 import android.media.IMediaScannerService;
37 import android.net.Uri;
38 import android.os.RemoteException;
39 import android.os.Environment;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.Process;
43 import android.provider.Downloads;
44 import android.util.Config;
45 import android.util.Log;
46
47 import java.io.File;
48 import java.util.ArrayList;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.List;
52
53
54 /**
55  * Performs the background downloads requested by applications that use the Downloads provider.
56  */
57 public class DownloadService extends Service {
58
59     /* ------------ Constants ------------ */
60
61     /* ------------ Members ------------ */
62
63     /** Observer to get notified when the content observer's data changes */
64     private DownloadManagerContentObserver mObserver;
65     
66     /** Class to handle Notification Manager updates */
67     private DownloadNotification mNotifier;
68
69     /**
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.
74      */
75     private ArrayList<DownloadInfo> mDownloads;
76
77     /**
78      * The thread that updates the internal download list from the content
79      * provider.
80      */
81     private UpdateThread updateThread;
82
83     /**
84      * Whether the internal download list should be updated from the content
85      * provider.
86      */
87     private boolean pendingUpdate;
88
89     /**
90      * The ServiceConnection object that tells us when we're connected to and disconnected from
91      * the Media Scanner
92      */
93     private MediaScannerConnection mMediaScannerConnection;
94
95     private boolean mMediaScannerConnecting;
96
97     /**
98      * The IPC interface to the Media Scanner
99      */
100     private IMediaScannerService mMediaScannerService;
101
102     /**
103      * Array used when extracting strings from content provider
104      */
105     private CharArrayBuffer oldChars;
106
107     /**
108      * Array used when extracting strings from content provider
109      */
110     private CharArrayBuffer newChars;
111
112     /* ------------ Inner Classes ------------ */
113
114     /**
115      * Receives notifications when the data in the content provider changes
116      */
117     private class DownloadManagerContentObserver extends ContentObserver {
118
119         public DownloadManagerContentObserver() {
120             super(new Handler());
121         }
122
123         /**
124          * Receives notification when the data in the observed content
125          * provider changes.
126          */
127         public void onChange(final boolean selfChange) {
128             if (Constants.LOGVV) {
129                 Log.v(Constants.TAG, "Service ContentObserver received notification");
130             }
131             updateFromProvider();
132         }
133
134     }
135
136     /**
137      * Gets called back when the connection to the media
138      * scanner is established or lost.
139      */
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");
144             }
145             mMediaScannerConnecting = false;
146             synchronized (DownloadService.this) {
147                 mMediaScannerService = IMediaScannerService.Stub.asInterface(service);
148                 if (mMediaScannerService != null) {
149                     updateFromProvider();
150                 }
151             }
152         }
153
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");
160                     }
161                     try {
162                         unbindService(this);
163                     } catch (IllegalArgumentException ex) {
164                         if (Constants.LOGV) {
165                             Log.v(Constants.TAG, "unbindService threw up: " + ex);
166                         }
167                     }
168                 }
169             }
170         }
171
172         public void onServiceDisconnected(ComponentName className) {
173             if (Constants.LOGVV) {
174                 Log.v(Constants.TAG, "Disconnected from Media Scanner");
175             }
176             synchronized (DownloadService.this) {
177                 mMediaScannerService = null;
178             }
179         }
180     }
181
182     /* ------------ Methods ------------ */
183
184     /**
185      * Returns an IBinder instance when someone wants to connect to this
186      * service. Binding to this service is not allowed.
187      *
188      * @throws UnsupportedOperationException
189      */
190     public IBinder onBind(Intent i) {
191         throw new UnsupportedOperationException("Cannot bind to Download Manager Service");
192     }
193
194     /**
195      * Initializes the service when it is first created
196      */
197     public void onCreate() {
198         super.onCreate();
199         if (Constants.LOGVV) {
200             Log.v(Constants.TAG, "Service onCreate");
201         }
202
203         mDownloads = Lists.newArrayList();
204
205         mObserver = new DownloadManagerContentObserver();
206         getContentResolver().registerContentObserver(Downloads.CONTENT_URI,
207                 true, mObserver);
208
209         mMediaScannerService = null;
210         mMediaScannerConnecting = false;
211         mMediaScannerConnection = new MediaScannerConnection();
212         
213         mNotifier = new DownloadNotification(this);
214         mNotifier.mNotificationMgr.cancelAll();
215         mNotifier.updateNotification();
216
217         trimDatabase();
218         removeSpuriousFiles();
219         updateFromProvider();
220     }
221
222     /**
223      * Responds to a call to startService
224      */
225     public void onStart(Intent intent, int startId) {
226         super.onStart(intent, startId);
227         if (Constants.LOGVV) {
228             Log.v(Constants.TAG, "Service onStart");
229         }
230
231         updateFromProvider();
232     }
233
234     /**
235      * Cleans up when the service is destroyed
236      */
237     public void onDestroy() {
238         getContentResolver().unregisterContentObserver(mObserver);
239         if (Constants.LOGVV) {
240             Log.v(Constants.TAG, "Service onDestroy");
241         }
242         super.onDestroy();
243     }
244
245     /**
246      * Parses data from the content provider into private array
247      */
248     private void updateFromProvider() {
249         synchronized (this) {
250             pendingUpdate = true;
251             if (updateThread == null) {
252                 updateThread = new UpdateThread();
253                 updateThread.start();
254             }
255         }
256     }
257
258     private class UpdateThread extends Thread {
259         public UpdateThread() {
260             super("Download Service");
261         }
262         
263         public void run() {
264             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
265             
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;
270             for (;;) {
271                 synchronized (DownloadService.this) {
272                     if (updateThread != this) {
273                         throw new IllegalStateException(
274                                 "multiple UpdateThreads in DownloadService");
275                     }
276                     if (!pendingUpdate) {
277                         updateThread = null;
278                         if (!keepService) {
279                             stopSelf();
280                         }
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");
286                             } else {
287                                 if (Constants.LOGV) {
288                                     Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms");
289                                 }
290                                 Intent intent = new Intent(Constants.ACTION_RETRY);
291                                 intent.setClassName("com.android.providers.downloads",
292                                         DownloadReceiver.class.getName());
293                                 alarms.set(
294                                         AlarmManager.RTC_WAKEUP,
295                                         System.currentTimeMillis() + wakeUp,
296                                         PendingIntent.getBroadcast(DownloadService.this, 0, intent,
297                                                 PendingIntent.FLAG_ONE_SHOT));
298                             }
299                         }
300                         oldChars = null;
301                         newChars = null;
302                         return;
303                     }
304                     pendingUpdate = false;
305                 }
306                 boolean networkAvailable = Helpers.isNetworkAvailable(DownloadService.this);
307                 boolean networkRoaming = Helpers.isNetworkRoaming(DownloadService.this);
308                 long now = System.currentTimeMillis();
309
310                 Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
311                         null, null, null, Downloads._ID);
312
313                 if (cursor == null) {
314                     return;
315                 }
316
317                 cursor.moveToFirst();
318
319                 int arrayPos = 0;
320
321                 boolean mustScan = false;
322                 keepService = false;
323                 wakeUp = Long.MAX_VALUE;
324
325                 boolean isAfterLast = cursor.isAfterLast();
326
327                 int idColumn = cursor.getColumnIndexOrThrow(Downloads._ID);
328
329                 /*
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.
337                  * At each step:
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
344                  *     array entry.
345                  */
346                 while (!isAfterLast || arrayPos < mDownloads.size()) {
347                     if (isAfterLast) {
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)).id;
352                             Log.v(Constants.TAG, "Array update: trimming " +
353                                     arrayId + " @ "  + arrayPos);
354                         }
355                         if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
356                             scanFile(null, arrayPos);
357                         }
358                         deleteDownload(arrayPos); // this advances in the array
359                     } else {
360                         int id = cursor.getInt(idColumn);
361
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);
367                             }
368                             if (shouldScanFile(arrayPos)
369                                     && (!mediaScannerConnected() || !scanFile(cursor, arrayPos))) {
370                                 mustScan = true;
371                                 keepService = true;
372                             }
373                             if (visibleNotification(arrayPos)) {
374                                 keepService = true;
375                             }
376                             long next = nextAction(arrayPos, now);
377                             if (next == 0) {
378                                 keepService = true;
379                             } else if (next > 0 && next < wakeUp) {
380                                 wakeUp = next;
381                             }
382                             ++arrayPos;
383                             cursor.moveToNext();
384                             isAfterLast = cursor.isAfterLast();
385                         } else {
386                             int arrayId = mDownloads.get(arrayPos).id;
387
388                             if (arrayId < id) {
389                                 // The array entry isn't in the cursor
390                                 if (Constants.LOGVV) {
391                                     Log.v(Constants.TAG, "Array update: removing " + arrayId
392                                             + " @ " + arrayPos);
393                                 }
394                                 if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
395                                     scanFile(null, arrayPos);
396                                 }
397                                 deleteDownload(arrayPos); // this advances in the array
398                             } else if (arrayId == id) {
399                                 // This cursor row already exists in the stored array
400                                 updateDownload(
401                                         cursor, arrayPos,
402                                         networkAvailable, networkRoaming, now);
403                                 if (shouldScanFile(arrayPos)
404                                         && (!mediaScannerConnected()
405                                                 || !scanFile(cursor, arrayPos))) {
406                                     mustScan = true;
407                                     keepService = true;
408                                 }
409                                 if (visibleNotification(arrayPos)) {
410                                     keepService = true;
411                                 }
412                                 long next = nextAction(arrayPos, now);
413                                 if (next == 0) {
414                                     keepService = true;
415                                 } else if (next > 0 && next < wakeUp) {
416                                     wakeUp = next;
417                                 }
418                                 ++arrayPos;
419                                 cursor.moveToNext();
420                                 isAfterLast = cursor.isAfterLast();
421                             } else {
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);
426                                 }
427                                 insertDownload(
428                                         cursor, arrayPos,
429                                         networkAvailable, networkRoaming, now);
430                                 if (shouldScanFile(arrayPos)
431                                         && (!mediaScannerConnected()
432                                                 || !scanFile(cursor, arrayPos))) {
433                                     mustScan = true;
434                                     keepService = true;
435                                 }
436                                 if (visibleNotification(arrayPos)) {
437                                     keepService = true;
438                                 }
439                                 long next = nextAction(arrayPos, now);
440                                 if (next == 0) {
441                                     keepService = true;
442                                 } else if (next > 0 && next < wakeUp) {
443                                     wakeUp = next;
444                                 }
445                                 ++arrayPos;
446                                 cursor.moveToNext();
447                                 isAfterLast = cursor.isAfterLast();
448                             }
449                         }
450                     }
451                 }
452
453                 mNotifier.updateNotification();
454
455                 if (mustScan) {
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);
462                     }
463                 } else {
464                     mMediaScannerConnection.disconnectMediaScanner();
465                 }
466
467                 cursor.close();
468             }
469         }
470     }
471
472     /**
473      * Removes files that may have been left behind in the cache directory
474      */
475     private void removeSpuriousFiles() {
476         File[] files = Environment.getDownloadCacheDirectory().listFiles();
477         if (files == null) {
478             // The cache folder doesn't appear to exist (this is likely the case
479             // when running the simulator).
480             return;
481         }
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)) {
485                 continue;
486             }
487             if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) {
488                 continue;
489             }
490             fileSet.add(files[i].getPath());
491         }
492
493         Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
494                 new String[] { Downloads._DATA }, null, null, null);
495         if (cursor != null) {
496             if (cursor.moveToFirst()) {
497                 do {
498                     fileSet.remove(cursor.getString(0));
499                 } while (cursor.moveToNext());
500             }
501             cursor.close();
502         }
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);
508             }
509             new File(filename).delete();
510         }
511     }
512
513     /**
514      * Drops old rows from the database to prevent it from growing too large
515      */
516     private void trimDatabase() {
517         Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
518                 new String[] { Downloads._ID },
519                 Downloads.STATUS + " >= '200'", null,
520                 Downloads.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");
524             return;
525         }
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)),
532                         null, null);
533                 if (!cursor.moveToNext()) {
534                     break;
535                 }
536                 numDelete--;
537             }
538         }
539         cursor.close();
540     }
541
542     /**
543      * Keeps a local copy of the info about a download, and initiates the
544      * download if appropriate.
545      */
546     private void insertDownload(
547             Cursor cursor, int arrayPos,
548             boolean networkAvailable, boolean networkRoaming, long now) {
549         int statusColumn = cursor.getColumnIndexOrThrow(Downloads.STATUS);
550         int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
551         int retryRedirect =
552                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER___REDIRECT_COUNT));
553         DownloadInfo info = new DownloadInfo(
554                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID)),
555                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.URI)),
556                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.NO_INTEGRITY)) == 1,
557                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.FILENAME_HINT)),
558                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads._DATA)),
559                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.MIMETYPE)),
560                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.DESTINATION)),
561                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.VISIBILITY)),
562                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.CONTROL)),
563                 cursor.getInt(statusColumn),
564                 cursor.getInt(failedColumn),
565                 retryRedirect & 0xfffffff,
566                 retryRedirect >> 28,
567                 cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.LAST_MODIFICATION)),
568                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.NOTIFICATION_PACKAGE)),
569                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.NOTIFICATION_CLASS)),
570                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.NOTIFICATION_EXTRAS)),
571                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COOKIE_DATA)),
572                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.USER_AGENT)),
573                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.REFERER)),
574                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.TOTAL_BYTES)),
575                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.CURRENT_BYTES)),
576                 cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)),
577                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1);
578
579         if (Constants.LOGVV) {
580             Log.v(Constants.TAG, "Service adding new entry");
581             Log.v(Constants.TAG, "ID      : " + info.id);
582             Log.v(Constants.TAG, "URI     : " + ((info.uri != null) ? "yes" : "no"));
583             Log.v(Constants.TAG, "NO_INTEG: " + info.noIntegrity);
584             Log.v(Constants.TAG, "HINT    : " + info.hint);
585             Log.v(Constants.TAG, "FILENAME: " + info.filename);
586             Log.v(Constants.TAG, "MIMETYPE: " + info.mimetype);
587             Log.v(Constants.TAG, "DESTINAT: " + info.destination);
588             Log.v(Constants.TAG, "VISIBILI: " + info.visibility);
589             Log.v(Constants.TAG, "CONTROL : " + info.control);
590             Log.v(Constants.TAG, "STATUS  : " + info.status);
591             Log.v(Constants.TAG, "FAILED_C: " + info.numFailed);
592             Log.v(Constants.TAG, "RETRY_AF: " + info.retryAfter);
593             Log.v(Constants.TAG, "REDIRECT: " + info.redirectCount);
594             Log.v(Constants.TAG, "LAST_MOD: " + info.lastMod);
595             Log.v(Constants.TAG, "PACKAGE : " + info.pckg);
596             Log.v(Constants.TAG, "CLASS   : " + info.clazz);
597             Log.v(Constants.TAG, "COOKIES : " + ((info.cookies != null) ? "yes" : "no"));
598             Log.v(Constants.TAG, "AGENT   : " + info.userAgent);
599             Log.v(Constants.TAG, "REFERER : " + ((info.referer != null) ? "yes" : "no"));
600             Log.v(Constants.TAG, "TOTAL   : " + info.totalBytes);
601             Log.v(Constants.TAG, "CURRENT : " + info.currentBytes);
602             Log.v(Constants.TAG, "ETAG    : " + info.etag);
603             Log.v(Constants.TAG, "SCANNED : " + info.mediaScanned);
604         }
605
606         mDownloads.add(arrayPos, info);
607
608         if (info.status == 0
609                 && (info.destination == Downloads.DESTINATION_EXTERNAL
610                     || info.destination == Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE)
611                 && info.mimetype != null
612                 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mimetype)) {
613             // Check to see if we are allowed to download this file. Only files
614             // that can be handled by the platform can be downloaded.
615             // special case DRM files, which we should always allow downloading.
616             Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
617             
618             // We can provide data as either content: or file: URIs,
619             // so allow both.  (I think it would be nice if we just did
620             // everything as content: URIs)
621             // Actually, right now the download manager's UId restrictions
622             // prevent use from using content: so it's got to be file: or
623             // nothing
624             
625             mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mimetype);
626             ResolveInfo ri = getPackageManager().resolveActivity(mimetypeIntent,
627                     PackageManager.MATCH_DEFAULT_ONLY);
628             //Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list);
629             
630             if (ri == null) {
631                 if (Config.LOGD) {
632                     Log.d(Constants.TAG, "no application to handle MIME type " + info.mimetype);
633                 }
634                 info.status = Downloads.STATUS_NOT_ACCEPTABLE;
635
636                 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id);
637                 ContentValues values = new ContentValues();
638                 values.put(Downloads.STATUS, Downloads.STATUS_NOT_ACCEPTABLE);
639                 getContentResolver().update(uri, values, null, null);
640                 info.sendIntentIfRequested(uri, this);
641                 return;
642             }
643         }
644
645         if (info.canUseNetwork(networkAvailable, networkRoaming)) {
646             if (info.isReadyToStart(now)) {
647                 if (Constants.LOGV) {
648                     Log.v(Constants.TAG, "Service spawning thread to handle new download " +
649                             info.id);
650                 }
651                 if (info.hasActiveThread) {
652                     throw new IllegalStateException("Multiple threads on same download on insert");
653                 }
654                 if (info.status != Downloads.STATUS_RUNNING) {
655                     info.status = Downloads.STATUS_RUNNING;
656                     ContentValues values = new ContentValues();
657                     values.put(Downloads.STATUS, info.status);
658                     getContentResolver().update(
659                             ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id),
660                             values, null, null);
661                 }
662                 DownloadThread downloader = new DownloadThread(this, info);
663                 info.hasActiveThread = true;
664                 downloader.start();
665             }
666         } else {
667             if (info.status == 0
668                     || info.status == Downloads.STATUS_PENDING
669                     || info.status == Downloads.STATUS_RUNNING) {
670                 info.status = Downloads.STATUS_RUNNING_PAUSED;
671                 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id);
672                 ContentValues values = new ContentValues();
673                 values.put(Downloads.STATUS, Downloads.STATUS_RUNNING_PAUSED);
674                 getContentResolver().update(uri, values, null, null);
675             }
676         }
677     }
678
679     /**
680      * Updates the local copy of the info about a download.
681      */
682     private void updateDownload(
683             Cursor cursor, int arrayPos,
684             boolean networkAvailable, boolean networkRoaming, long now) {
685         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
686         int statusColumn = cursor.getColumnIndexOrThrow(Downloads.STATUS);
687         int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
688         info.id = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID));
689         info.uri = stringFromCursor(info.uri, cursor, Downloads.URI);
690         info.noIntegrity =
691                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.NO_INTEGRITY)) == 1;
692         info.hint = stringFromCursor(info.hint, cursor, Downloads.FILENAME_HINT);
693         info.filename = stringFromCursor(info.filename, cursor, Downloads._DATA);
694         info.mimetype = stringFromCursor(info.mimetype, cursor, Downloads.MIMETYPE);
695         info.destination = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.DESTINATION));
696         int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.VISIBILITY));
697         if (info.visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
698                 && newVisibility != Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
699                 && Downloads.isStatusCompleted(info.status)) {
700             mNotifier.mNotificationMgr.cancel(info.id);
701         }
702         info.visibility = newVisibility;
703         synchronized(info) {
704             info.control = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.CONTROL));
705         }
706         int newStatus = cursor.getInt(statusColumn);
707         if (!Downloads.isStatusCompleted(info.status) && Downloads.isStatusCompleted(newStatus)) {
708             mNotifier.mNotificationMgr.cancel(info.id);
709         }
710         info.status = newStatus;
711         info.numFailed = cursor.getInt(failedColumn);
712         int retryRedirect =
713                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER___REDIRECT_COUNT));
714         info.retryAfter = retryRedirect & 0xfffffff;
715         info.redirectCount = retryRedirect >> 28;
716         info.lastMod = cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.LAST_MODIFICATION));
717         info.pckg = stringFromCursor(info.pckg, cursor, Downloads.NOTIFICATION_PACKAGE);
718         info.clazz = stringFromCursor(info.clazz, cursor, Downloads.NOTIFICATION_CLASS);
719         info.cookies = stringFromCursor(info.cookies, cursor, Downloads.COOKIE_DATA);
720         info.userAgent = stringFromCursor(info.userAgent, cursor, Downloads.USER_AGENT);
721         info.referer = stringFromCursor(info.referer, cursor, Downloads.REFERER);
722         info.totalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.TOTAL_BYTES));
723         info.currentBytes = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.CURRENT_BYTES));
724         info.etag = stringFromCursor(info.etag, cursor, Constants.ETAG);
725         info.mediaScanned =
726                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1;
727
728         if (info.canUseNetwork(networkAvailable, networkRoaming)) {
729             if (info.isReadyToRestart(now)) {
730                 if (Constants.LOGV) {
731                     Log.v(Constants.TAG, "Service spawning thread to handle updated download " +
732                             info.id);
733                 }
734                 if (info.hasActiveThread) {
735                     throw new IllegalStateException("Multiple threads on same download on update");
736                 }
737                 info.status = Downloads.STATUS_RUNNING;
738                 ContentValues values = new ContentValues();
739                 values.put(Downloads.STATUS, info.status);
740                 getContentResolver().update(
741                         ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id),
742                         values, null, null);
743                 DownloadThread downloader = new DownloadThread(this, info);
744                 info.hasActiveThread = true;
745                 downloader.start();
746             }
747         }
748     }
749
750     /**
751      * Returns a String that holds the current value of the column,
752      * optimizing for the case where the value hasn't changed.
753      */
754     private String stringFromCursor(String old, Cursor cursor, String column) {
755         int index = cursor.getColumnIndexOrThrow(column);
756         if (old == null) {
757             return cursor.getString(index);
758         }
759         if (newChars == null) {
760             newChars = new CharArrayBuffer(128);
761         }
762         cursor.copyStringToBuffer(index, newChars);
763         int length = newChars.sizeCopied;
764         if (length != old.length()) {
765             return cursor.getString(index);
766         }
767         if (oldChars == null || oldChars.sizeCopied < length) {
768             oldChars = new CharArrayBuffer(length);
769         }
770         char[] oldArray = oldChars.data;
771         char[] newArray = newChars.data;
772         old.getChars(0, length, oldArray, 0);
773         for (int i = length - 1; i >= 0; --i) {
774             if (oldArray[i] != newArray[i]) {
775                 return new String(newArray, 0, length);
776             }
777         }
778         return old;
779     }
780
781     /**
782      * Removes the local copy of the info about a download.
783      */
784     private void deleteDownload(int arrayPos) {
785         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
786         if (info.status == Downloads.STATUS_RUNNING) {
787             info.status = Downloads.STATUS_CANCELED;
788         } else if (info.destination != Downloads.DESTINATION_EXTERNAL && info.filename != null) {
789             new File(info.filename).delete();
790         }
791         mNotifier.mNotificationMgr.cancel(info.id);
792
793         mDownloads.remove(arrayPos);
794     }
795
796     /**
797      * Returns the amount of time (as measured from the "now" parameter)
798      * at which a download will be active.
799      * 0 = immediately - service should stick around to handle this download.
800      * -1 = never - service can go away without ever waking up.
801      * positive value - service must wake up in the future, as specified in ms from "now"
802      */
803     private long nextAction(int arrayPos, long now) {
804         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
805         if (Downloads.isStatusCompleted(info.status)) {
806             return -1;
807         }
808         if (info.status != Downloads.STATUS_RUNNING_PAUSED) {
809             return 0;
810         }
811         if (info.numFailed == 0) {
812             return 0;
813         }
814         long when = info.restartTime();
815         if (when <= now) {
816             return 0;
817         }
818         return when - now;
819     }
820
821     /**
822      * Returns whether there's a visible notification for this download
823      */
824     private boolean visibleNotification(int arrayPos) {
825         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
826         return info.hasCompletionNotification();
827     }
828
829     /**
830      * Returns whether a file should be scanned
831      */
832     private boolean shouldScanFile(int arrayPos) {
833         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
834         return !info.mediaScanned
835                 && info.destination == Downloads.DESTINATION_EXTERNAL
836                 && Downloads.isStatusSuccess(info.status)
837                 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mimetype);
838     }
839
840     /**
841      * Returns whether we have a live connection to the Media Scanner
842      */
843     private boolean mediaScannerConnected() {
844         return mMediaScannerService != null;
845     }
846
847     /**
848      * Attempts to scan the file if necessary.
849      * Returns true if the file has been properly scanned.
850      */
851     private boolean scanFile(Cursor cursor, int arrayPos) {
852         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
853         synchronized (this) {
854             if (mMediaScannerService != null) {
855                 try {
856                     if (Constants.LOGV) {
857                         Log.v(Constants.TAG, "Scanning file " + info.filename);
858                     }
859                     mMediaScannerService.scanFile(info.filename, info.mimetype);
860                     if (cursor != null) {
861                         ContentValues values = new ContentValues();
862                         values.put(Constants.MEDIA_SCANNED, 1);
863                         getContentResolver().update(
864                                 ContentUris.withAppendedId(Downloads.CONTENT_URI,
865                                        cursor.getLong(cursor.getColumnIndexOrThrow(Downloads._ID))),
866                                 values, null, null);
867                     }
868                     return true;
869                 } catch (RemoteException e) {
870                     if (Config.LOGD) {
871                         Log.d(Constants.TAG, "Failed to scan file " + info.filename);
872                     }
873                 }
874             }
875         }
876         return false;
877     }
878
879 }