Use the new download manager APIs introduced in change 7400
[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                     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)).mId;
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).mId;
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.COLUMN_STATUS + " >= '200'", null,
520                 Downloads.COLUMN_LAST_MODIFICATION);
521         if (cursor == null) {
522             // This isn't good - if we can't do basic queries in our database, nothing's gonna work
523             Log.e(Constants.TAG, "null cursor in trimDatabase");
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.COLUMN_STATUS);
550         int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
551         int retryRedirect =
552                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
553         DownloadInfo info = new DownloadInfo(
554                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID)),
555                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_URI)),
556                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1,
557                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_FILE_NAME_HINT)),
558                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads._DATA)),
559                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE)),
560                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_DESTINATION)),
561                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY)),
562                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL)),
563                 cursor.getInt(statusColumn),
564                 cursor.getInt(failedColumn),
565                 retryRedirect & 0xfffffff,
566                 retryRedirect >> 28,
567                 cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION)),
568                 cursor.getString(cursor.getColumnIndexOrThrow(
569                         Downloads.COLUMN_NOTIFICATION_PACKAGE)),
570                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NOTIFICATION_CLASS)),
571                 cursor.getString(cursor.getColumnIndexOrThrow(
572                         Downloads.COLUMN_NOTIFICATION_EXTRAS)),
573                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_COOKIE_DATA)),
574                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_USER_AGENT)),
575                 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_REFERER)),
576                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES)),
577                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES)),
578                 cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)),
579                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1);
580
581         if (Constants.LOGVV) {
582             Log.v(Constants.TAG, "Service adding new entry");
583             Log.v(Constants.TAG, "ID      : " + info.mId);
584             Log.v(Constants.TAG, "URI     : " + ((info.mUri != null) ? "yes" : "no"));
585             Log.v(Constants.TAG, "NO_INTEG: " + info.mNoIntegrity);
586             Log.v(Constants.TAG, "HINT    : " + info.mHint);
587             Log.v(Constants.TAG, "FILENAME: " + info.mFileName);
588             Log.v(Constants.TAG, "MIMETYPE: " + info.mMimeType);
589             Log.v(Constants.TAG, "DESTINAT: " + info.mDestination);
590             Log.v(Constants.TAG, "VISIBILI: " + info.mVisibility);
591             Log.v(Constants.TAG, "CONTROL : " + info.mControl);
592             Log.v(Constants.TAG, "STATUS  : " + info.mStatus);
593             Log.v(Constants.TAG, "FAILED_C: " + info.mNumFailed);
594             Log.v(Constants.TAG, "RETRY_AF: " + info.mRetryAfter);
595             Log.v(Constants.TAG, "REDIRECT: " + info.mRedirectCount);
596             Log.v(Constants.TAG, "LAST_MOD: " + info.mLastMod);
597             Log.v(Constants.TAG, "PACKAGE : " + info.mPackage);
598             Log.v(Constants.TAG, "CLASS   : " + info.mClass);
599             Log.v(Constants.TAG, "COOKIES : " + ((info.mCookies != null) ? "yes" : "no"));
600             Log.v(Constants.TAG, "AGENT   : " + info.mUserAgent);
601             Log.v(Constants.TAG, "REFERER : " + ((info.mReferer != null) ? "yes" : "no"));
602             Log.v(Constants.TAG, "TOTAL   : " + info.mTotalBytes);
603             Log.v(Constants.TAG, "CURRENT : " + info.mCurrentBytes);
604             Log.v(Constants.TAG, "ETAG    : " + info.mETag);
605             Log.v(Constants.TAG, "SCANNED : " + info.mMediaScanned);
606         }
607
608         mDownloads.add(arrayPos, info);
609
610         if (info.mStatus == 0
611                 && (info.mDestination == Downloads.DESTINATION_EXTERNAL
612                     || info.mDestination == Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE)
613                 && info.mMimeType != null
614                 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType)) {
615             // Check to see if we are allowed to download this file. Only files
616             // that can be handled by the platform can be downloaded.
617             // special case DRM files, which we should always allow downloading.
618             Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
619             
620             // We can provide data as either content: or file: URIs,
621             // so allow both.  (I think it would be nice if we just did
622             // everything as content: URIs)
623             // Actually, right now the download manager's UId restrictions
624             // prevent use from using content: so it's got to be file: or
625             // nothing
626             
627             mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mMimeType);
628             List<ResolveInfo> list = getPackageManager().queryIntentActivities(mimetypeIntent,
629                     PackageManager.MATCH_DEFAULT_ONLY);
630             //Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list);
631             
632             if (list.size() == 0) {
633                 if (Config.LOGD) {
634                     Log.d(Constants.TAG, "no application to handle MIME type " + info.mMimeType);
635                 }
636                 info.mStatus = Downloads.STATUS_NOT_ACCEPTABLE;
637
638                 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId);
639                 ContentValues values = new ContentValues();
640                 values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_NOT_ACCEPTABLE);
641                 getContentResolver().update(uri, values, null, null);
642                 info.sendIntentIfRequested(uri, this);
643                 return;
644             }
645         }
646
647         if (info.canUseNetwork(networkAvailable, networkRoaming)) {
648             if (info.isReadyToStart(now)) {
649                 if (Constants.LOGV) {
650                     Log.v(Constants.TAG, "Service spawning thread to handle new download " +
651                             info.mId);
652                 }
653                 if (info.mHasActiveThread) {
654                     throw new IllegalStateException("Multiple threads on same download on insert");
655                 }
656                 if (info.mStatus != Downloads.STATUS_RUNNING) {
657                     info.mStatus = Downloads.STATUS_RUNNING;
658                     ContentValues values = new ContentValues();
659                     values.put(Downloads.COLUMN_STATUS, info.mStatus);
660                     getContentResolver().update(
661                             ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId),
662                             values, null, null);
663                 }
664                 DownloadThread downloader = new DownloadThread(this, info);
665                 info.mHasActiveThread = true;
666                 downloader.start();
667             }
668         } else {
669             if (info.mStatus == 0
670                     || info.mStatus == Downloads.STATUS_PENDING
671                     || info.mStatus == Downloads.STATUS_RUNNING) {
672                 info.mStatus = Downloads.STATUS_RUNNING_PAUSED;
673                 Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId);
674                 ContentValues values = new ContentValues();
675                 values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_RUNNING_PAUSED);
676                 getContentResolver().update(uri, values, null, null);
677             }
678         }
679     }
680
681     /**
682      * Updates the local copy of the info about a download.
683      */
684     private void updateDownload(
685             Cursor cursor, int arrayPos,
686             boolean networkAvailable, boolean networkRoaming, long now) {
687         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
688         int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
689         int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
690         info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID));
691         info.mUri = stringFromCursor(info.mUri, cursor, Downloads.COLUMN_URI);
692         info.mNoIntegrity =
693                 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1;
694         info.mHint = stringFromCursor(info.mHint, cursor, Downloads.COLUMN_FILE_NAME_HINT);
695         info.mFileName = stringFromCursor(info.mFileName, cursor, Downloads._DATA);
696         info.mMimeType = stringFromCursor(info.mMimeType, cursor, Downloads.COLUMN_MIME_TYPE);
697         info.mDestination = cursor.getInt(cursor.getColumnIndexOrThrow(
698                 Downloads.COLUMN_DESTINATION));
699         int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(
700                 Downloads.COLUMN_VISIBILITY));
701         if (info.mVisibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
702                 && newVisibility != Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
703                 && Downloads.isStatusCompleted(info.mStatus)) {
704             mNotifier.mNotificationMgr.cancel(info.mId);
705         }
706         info.mVisibility = newVisibility;
707         synchronized (info) {
708             info.mControl = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL));
709         }
710         int newStatus = cursor.getInt(statusColumn);
711         if (!Downloads.isStatusCompleted(info.mStatus) && Downloads.isStatusCompleted(newStatus)) {
712             mNotifier.mNotificationMgr.cancel(info.mId);
713         }
714         info.mStatus = newStatus;
715         info.mNumFailed = cursor.getInt(failedColumn);
716         int retryRedirect =
717                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT));
718         info.mRetryAfter = retryRedirect & 0xfffffff;
719         info.mRedirectCount = retryRedirect >> 28;
720         info.mLastMod = cursor.getLong(cursor.getColumnIndexOrThrow(
721                 Downloads.COLUMN_LAST_MODIFICATION));
722         info.mPackage = stringFromCursor(
723                 info.mPackage, cursor, Downloads.COLUMN_NOTIFICATION_PACKAGE);
724         info.mClass = stringFromCursor(info.mClass, cursor, Downloads.COLUMN_NOTIFICATION_CLASS);
725         info.mCookies = stringFromCursor(info.mCookies, cursor, Downloads.COLUMN_COOKIE_DATA);
726         info.mUserAgent = stringFromCursor(info.mUserAgent, cursor, Downloads.COLUMN_USER_AGENT);
727         info.mReferer = stringFromCursor(info.mReferer, cursor, Downloads.COLUMN_REFERER);
728         info.mTotalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(
729                 Downloads.COLUMN_TOTAL_BYTES));
730         info.mCurrentBytes = cursor.getInt(cursor.getColumnIndexOrThrow(
731                 Downloads.COLUMN_CURRENT_BYTES));
732         info.mETag = stringFromCursor(info.mETag, cursor, Constants.ETAG);
733         info.mMediaScanned =
734                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1;
735
736         if (info.canUseNetwork(networkAvailable, networkRoaming)) {
737             if (info.isReadyToRestart(now)) {
738                 if (Constants.LOGV) {
739                     Log.v(Constants.TAG, "Service spawning thread to handle updated download " +
740                             info.mId);
741                 }
742                 if (info.mHasActiveThread) {
743                     throw new IllegalStateException("Multiple threads on same download on update");
744                 }
745                 info.mStatus = Downloads.STATUS_RUNNING;
746                 ContentValues values = new ContentValues();
747                 values.put(Downloads.COLUMN_STATUS, info.mStatus);
748                 getContentResolver().update(
749                         ContentUris.withAppendedId(Downloads.CONTENT_URI, info.mId),
750                         values, null, null);
751                 DownloadThread downloader = new DownloadThread(this, info);
752                 info.mHasActiveThread = true;
753                 downloader.start();
754             }
755         }
756     }
757
758     /**
759      * Returns a String that holds the current value of the column,
760      * optimizing for the case where the value hasn't changed.
761      */
762     private String stringFromCursor(String old, Cursor cursor, String column) {
763         int index = cursor.getColumnIndexOrThrow(column);
764         if (old == null) {
765             return cursor.getString(index);
766         }
767         if (mNewChars == null) {
768             mNewChars = new CharArrayBuffer(128);
769         }
770         cursor.copyStringToBuffer(index, mNewChars);
771         int length = mNewChars.sizeCopied;
772         if (length != old.length()) {
773             return cursor.getString(index);
774         }
775         if (oldChars == null || oldChars.sizeCopied < length) {
776             oldChars = new CharArrayBuffer(length);
777         }
778         char[] oldArray = oldChars.data;
779         char[] newArray = mNewChars.data;
780         old.getChars(0, length, oldArray, 0);
781         for (int i = length - 1; i >= 0; --i) {
782             if (oldArray[i] != newArray[i]) {
783                 return new String(newArray, 0, length);
784             }
785         }
786         return old;
787     }
788
789     /**
790      * Removes the local copy of the info about a download.
791      */
792     private void deleteDownload(int arrayPos) {
793         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
794         if (info.mStatus == Downloads.STATUS_RUNNING) {
795             info.mStatus = Downloads.STATUS_CANCELED;
796         } else if (info.mDestination != Downloads.DESTINATION_EXTERNAL && info.mFileName != null) {
797             new File(info.mFileName).delete();
798         }
799         mNotifier.mNotificationMgr.cancel(info.mId);
800
801         mDownloads.remove(arrayPos);
802     }
803
804     /**
805      * Returns the amount of time (as measured from the "now" parameter)
806      * at which a download will be active.
807      * 0 = immediately - service should stick around to handle this download.
808      * -1 = never - service can go away without ever waking up.
809      * positive value - service must wake up in the future, as specified in ms from "now"
810      */
811     private long nextAction(int arrayPos, long now) {
812         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
813         if (Downloads.isStatusCompleted(info.mStatus)) {
814             return -1;
815         }
816         if (info.mStatus != Downloads.STATUS_RUNNING_PAUSED) {
817             return 0;
818         }
819         if (info.mNumFailed == 0) {
820             return 0;
821         }
822         long when = info.restartTime();
823         if (when <= now) {
824             return 0;
825         }
826         return when - now;
827     }
828
829     /**
830      * Returns whether there's a visible notification for this download
831      */
832     private boolean visibleNotification(int arrayPos) {
833         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
834         return info.hasCompletionNotification();
835     }
836
837     /**
838      * Returns whether a file should be scanned
839      */
840     private boolean shouldScanFile(int arrayPos) {
841         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
842         return !info.mMediaScanned
843                 && info.mDestination == Downloads.DESTINATION_EXTERNAL
844                 && Downloads.isStatusSuccess(info.mStatus)
845                 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType);
846     }
847
848     /**
849      * Returns whether we have a live connection to the Media Scanner
850      */
851     private boolean mediaScannerConnected() {
852         return mMediaScannerService != null;
853     }
854
855     /**
856      * Attempts to scan the file if necessary.
857      * Returns true if the file has been properly scanned.
858      */
859     private boolean scanFile(Cursor cursor, int arrayPos) {
860         DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
861         synchronized (this) {
862             if (mMediaScannerService != null) {
863                 try {
864                     if (Constants.LOGV) {
865                         Log.v(Constants.TAG, "Scanning file " + info.mFileName);
866                     }
867                     mMediaScannerService.scanFile(info.mFileName, info.mMimeType);
868                     if (cursor != null) {
869                         ContentValues values = new ContentValues();
870                         values.put(Constants.MEDIA_SCANNED, 1);
871                         getContentResolver().update(
872                                 ContentUris.withAppendedId(Downloads.CONTENT_URI,
873                                        cursor.getLong(cursor.getColumnIndexOrThrow(Downloads._ID))),
874                                 values, null, null);
875                     }
876                     return true;
877                 } catch (RemoteException e) {
878                     if (Config.LOGD) {
879                         Log.d(Constants.TAG, "Failed to scan file " + info.mFileName);
880                     }
881                 }
882             }
883         }
884         return false;
885     }
886
887 }