DownloadService.java revision 764844ebbb788cdde731abbacc89b9afcab8b49f
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
17package com.android.providers.downloads;
18
19import com.google.android.collect.Lists;
20
21import android.app.AlarmManager;
22import android.app.PendingIntent;
23import android.app.Service;
24import android.content.ComponentName;
25import android.content.ContentUris;
26import android.content.ContentValues;
27import android.content.Context;
28import android.content.Intent;
29import android.content.ServiceConnection;
30import android.content.pm.PackageManager;
31import android.content.pm.ResolveInfo;
32import android.database.CharArrayBuffer;
33import android.database.ContentObserver;
34import android.database.Cursor;
35import android.drm.mobile1.DrmRawContent;
36import android.media.IMediaScannerService;
37import android.net.Uri;
38import android.os.Environment;
39import android.os.Handler;
40import android.os.IBinder;
41import android.os.Process;
42import android.os.RemoteException;
43import android.provider.Downloads;
44import android.util.Config;
45import android.util.Log;
46
47import java.io.File;
48import java.util.ArrayList;
49import java.util.HashSet;
50import java.util.Iterator;
51import java.util.List;
52
53
54/**
55 * Performs the background downloads requested by applications that use the Downloads provider.
56 */
57public 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}
923