DownloadServicepublic class DownloadService extends android.app.Service Performs the background downloads requested by applications that use the Downloads provider. |
Fields Summary |
---|
private DownloadManagerContentObserver | mObserverObserver to get notified when the content observer's data changes | private DownloadNotification | mNotifierClass to handle Notification Manager updates | private ArrayList | mDownloadsThe Service's view of the list of downloads. This is kept independently
from the content provider, and the Service only initiates downloads
based on this data, so that it can deal with situation where the data
in the content provider changes or disappears. | private UpdateThread | updateThreadThe thread that updates the internal download list from the content
provider. | private boolean | pendingUpdateWhether the internal download list should be updated from the content
provider. | private MediaScannerConnection | mMediaScannerConnectionThe ServiceConnection object that tells us when we're connected to and disconnected from
the Media Scanner | private boolean | mMediaScannerConnecting | private android.media.IMediaScannerService | mMediaScannerServiceThe IPC interface to the Media Scanner | private android.database.CharArrayBuffer | oldCharsArray used when extracting strings from content provider | private android.database.CharArrayBuffer | newCharsArray used when extracting strings from content provider |
Methods Summary |
---|
private void | deleteDownload(int arrayPos)Removes the local copy of the info about a download.
DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
if (info.status == Downloads.STATUS_RUNNING) {
info.status = Downloads.STATUS_CANCELED;
} else if (info.destination != Downloads.DESTINATION_EXTERNAL && info.filename != null) {
new File(info.filename).delete();
}
mNotifier.mNotificationMgr.cancel(info.id);
mDownloads.remove(arrayPos);
| private void | insertDownload(android.database.Cursor cursor, int arrayPos, boolean networkAvailable, boolean networkRoaming, long now)Keeps a local copy of the info about a download, and initiates the
download if appropriate.
int statusColumn = cursor.getColumnIndexOrThrow(Downloads.STATUS);
int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
int retryRedirect =
cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER___REDIRECT_COUNT));
DownloadInfo info = new DownloadInfo(
cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID)),
cursor.getString(cursor.getColumnIndexOrThrow(Downloads.URI)),
cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.NO_INTEGRITY)) == 1,
cursor.getString(cursor.getColumnIndexOrThrow(Downloads.FILENAME_HINT)),
cursor.getString(cursor.getColumnIndexOrThrow(Downloads._DATA)),
cursor.getString(cursor.getColumnIndexOrThrow(Downloads.MIMETYPE)),
cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.DESTINATION)),
cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.VISIBILITY)),
cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.CONTROL)),
cursor.getInt(statusColumn),
cursor.getInt(failedColumn),
retryRedirect & 0xfffffff,
retryRedirect >> 28,
cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.LAST_MODIFICATION)),
cursor.getString(cursor.getColumnIndexOrThrow(Downloads.NOTIFICATION_PACKAGE)),
cursor.getString(cursor.getColumnIndexOrThrow(Downloads.NOTIFICATION_CLASS)),
cursor.getString(cursor.getColumnIndexOrThrow(Downloads.NOTIFICATION_EXTRAS)),
cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COOKIE_DATA)),
cursor.getString(cursor.getColumnIndexOrThrow(Downloads.USER_AGENT)),
cursor.getString(cursor.getColumnIndexOrThrow(Downloads.REFERER)),
cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.TOTAL_BYTES)),
cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.CURRENT_BYTES)),
cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)),
cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1);
if (Constants.LOGVV) {
Log.v(Constants.TAG, "Service adding new entry");
Log.v(Constants.TAG, "ID : " + info.id);
Log.v(Constants.TAG, "URI : " + ((info.uri != null) ? "yes" : "no"));
Log.v(Constants.TAG, "NO_INTEG: " + info.noIntegrity);
Log.v(Constants.TAG, "HINT : " + info.hint);
Log.v(Constants.TAG, "FILENAME: " + info.filename);
Log.v(Constants.TAG, "MIMETYPE: " + info.mimetype);
Log.v(Constants.TAG, "DESTINAT: " + info.destination);
Log.v(Constants.TAG, "VISIBILI: " + info.visibility);
Log.v(Constants.TAG, "CONTROL : " + info.control);
Log.v(Constants.TAG, "STATUS : " + info.status);
Log.v(Constants.TAG, "FAILED_C: " + info.numFailed);
Log.v(Constants.TAG, "RETRY_AF: " + info.retryAfter);
Log.v(Constants.TAG, "REDIRECT: " + info.redirectCount);
Log.v(Constants.TAG, "LAST_MOD: " + info.lastMod);
Log.v(Constants.TAG, "PACKAGE : " + info.pckg);
Log.v(Constants.TAG, "CLASS : " + info.clazz);
Log.v(Constants.TAG, "COOKIES : " + ((info.cookies != null) ? "yes" : "no"));
Log.v(Constants.TAG, "AGENT : " + info.userAgent);
Log.v(Constants.TAG, "REFERER : " + ((info.referer != null) ? "yes" : "no"));
Log.v(Constants.TAG, "TOTAL : " + info.totalBytes);
Log.v(Constants.TAG, "CURRENT : " + info.currentBytes);
Log.v(Constants.TAG, "ETAG : " + info.etag);
Log.v(Constants.TAG, "SCANNED : " + info.mediaScanned);
}
mDownloads.add(arrayPos, info);
if (info.status == 0
&& (info.destination == Downloads.DESTINATION_EXTERNAL
|| info.destination == Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE)
&& info.mimetype != null
&& !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mimetype)) {
// Check to see if we are allowed to download this file. Only files
// that can be handled by the platform can be downloaded.
// special case DRM files, which we should always allow downloading.
Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
// We can provide data as either content: or file: URIs,
// so allow both. (I think it would be nice if we just did
// everything as content: URIs)
// Actually, right now the download manager's UId restrictions
// prevent use from using content: so it's got to be file: or
// nothing
mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mimetype);
ResolveInfo ri = getPackageManager().resolveActivity(mimetypeIntent,
PackageManager.MATCH_DEFAULT_ONLY);
//Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list);
if (ri == null) {
if (Config.LOGD) {
Log.d(Constants.TAG, "no application to handle MIME type " + info.mimetype);
}
info.status = Downloads.STATUS_NOT_ACCEPTABLE;
Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id);
ContentValues values = new ContentValues();
values.put(Downloads.STATUS, Downloads.STATUS_NOT_ACCEPTABLE);
getContentResolver().update(uri, values, null, null);
info.sendIntentIfRequested(uri, this);
return;
}
}
if (info.canUseNetwork(networkAvailable, networkRoaming)) {
if (info.isReadyToStart(now)) {
if (Constants.LOGV) {
Log.v(Constants.TAG, "Service spawning thread to handle new download " +
info.id);
}
if (info.hasActiveThread) {
throw new IllegalStateException("Multiple threads on same download on insert");
}
if (info.status != Downloads.STATUS_RUNNING) {
info.status = Downloads.STATUS_RUNNING;
ContentValues values = new ContentValues();
values.put(Downloads.STATUS, info.status);
getContentResolver().update(
ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id),
values, null, null);
}
DownloadThread downloader = new DownloadThread(this, info);
info.hasActiveThread = true;
downloader.start();
}
} else {
if (info.status == 0
|| info.status == Downloads.STATUS_PENDING
|| info.status == Downloads.STATUS_RUNNING) {
info.status = Downloads.STATUS_RUNNING_PAUSED;
Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id);
ContentValues values = new ContentValues();
values.put(Downloads.STATUS, Downloads.STATUS_RUNNING_PAUSED);
getContentResolver().update(uri, values, null, null);
}
}
| private boolean | mediaScannerConnected()Returns whether we have a live connection to the Media Scanner
return mMediaScannerService != null;
| private long | nextAction(int arrayPos, long now)Returns the amount of time (as measured from the "now" parameter)
at which a download will be active.
0 = immediately - service should stick around to handle this download.
-1 = never - service can go away without ever waking up.
positive value - service must wake up in the future, as specified in ms from "now"
DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
if (Downloads.isStatusCompleted(info.status)) {
return -1;
}
if (info.status != Downloads.STATUS_RUNNING_PAUSED) {
return 0;
}
if (info.numFailed == 0) {
return 0;
}
long when = info.restartTime();
if (when <= now) {
return 0;
}
return when - now;
| public android.os.IBinder | onBind(android.content.Intent i)Returns an IBinder instance when someone wants to connect to this
service. Binding to this service is not allowed.
throw new UnsupportedOperationException("Cannot bind to Download Manager Service");
| public void | onCreate()Initializes the service when it is first created
super.onCreate();
if (Constants.LOGVV) {
Log.v(Constants.TAG, "Service onCreate");
}
mDownloads = Lists.newArrayList();
mObserver = new DownloadManagerContentObserver();
getContentResolver().registerContentObserver(Downloads.CONTENT_URI,
true, mObserver);
mMediaScannerService = null;
mMediaScannerConnecting = false;
mMediaScannerConnection = new MediaScannerConnection();
mNotifier = new DownloadNotification(this);
mNotifier.mNotificationMgr.cancelAll();
mNotifier.updateNotification();
trimDatabase();
removeSpuriousFiles();
updateFromProvider();
| public void | onDestroy()Cleans up when the service is destroyed
getContentResolver().unregisterContentObserver(mObserver);
if (Constants.LOGVV) {
Log.v(Constants.TAG, "Service onDestroy");
}
super.onDestroy();
| public void | onStart(android.content.Intent intent, int startId)Responds to a call to startService
super.onStart(intent, startId);
if (Constants.LOGVV) {
Log.v(Constants.TAG, "Service onStart");
}
updateFromProvider();
| private void | removeSpuriousFiles()Removes files that may have been left behind in the cache directory
File[] files = Environment.getDownloadCacheDirectory().listFiles();
if (files == null) {
// The cache folder doesn't appear to exist (this is likely the case
// when running the simulator).
return;
}
HashSet<String> fileSet = new HashSet();
for (int i = 0; i < files.length; i++) {
if (files[i].getName().equals(Constants.KNOWN_SPURIOUS_FILENAME)) {
continue;
}
if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) {
continue;
}
fileSet.add(files[i].getPath());
}
Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
new String[] { Downloads._DATA }, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
fileSet.remove(cursor.getString(0));
} while (cursor.moveToNext());
}
cursor.close();
}
Iterator<String> iterator = fileSet.iterator();
while (iterator.hasNext()) {
String filename = iterator.next();
if (Constants.LOGV) {
Log.v(Constants.TAG, "deleting spurious file " + filename);
}
new File(filename).delete();
}
| private boolean | scanFile(android.database.Cursor cursor, int arrayPos)Attempts to scan the file if necessary.
Returns true if the file has been properly scanned.
DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
synchronized (this) {
if (mMediaScannerService != null) {
try {
if (Constants.LOGV) {
Log.v(Constants.TAG, "Scanning file " + info.filename);
}
mMediaScannerService.scanFile(info.filename, info.mimetype);
if (cursor != null) {
ContentValues values = new ContentValues();
values.put(Constants.MEDIA_SCANNED, 1);
getContentResolver().update(
ContentUris.withAppendedId(Downloads.CONTENT_URI,
cursor.getLong(cursor.getColumnIndexOrThrow(Downloads._ID))),
values, null, null);
}
return true;
} catch (RemoteException e) {
if (Config.LOGD) {
Log.d(Constants.TAG, "Failed to scan file " + info.filename);
}
}
}
}
return false;
| private boolean | shouldScanFile(int arrayPos)Returns whether a file should be scanned
DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
return !info.mediaScanned
&& info.destination == Downloads.DESTINATION_EXTERNAL
&& Downloads.isStatusSuccess(info.status)
&& !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mimetype);
| private java.lang.String | stringFromCursor(java.lang.String old, android.database.Cursor cursor, java.lang.String column)Returns a String that holds the current value of the column,
optimizing for the case where the value hasn't changed.
int index = cursor.getColumnIndexOrThrow(column);
if (old == null) {
return cursor.getString(index);
}
if (newChars == null) {
newChars = new CharArrayBuffer(128);
}
cursor.copyStringToBuffer(index, newChars);
int length = newChars.sizeCopied;
if (length != old.length()) {
return cursor.getString(index);
}
if (oldChars == null || oldChars.sizeCopied < length) {
oldChars = new CharArrayBuffer(length);
}
char[] oldArray = oldChars.data;
char[] newArray = newChars.data;
old.getChars(0, length, oldArray, 0);
for (int i = length - 1; i >= 0; --i) {
if (oldArray[i] != newArray[i]) {
return new String(newArray, 0, length);
}
}
return old;
| private void | trimDatabase()Drops old rows from the database to prevent it from growing too large
Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
new String[] { Downloads._ID },
Downloads.STATUS + " >= '200'", null,
Downloads.LAST_MODIFICATION);
if (cursor == null) {
// This isn't good - if we can't do basic queries in our database, nothing's gonna work
Log.e(Constants.TAG, "null cursor in trimDatabase");
return;
}
if (cursor.moveToFirst()) {
int numDelete = cursor.getCount() - Constants.MAX_DOWNLOADS;
int columnId = cursor.getColumnIndexOrThrow(Downloads._ID);
while (numDelete > 0) {
getContentResolver().delete(
ContentUris.withAppendedId(Downloads.CONTENT_URI, cursor.getLong(columnId)),
null, null);
if (!cursor.moveToNext()) {
break;
}
numDelete--;
}
}
cursor.close();
| private void | updateDownload(android.database.Cursor cursor, int arrayPos, boolean networkAvailable, boolean networkRoaming, long now)Updates the local copy of the info about a download.
DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
int statusColumn = cursor.getColumnIndexOrThrow(Downloads.STATUS);
int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
info.id = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID));
info.uri = stringFromCursor(info.uri, cursor, Downloads.URI);
info.noIntegrity =
cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.NO_INTEGRITY)) == 1;
info.hint = stringFromCursor(info.hint, cursor, Downloads.FILENAME_HINT);
info.filename = stringFromCursor(info.filename, cursor, Downloads._DATA);
info.mimetype = stringFromCursor(info.mimetype, cursor, Downloads.MIMETYPE);
info.destination = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.DESTINATION));
int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.VISIBILITY));
if (info.visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
&& newVisibility != Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
&& Downloads.isStatusCompleted(info.status)) {
mNotifier.mNotificationMgr.cancel(info.id);
}
info.visibility = newVisibility;
synchronized(info) {
info.control = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.CONTROL));
}
int newStatus = cursor.getInt(statusColumn);
if (!Downloads.isStatusCompleted(info.status) && Downloads.isStatusCompleted(newStatus)) {
mNotifier.mNotificationMgr.cancel(info.id);
}
info.status = newStatus;
info.numFailed = cursor.getInt(failedColumn);
int retryRedirect =
cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER___REDIRECT_COUNT));
info.retryAfter = retryRedirect & 0xfffffff;
info.redirectCount = retryRedirect >> 28;
info.lastMod = cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.LAST_MODIFICATION));
info.pckg = stringFromCursor(info.pckg, cursor, Downloads.NOTIFICATION_PACKAGE);
info.clazz = stringFromCursor(info.clazz, cursor, Downloads.NOTIFICATION_CLASS);
info.cookies = stringFromCursor(info.cookies, cursor, Downloads.COOKIE_DATA);
info.userAgent = stringFromCursor(info.userAgent, cursor, Downloads.USER_AGENT);
info.referer = stringFromCursor(info.referer, cursor, Downloads.REFERER);
info.totalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.TOTAL_BYTES));
info.currentBytes = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.CURRENT_BYTES));
info.etag = stringFromCursor(info.etag, cursor, Constants.ETAG);
info.mediaScanned =
cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1;
if (info.canUseNetwork(networkAvailable, networkRoaming)) {
if (info.isReadyToRestart(now)) {
if (Constants.LOGV) {
Log.v(Constants.TAG, "Service spawning thread to handle updated download " +
info.id);
}
if (info.hasActiveThread) {
throw new IllegalStateException("Multiple threads on same download on update");
}
info.status = Downloads.STATUS_RUNNING;
ContentValues values = new ContentValues();
values.put(Downloads.STATUS, info.status);
getContentResolver().update(
ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id),
values, null, null);
DownloadThread downloader = new DownloadThread(this, info);
info.hasActiveThread = true;
downloader.start();
}
}
| private void | updateFromProvider()Parses data from the content provider into private array
synchronized (this) {
pendingUpdate = true;
if (updateThread == null) {
updateThread = new UpdateThread();
updateThread.start();
}
}
| private boolean | visibleNotification(int arrayPos)Returns whether there's a visible notification for this download
DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
return info.hasCompletionNotification();
|
|