OSDN Git Service

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