eclair snapshot
[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.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;
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 mUpdateThread;
82
83     /**
84      * Whether the internal download list should be updated from the content
85      * provider.
86      */
87     private boolean mPendingUpdate;
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 mNewChars;
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             mPendingUpdate = true;
251             if (mUpdateThread == null) {
252                 mUpdateThread = new UpdateThread();
253                 mUpdateThread.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 (mUpdateThread != this) {
273                         throw new IllegalStateException(
274                                 "multiple UpdateThreads in DownloadService");
275                     }
276                     if (!mPendingUpdate) {
277                         mUpdateThread = 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                         mNewChars = null;
302                         return;
303                     }
304                     mPendingUpdate = 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                     // TODO: this doesn't look right, it'd leave the loop in an inconsistent state
315                     return;
316                 }
317
318                 cursor.moveToFirst();
319
320                 int arrayPos = 0;
321
322                 boolean mustScan = false;
323                 keepService = false;
324                 wakeUp = Long.MAX_VALUE;
325
326                 boolean isAfterLast = cursor.isAfterLast();
327
328                 int idColumn = cursor.getColumnIndexOrThrow(Downloads._ID);
329
330                 /*
331                  * Walk the cursor and the local array to keep them in sync. The key
332                  *     to the algorithm is that the ids are unique and sorted both in
333                  *     the cursor and in the array, so that they can be processed in
334                  *     order in both sources at the same time: at each step, both
335                  *     sources point to the lowest id that hasn't been processed from
336                  *     that source, and the algorithm processes the lowest id from
337                  *     those two possibilities.
338                  * At each step:
339                  * -If the array contains an entry that's not in the cursor, remove the
340                  *     entry, move to next entry in the array.
341                  * -If the array contains an entry that's in the cursor, nothing to do,
342                  *     move to next cursor row and next array entry.
343                  * -If the cursor contains an entry that's not in the array, insert
344                  *     a new entry in the array, move to next cursor row and next
345                  *     array entry.
346                  */
347                 while (!isAfterLast || arrayPos < mDownloads.size()) {
348                     if (isAfterLast) {
349                         // We're beyond the end of the cursor but there's still some
350                         //     stuff in the local array, which can only be junk
351                         if (Constants.LOGVV) {
352                             int arrayId = ((DownloadInfo) mDownloads.get(arrayPos)).mId;
353                             Log.v(Constants.TAG, "Array update: trimming " +
354                                     arrayId + " @ "  + arrayPos);
355                         }
356                         if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
357                             scanFile(null, arrayPos);
358                         }
359                         deleteDownload(arrayPos); // this advances in the array
360                     } else {
361                         int id = cursor.getInt(idColumn);
362
363                         if (arrayPos == mDownloads.size()) {
364                             insertDownload(cursor, arrayPos, networkAvailable, networkRoaming, now);
365                             if (Constants.LOGVV) {
366                                 Log.v(Constants.TAG, "Array update: appending " +
367                                         id + " @ " + arrayPos);
368                             }
369                             if (shouldScanFile(arrayPos)
370                                     && (!mediaScannerConnected() || !scanFile(cursor, arrayPos))) {
371                                 mustScan = true;
372                                 keepService = true;
373                             }
374                             if (visibleNotification(arrayPos)) {
375                                 keepService = true;
376                             }
377                             long next = nextAction(arrayPos, now);
378                             if (next == 0) {
379                                 keepService = true;
380                             } else if (next > 0 && next < wakeUp) {
381                                 wakeUp = next;
382                             }
383                             ++arrayPos;
384                             cursor.moveToNext();
385                             isAfterLast = cursor.isAfterLast();
386                         } else {
387                             int arrayId = mDownloads.get(arrayPos).mId;
388
389                             if (arrayId < id) {
390                                 // The array entry isn't in the cursor
391                                 if (Constants.LOGVV) {
392                                     Log.v(Constants.TAG, "Array update: removing " + arrayId
393                                             + " @ " + arrayPos);
394                                 }
395                                 if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
396                                     scanFile(null, arrayPos);
397                                 }
398                                 deleteDownload(arrayPos); // this advances in the array
399                             } else if (arrayId == id) {
400                                 // This cursor row already exists in the stored array
401                                 updateDownload(
402                                         cursor, arrayPos,
403                                         networkAvailable, networkRoaming, now);
404                                 if (shouldScanFile(arrayPos)
405                                         && (!mediaScannerConnected()
406                                                 || !scanFile(cursor, arrayPos))) {
407                                     mustScan = true;
408                                     keepService = true;
409                                 }
410                                 if (visibleNotification(arrayPos)) {
411                                     keepService = true;
412                                 }
413                                 long next = nextAction(arrayPos, now);
414                                 if (next == 0) {
415                                     keepService = true;
416                                 } else if (next > 0 && next < wakeUp) {
417                                     wakeUp = next;
418                                 }
419                                 ++arrayPos;
420                                 cursor.moveToNext();
421                                 isAfterLast = cursor.isAfterLast();
422                             } else {
423                                 // This cursor entry didn't exist in the stored array
424                                 if (Constants.LOGVV) {
425                                     Log.v(Constants.TAG, "Array update: inserting " +
426                                             id + " @ " + arrayPos);
427                                 }
428                                 insertDownload(
429                                         cursor, arrayPos,
430                                         networkAvailable, networkRoaming, now);
431                                 if (shouldScanFile(arrayPos)
432                                         && (!mediaScannerConnected()
433                                                 || !scanFile(cursor, arrayPos))) {
434                                     mustScan = true;
435                                     keepService = true;
436                                 }
437                                 if (visibleNotification(arrayPos)) {
438                                     keepService = true;
439                                 }
440                                 long next = nextAction(arrayPos, now);
441                                 if (next == 0) {
442                                     keepService = true;
443                                 } else if (next > 0 && next < wakeUp) {
444                                     wakeUp = next;
445                                 }
446                                 ++arrayPos;
447                                 cursor.moveToNext();
448                                 isAfterLast = cursor.isAfterLast();
449                             }
450                         }
451                     }
452                 }
453
454                 mNotifier.updateNotification();
455
456                 if (mustScan) {
457                     if (!mMediaScannerConnecting) {
458                         Intent intent = new Intent();
459                         intent.setClassName("com.android.providers.media",
460                                 "com.android.providers.media.MediaScannerService");
461                         mMediaScannerConnecting = true;
462                         bindService(intent, mMediaScannerConnection, BIND_AUTO_CREATE);
463                     }
464                 } else {
465                     mMediaScannerConnection.disconnectMediaScanner();
466                 }
467
468                 cursor.close();
469             }
470         }
471     }
472
473     /**
474      * Removes files that may have been left behind in the cache directory
475      */
476     private void removeSpuriousFiles() {
477         File[] files = Environment.getDownloadCacheDirectory().listFiles();
478         if (files == null) {
479             // The cache folder doesn't appear to exist (this is likely the case
480             // when running the simulator).
481             return;
482         }
483         HashSet<String> fileSet = new HashSet();
484         for (int i = 0; i < files.length; i++) {
485             if (files[i].getName().equals(Constants.KNOWN_SPURIOUS_FILENAME)) {
486                 continue;
487             }
488             if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) {
489                 continue;
490             }
491             fileSet.add(files[i].getPath());
492         }
493
494         Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
495                 new String[] { Downloads._DATA }, null, null, null);
496         if (cursor != null) {
497             if (cursor.moveToFirst()) {
498                 do {
499                     fileSet.remove(cursor.getString(0));
500                 } while (cursor.moveToNext());
501             }
502             cursor.close();
503         }
504         Iterator<String> iterator = fileSet.iterator();
505         while (iterator.hasNext()) {
506             String filename = iterator.next();
507             if (Constants.LOGV) {
508                 Log.v(Constants.TAG, "deleting spurious file " + filename);
509             }
510             new File(filename).delete();
511         }
512     }
513
514     /**
515      * Drops old rows from the database to prevent it from growing too large
516      */
517     private void trimDatabase() {
518         Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
519                 new String[] { Downloads._ID },
520                 Downloads.COLUMN_STATUS + " >= '200'", null,
521                 Downloads.COLUMN_LAST_MODIFICATION);
522         if (cursor == null) {
523             // This isn't good - if we can't do basic queries in our database, nothing's gonna work
524             Log.e(Constants.TAG, "null cursor in trimDatabase");
525             return;
526         }
527         if (cursor.moveToFirst()) {
528             int numDelete = cursor.getCount() - Constants.MAX_DOWNLOADS;
529             int columnId = cursor.getColumnIndexOrThrow(Downloads._ID);
530             while (numDelete > 0) {
531                 getContentResolver().delete(
532                         ContentUris.withAppendedId(Downloads.CONTENT_URI, cursor.getLong(columnId)),
533                         null, null);
534                 if (!cursor.moveToNext()) {
535                     break;
536                 }
537                 numDelete--;
538             }
539         }
540         cursor.close();
541     }
542
543     /**
544      * Keeps a local copy of the info about a download, and initiates the
545      * download if appropriate.
546      */
547     private void insertDownload(
548             Cursor cursor, int arrayPos,
549             boolean networkAvailable, boolean networkRoaming, long now) {
550         int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
551         int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
552         int retryRedirect =
553                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
554         DownloadInfo info = new DownloadInfo(
555                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID)),
556                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_URI)),
557                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1,
558                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_FILE_NAME_HINT)),
559                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads._DATA)),
560                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE)),
561                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_DESTINATION)),
562                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY)),
563                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL)),
564                 cursor.getInt(statusColumn),
565                 cursor.getInt(failedColumn),
566                 retryRedirect & 0xfffffff,
567                 retryRedirect >> 28,
568                 cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION)),
569                 cursor.getString(cursor.getColumnIndexOrThrow(
570                         Downloads.COLUMN_NOTIFICATION_PACKAGE)),
571                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NOTIFICATION_CLASS)),
572                 cursor.getString(cursor.getColumnIndexOrThrow(
573                         Downloads.COLUMN_NOTIFICATION_EXTRAS)),
574                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_COOKIE_DATA)),
575                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_USER_AGENT)),
576                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_REFERER)),
577                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES)),
578                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES)),
579                 cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)),
580                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1);
581
582         if (Constants.LOGVV) {
583             Log.v(Constants.TAG, "Service adding new entry");
584             Log.v(Constants.TAG, "ID      : " + info.mId);
585             Log.v(Constants.TAG, "URI     : " + ((info.mUri != null) ? "yes" : "no"));
586             Log.v(Constants.TAG, "NO_INTEG: " + info.mNoIntegrity);
587             Log.v(Constants.TAG, "HINT    : " + info.mHint);
588             Log.v(Constants.TAG, "FILENAME: " + info.mFileName);
589             Log.v(Constants.TAG, "MIMETYPE: " + info.mMimeType);
590             Log.v(Constants.TAG, "DESTINAT: " + info.mDestination);
591             Log.v(Constants.TAG, "VISIBILI: " + info.mVisibility);
592             Log.v(Constants.TAG, "CONTROL : " + info.mControl);
593             Log.v(Constants.TAG, "STATUS  : " + info.mStatus);
594             Log.v(Constants.TAG, "FAILED_C: " + info.mNumFailed);
595             Log.v(Constants.TAG, "RETRY_AF: " + info.mRetryAfter);
596             Log.v(Constants.TAG, "REDIRECT: " + info.mRedirectCount);
597             Log.v(Constants.TAG, "LAST_MOD: " + info.mLastMod);
598             Log.v(Constants.TAG, "PACKAGE : " + info.mPackage);
599             Log.v(Constants.TAG, "CLASS   : " + info.mClass);
600             Log.v(Constants.TAG, "COOKIES : " + ((info.mCookies != null) ? "yes" : "no"));
601             Log.v(Constants.TAG, "AGENT   : " + info.mUserAgent);
602             Log.v(Constants.TAG, "REFERER : " + ((info.mReferer != null) ? "yes" : "no"));
603             Log.v(Constants.TAG, "TOTAL   : " + info.mTotalBytes);
604             Log.v(Constants.TAG, "CURRENT : " + info.mCurrentBytes);
605             Log.v(Constants.TAG, "ETAG    : " + info.mETag);
606             Log.v(Constants.TAG, "SCANNED : " + info.mMediaScanned);
607         }
608
609         mDownloads.add(arrayPos, info);
610
611         if (info.mStatus == 0
612                 && (info.mDestination == Downloads.DESTINATION_EXTERNAL
613                     || info.mDestination == Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE)
614                 && info.mMimeType != null
615                 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType)) {
616             // Check to see if we are allowed to download this file. Only files
617             // that can be handled by the platform can be downloaded.
618             // special case DRM files, which we should always allow downloading.
619             Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
620             
621             // We can provide data as either content: or file: URIs,
622             // so allow both.  (I think it would be nice if we just did
623             // everything as content: URIs)
624             // Actually, right now the download manager's UId restrictions
625             // prevent use from using content: so it's got to be file: or
626             // nothing
627             
628             mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mMimeType);
629             ResolveInfo ri = getPackageManager().resolveActivity(mimetypeIntent,
630                     PackageManager.MATCH_DEFAULT_ONLY);
631             //Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list);
632             
633             if (ri == null) {
634                 if (Config.LOGD) {
635                     Log.d(Constants.TAG, "no application to handle MIME type " + info.mMimeType);
636                 }
637                 info.mStatus = Downloads.STATUS_NOT_ACCEPTABLE;
638
639                 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId);
640                 ContentValues values = new ContentValues();
641                 values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_NOT_ACCEPTABLE);
642                 getContentResolver().update(uri, values, null, null);
643                 info.sendIntentIfRequested(uri, this);
644                 return;
645             }
646         }
647
648         if (info.canUseNetwork(networkAvailable, networkRoaming)) {
649             if (info.isReadyToStart(now)) {
650                 if (Constants.LOGV) {
651                     Log.v(Constants.TAG, "Service spawning thread to handle new download " +
652                             info.mId);
653                 }
654                 if (info.mHasActiveThread) {
655                     throw new IllegalStateException("Multiple threads on same download on insert");
656                 }
657                 if (info.mStatus != Downloads.STATUS_RUNNING) {
658                     info.mStatus = Downloads.STATUS_RUNNING;
659                     ContentValues values = new ContentValues();
660                     values.put(Downloads.COLUMN_STATUS, info.mStatus);
661                     getContentResolver().update(
662                             ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId),
663                             values, null, null);
664                 }
665                 DownloadThread downloader = new DownloadThread(this, info);
666                 info.mHasActiveThread = true;
667                 downloader.start();
668             }
669         } else {
670             if (info.mStatus == 0
671                     || info.mStatus == Downloads.STATUS_PENDING
672                     || info.mStatus == Downloads.STATUS_RUNNING) {
673                 info.mStatus = Downloads.STATUS_RUNNING_PAUSED;
674                 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId);
675                 ContentValues values = new ContentValues();
676                 values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_RUNNING_PAUSED);
677                 getContentResolver().update(uri, values, null, null);
678             }
679         }
680     }
681
682     /**
683      * Updates the local copy of the info about a download.
684      */
685     private void updateDownload(
686             Cursor cursor, int arrayPos,
687             boolean networkAvailable, boolean networkRoaming, long now) {
688         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
689         int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
690         int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
691         info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID));
692         info.mUri = stringFromCursor(info.mUri, cursor, Downloads.COLUMN_URI);
693         info.mNoIntegrity =
694                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1;
695         info.mHint = stringFromCursor(info.mHint, cursor, Downloads.COLUMN_FILE_NAME_HINT);
696         info.mFileName = stringFromCursor(info.mFileName, cursor, Downloads._DATA);
697         info.mMimeType = stringFromCursor(info.mMimeType, cursor, Downloads.COLUMN_MIME_TYPE);
698         info.mDestination = cursor.getInt(cursor.getColumnIndexOrThrow(
699                 Downloads.COLUMN_DESTINATION));
700         int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(
701                 Downloads.COLUMN_VISIBILITY));
702         if (info.mVisibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
703                 && newVisibility != Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
704                 && Downloads.isStatusCompleted(info.mStatus)) {
705             mNotifier.mNotificationMgr.cancel(info.mId);
706         }
707         info.mVisibility = newVisibility;
708         synchronized (info) {
709             info.mControl = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL));
710         }
711         int newStatus = cursor.getInt(statusColumn);
712         if (!Downloads.isStatusCompleted(info.mStatus) && Downloads.isStatusCompleted(newStatus)) {
713             mNotifier.mNotificationMgr.cancel(info.mId);
714         }
715         info.mStatus = newStatus;
716         info.mNumFailed = cursor.getInt(failedColumn);
717         int retryRedirect =
718                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
719         info.mRetryAfter = retryRedirect & 0xfffffff;
720         info.mRedirectCount = retryRedirect >> 28;
721         info.mLastMod = cursor.getLong(cursor.getColumnIndexOrThrow(
722                 Downloads.COLUMN_LAST_MODIFICATION));
723         info.mPackage = stringFromCursor(
724                 info.mPackage, cursor, Downloads.COLUMN_NOTIFICATION_PACKAGE);
725         info.mClass = stringFromCursor(info.mClass, cursor, Downloads.COLUMN_NOTIFICATION_CLASS);
726         info.mCookies = stringFromCursor(info.mCookies, cursor, Downloads.COLUMN_COOKIE_DATA);
727         info.mUserAgent = stringFromCursor(info.mUserAgent, cursor, Downloads.COLUMN_USER_AGENT);
728         info.mReferer = stringFromCursor(info.mReferer, cursor, Downloads.COLUMN_REFERER);
729         info.mTotalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(
730                 Downloads.COLUMN_TOTAL_BYTES));
731         info.mCurrentBytes = cursor.getInt(cursor.getColumnIndexOrThrow(
732                 Downloads.COLUMN_CURRENT_BYTES));
733         info.mETag = stringFromCursor(info.mETag, cursor, Constants.ETAG);
734         info.mMediaScanned =
735                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1;
736
737         if (info.canUseNetwork(networkAvailable, networkRoaming)) {
738             if (info.isReadyToRestart(now)) {
739                 if (Constants.LOGV) {
740                     Log.v(Constants.TAG, "Service spawning thread to handle updated download " +
741                             info.mId);
742                 }
743                 if (info.mHasActiveThread) {
744                     throw new IllegalStateException("Multiple threads on same download on update");
745                 }
746                 info.mStatus = Downloads.STATUS_RUNNING;
747                 ContentValues values = new ContentValues();
748                 values.put(Downloads.COLUMN_STATUS, info.mStatus);
749                 getContentResolver().update(
750                         ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId),
751                         values, null, null);
752                 DownloadThread downloader = new DownloadThread(this, info);
753                 info.mHasActiveThread = true;
754                 downloader.start();
755             }
756         }
757     }
758
759     /**
760      * Returns a String that holds the current value of the column,
761      * optimizing for the case where the value hasn't changed.
762      */
763     private String stringFromCursor(String old, Cursor cursor, String column) {
764         int index = cursor.getColumnIndexOrThrow(column);
765         if (old == null) {
766             return cursor.getString(index);
767         }
768         if (mNewChars == null) {
769             mNewChars = new CharArrayBuffer(128);
770         }
771         cursor.copyStringToBuffer(index, mNewChars);
772         int length = mNewChars.sizeCopied;
773         if (length != old.length()) {
774             return cursor.getString(index);
775         }
776         if (oldChars == null || oldChars.sizeCopied < length) {
777             oldChars = new CharArrayBuffer(length);
778         }
779         char[] oldArray = oldChars.data;
780         char[] newArray = mNewChars.data;
781         old.getChars(0, length, oldArray, 0);
782         for (int i = length - 1; i >= 0; --i) {
783             if (oldArray[i] != newArray[i]) {
784                 return new String(newArray, 0, length);
785             }
786         }
787         return old;
788     }
789
790     /**
791      * Removes the local copy of the info about a download.
792      */
793     private void deleteDownload(int arrayPos) {
794         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
795         if (info.mStatus == Downloads.STATUS_RUNNING) {
796             info.mStatus = Downloads.STATUS_CANCELED;
797         } else if (info.mDestination != Downloads.DESTINATION_EXTERNAL && info.mFileName != null) {
798             new File(info.mFileName).delete();
799         }
800         mNotifier.mNotificationMgr.cancel(info.mId);
801
802         mDownloads.remove(arrayPos);
803     }
804
805     /**
806      * Returns the amount of time (as measured from the "now" parameter)
807      * at which a download will be active.
808      * 0 = immediately - service should stick around to handle this download.
809      * -1 = never - service can go away without ever waking up.
810      * positive value - service must wake up in the future, as specified in ms from "now"
811      */
812     private long nextAction(int arrayPos, long now) {
813         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
814         if (Downloads.isStatusCompleted(info.mStatus)) {
815             return -1;
816         }
817         if (info.mStatus != Downloads.STATUS_RUNNING_PAUSED) {
818             return 0;
819         }
820         if (info.mNumFailed == 0) {
821             return 0;
822         }
823         long when = info.restartTime();
824         if (when <= now) {
825             return 0;
826         }
827         return when - now;
828     }
829
830     /**
831      * Returns whether there's a visible notification for this download
832      */
833     private boolean visibleNotification(int arrayPos) {
834         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
835         return info.hasCompletionNotification();
836     }
837
838     /**
839      * Returns whether a file should be scanned
840      */
841     private boolean shouldScanFile(int arrayPos) {
842         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
843         return !info.mMediaScanned
844                 && info.mDestination == Downloads.DESTINATION_EXTERNAL
845                 && Downloads.isStatusSuccess(info.mStatus)
846                 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType);
847     }
848
849     /**
850      * Returns whether we have a live connection to the Media Scanner
851      */
852     private boolean mediaScannerConnected() {
853         return mMediaScannerService != null;
854     }
855
856     /**
857      * Attempts to scan the file if necessary.
858      * Returns true if the file has been properly scanned.
859      */
860     private boolean scanFile(Cursor cursor, int arrayPos) {
861         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
862         synchronized (this) {
863             if (mMediaScannerService != null) {
864                 try {
865                     if (Constants.LOGV) {
866                         Log.v(Constants.TAG, "Scanning file " + info.mFileName);
867                     }
868                     mMediaScannerService.scanFile(info.mFileName, info.mMimeType);
869                     if (cursor != null) {
870                         ContentValues values = new ContentValues();
871                         values.put(Constants.MEDIA_SCANNED, 1);
872                         getContentResolver().update(
873                                 ContentUris.withAppendedId(Downloads.CONTENT_URI,
874                                        cursor.getLong(cursor.getColumnIndexOrThrow(Downloads._ID))),
875                                 values, null, null);
876                     }
877                     return true;
878                 } catch (RemoteException e) {
879                     if (Config.LOGD) {
880                         Log.d(Constants.TAG, "Failed to scan file " + info.mFileName);
881                     }
882                 }
883             }
884         }
885         return false;
886     }
887
888 }