From 1edb39f280d23b3a87db45b63c2f26850d68eafe Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 18:28:53 -0800 Subject: auto import from //depot/cupcake/@135843 --- src/com/android/providers/downloads/Constants.java | 151 ---- .../providers/downloads/DownloadFileInfo.java | 34 - .../android/providers/downloads/DownloadInfo.java | 212 ----- .../providers/downloads/DownloadNotification.java | 300 ------- .../providers/downloads/DownloadProvider.java | 731 ----------------- .../providers/downloads/DownloadReceiver.java | 159 ---- .../providers/downloads/DownloadService.java | 879 --------------------- .../providers/downloads/DownloadThread.java | 710 ----------------- src/com/android/providers/downloads/Helpers.java | 793 ------------------- 9 files changed, 3969 deletions(-) delete mode 100644 src/com/android/providers/downloads/Constants.java delete mode 100644 src/com/android/providers/downloads/DownloadFileInfo.java delete mode 100644 src/com/android/providers/downloads/DownloadInfo.java delete mode 100644 src/com/android/providers/downloads/DownloadNotification.java delete mode 100644 src/com/android/providers/downloads/DownloadProvider.java delete mode 100644 src/com/android/providers/downloads/DownloadReceiver.java delete mode 100644 src/com/android/providers/downloads/DownloadService.java delete mode 100644 src/com/android/providers/downloads/DownloadThread.java delete mode 100644 src/com/android/providers/downloads/Helpers.java (limited to 'src') diff --git a/src/com/android/providers/downloads/Constants.java b/src/com/android/providers/downloads/Constants.java deleted file mode 100644 index cffda04a..00000000 --- a/src/com/android/providers/downloads/Constants.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.providers.downloads; - -import android.util.Config; -import android.util.Log; - -/** - * Contains the internal constants that are used in the download manager. - * As a general rule, modifying these constants should be done with care. - */ -public class Constants { - - /** Tag used for debugging/logging */ - public static final String TAG = "DownloadManager"; - - /** The column that used to be used for the HTTP method of the request */ - public static final String RETRY_AFTER___REDIRECT_COUNT = "method"; - - /** The column that used to be used for the magic OTA update filename */ - public static final String OTA_UPDATE = "otaupdate"; - - /** The column that used to be used to reject system filetypes */ - public static final String NO_SYSTEM_FILES = "no_system"; - - /** The column that is used for the downloads's ETag */ - public static final String ETAG = "etag"; - - /** The column that is used for the initiating app's UID */ - public static final String UID = "uid"; - - /** The column that is used to remember whether the media scanner was invoked */ - public static final String MEDIA_SCANNED = "scanned"; - - /** The column that is used to count retries */ - public static final String FAILED_CONNECTIONS = "numfailed"; - - /** The intent that gets sent when the service must wake up for a retry */ - public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP"; - - /** the intent that gets sent when clicking a successful download */ - public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN"; - - /** the intent that gets sent when clicking an incomplete/failed download */ - public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST"; - - /** the intent that gets sent when deleting the notification of a completed download */ - public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE"; - - /** The default base name for downloaded files if we can't get one at the HTTP level */ - public static final String DEFAULT_DL_FILENAME = "downloadfile"; - - /** The default extension for html files if we can't get one at the HTTP level */ - public static final String DEFAULT_DL_HTML_EXTENSION = ".html"; - - /** The default extension for text files if we can't get one at the HTTP level */ - public static final String DEFAULT_DL_TEXT_EXTENSION = ".txt"; - - /** The default extension for binary files if we can't get one at the HTTP level */ - public static final String DEFAULT_DL_BINARY_EXTENSION = ".bin"; - - /** - * When a number has to be appended to the filename, this string is used to separate the - * base filename from the sequence number - */ - public static final String FILENAME_SEQUENCE_SEPARATOR = "-"; - - /** Where we store downloaded files on the external storage */ - public static final String DEFAULT_DL_SUBDIR = "/download"; - - /** A magic filename that is allowed to exist within the system cache */ - public static final String KNOWN_SPURIOUS_FILENAME = "lost+found"; - - /** A magic filename that is allowed to exist within the system cache */ - public static final String RECOVERY_DIRECTORY = "recovery"; - - /** The default user agent used for downloads */ - public static final String DEFAULT_USER_AGENT = "AndroidDownloadManager"; - - /** The MIME type of special DRM files */ - public static final String MIMETYPE_DRM_MESSAGE = - android.drm.mobile1.DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING; - - /** The MIME type of APKs */ - public static final String MIMETYPE_APK = "application/vnd.android.package"; - - /** The buffer size used to stream the data */ - public static final int BUFFER_SIZE = 4096; - - /** The minimum amount of progress that has to be done before the progress bar gets updated */ - public static final int MIN_PROGRESS_STEP = 4096; - - /** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */ - public static final long MIN_PROGRESS_TIME = 1500; - - /** The maximum number of rows in the database (FIFO) */ - public static final int MAX_DOWNLOADS = 1000; - - /** - * The number of times that the download manager will retry its network - * operations when no progress is happening before it gives up. - */ - public static final int MAX_RETRIES = 5; - - /** - * The minimum amount of time that the download manager accepts for - * a Retry-After response header with a parameter in delta-seconds. - */ - public static final int MIN_RETRY_AFTER = 30; // 30s - - /** - * The maximum amount of time that the download manager accepts for - * a Retry-After response header with a parameter in delta-seconds. - */ - public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h - - /** - * The maximum number of redirects. - */ - public static final int MAX_REDIRECTS = 5; // can't be more than 7. - - /** - * The time between a failure and the first retry after an IOException. - * Each subsequent retry grows exponentially, doubling each time. - * The time is in seconds. - */ - public static final int RETRY_FIRST_DELAY = 30; - - /** Enable verbose logging - use with "setprop log.tag.DownloadManager VERBOSE" */ - private static final boolean LOCAL_LOGV = true; - public static final boolean LOGV = Config.LOGV - || (Config.LOGD && LOCAL_LOGV && Log.isLoggable(TAG, Log.VERBOSE)); - - /** Enable super-verbose logging */ - private static final boolean LOCAL_LOGVV = false; - public static final boolean LOGVV = LOCAL_LOGVV && LOGV; -} diff --git a/src/com/android/providers/downloads/DownloadFileInfo.java b/src/com/android/providers/downloads/DownloadFileInfo.java deleted file mode 100644 index 29cbd940..00000000 --- a/src/com/android/providers/downloads/DownloadFileInfo.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.providers.downloads; - -import java.io.FileOutputStream; - -/** - * Stores information about the file in which a download gets saved. - */ -public class DownloadFileInfo { - public DownloadFileInfo(String filename, FileOutputStream stream, int status) { - this.filename = filename; - this.stream = stream; - this.status = status; - } - - String filename; - FileOutputStream stream; - int status; -} diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java deleted file mode 100644 index e051f41a..00000000 --- a/src/com/android/providers/downloads/DownloadInfo.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.providers.downloads; - -import android.net.Uri; -import android.content.Context; -import android.content.Intent; -import android.provider.Downloads; - -/** - * Stores information about an individual download. - */ -public class DownloadInfo { - public int id; - public String uri; - public boolean noIntegrity; - public String hint; - public String filename; - public String mimetype; - public int destination; - public int visibility; - public int control; - public int status; - public int numFailed; - public int retryAfter; - public int redirectCount; - public long lastMod; - public String pckg; - public String clazz; - public String extras; - public String cookies; - public String userAgent; - public String referer; - public int totalBytes; - public int currentBytes; - public String etag; - public boolean mediaScanned; - - public volatile boolean hasActiveThread; - - public DownloadInfo(int id, String uri, boolean noIntegrity, - String hint, String filename, - String mimetype, int destination, int visibility, int control, - int status, int numFailed, int retryAfter, int redirectCount, long lastMod, - String pckg, String clazz, String extras, String cookies, - String userAgent, String referer, int totalBytes, int currentBytes, String etag, - boolean mediaScanned) { - this.id = id; - this.uri = uri; - this.noIntegrity = noIntegrity; - this.hint = hint; - this.filename = filename; - this.mimetype = mimetype; - this.destination = destination; - this.visibility = visibility; - this.control = control; - this.status = status; - this.numFailed = numFailed; - this.retryAfter = retryAfter; - this.redirectCount = redirectCount; - this.lastMod = lastMod; - this.pckg = pckg; - this.clazz = clazz; - this.extras = extras; - this.cookies = cookies; - this.userAgent = userAgent; - this.referer = referer; - this.totalBytes = totalBytes; - this.currentBytes = currentBytes; - this.etag = etag; - this.mediaScanned = mediaScanned; - } - - public void sendIntentIfRequested(Uri contentUri, Context context) { - if (pckg != null && clazz != null) { - Intent intent = new Intent(Downloads.DOWNLOAD_COMPLETED_ACTION); - intent.setClassName(pckg, clazz); - if (extras != null) { - intent.putExtra(Downloads.NOTIFICATION_EXTRAS, extras); - } - // We only send the content: URI, for security reasons. Otherwise, malicious - // applications would have an easier time spoofing download results by - // sending spoofed intents. - intent.setData(contentUri); - context.sendBroadcast(intent); - } - } - - /** - * Returns the time when a download should be restarted. Must only - * be called when numFailed > 0. - */ - public long restartTime() { - if (retryAfter > 0) { - return lastMod + retryAfter; - } - return lastMod + - Constants.RETRY_FIRST_DELAY * - (1000 + Helpers.rnd.nextInt(1001)) * (1 << (numFailed - 1)); - } - - /** - * Returns whether this download (which the download manager hasn't seen yet) - * should be started. - */ - public boolean isReadyToStart(long now) { - if (control == Downloads.CONTROL_PAUSED) { - // the download is paused, so it's not going to start - return false; - } - if (status == 0) { - // status hasn't been initialized yet, this is a new download - return true; - } - if (status == Downloads.STATUS_PENDING) { - // download is explicit marked as ready to start - return true; - } - if (status == Downloads.STATUS_RUNNING) { - // download was interrupted (process killed, loss of power) while it was running, - // without a chance to update the database - return true; - } - if (status == Downloads.STATUS_RUNNING_PAUSED) { - if (numFailed == 0) { - // download is waiting for network connectivity to return before it can resume - return true; - } - if (restartTime() < now) { - // download was waiting for a delayed restart, and the delay has expired - return true; - } - } - return false; - } - - /** - * Returns whether this download (which the download manager has already seen - * and therefore potentially started) should be restarted. - * - * In a nutshell, this returns true if the download isn't already running - * but should be, and it can know whether the download is already running - * by checking the status. - */ - public boolean isReadyToRestart(long now) { - if (control == Downloads.CONTROL_PAUSED) { - // the download is paused, so it's not going to restart - return false; - } - if (status == 0) { - // download hadn't been initialized yet - return true; - } - if (status == Downloads.STATUS_PENDING) { - // download is explicit marked as ready to start - return true; - } - if (status == Downloads.STATUS_RUNNING_PAUSED) { - if (numFailed == 0) { - // download is waiting for network connectivity to return before it can resume - return true; - } - if (restartTime() < now) { - // download was waiting for a delayed restart, and the delay has expired - return true; - } - } - return false; - } - - /** - * Returns whether this download has a visible notification after - * completion. - */ - public boolean hasCompletionNotification() { - if (!Downloads.isStatusCompleted(status)) { - return false; - } - if (visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) { - return true; - } - return false; - } - - /** - * Returns whether this download is allowed to use the network. - */ - public boolean canUseNetwork(boolean available, boolean roaming) { - if (!available) { - return false; - } - if (destination == Downloads.DESTINATION_CACHE_PARTITION_NOROAMING) { - return !roaming; - } else { - return true; - } - } -} diff --git a/src/com/android/providers/downloads/DownloadNotification.java b/src/com/android/providers/downloads/DownloadNotification.java deleted file mode 100644 index ed17ab7a..00000000 --- a/src/com/android/providers/downloads/DownloadNotification.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.providers.downloads; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.provider.Downloads; -import android.widget.RemoteViews; - -import java.util.HashMap; - -/** - * This class handles the updating of the Notification Manager for the - * cases where there is an ongoing download. Once the download is complete - * (be it successful or unsuccessful) it is no longer the responsibility - * of this component to show the download in the notification manager. - * - */ -class DownloadNotification { - - Context mContext; - public NotificationManager mNotificationMgr; - HashMap mNotifications; - - static final String LOGTAG = "DownloadNotification"; - static final String WHERE_RUNNING = - "(" + Downloads.STATUS + " >= '100') AND (" + - Downloads.STATUS + " <= '199') AND (" + - Downloads.VISIBILITY + " IS NULL OR " + - Downloads.VISIBILITY + " == '" + Downloads.VISIBILITY_VISIBLE + "' OR " + - Downloads.VISIBILITY + " == '" + Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED + "')"; - static final String WHERE_COMPLETED = - Downloads.STATUS + " >= '200' AND " + - Downloads.VISIBILITY + " == '" + Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED + "'"; - - - /** - * This inner class is used to collate downloads that are owned by - * the same application. This is so that only one notification line - * item is used for all downloads of a given application. - * - */ - static class NotificationItem { - int id; // This first db _id for the download for the app - int totalCurrent = 0; - int totalTotal = 0; - int titleCount = 0; - String packageName; // App package name - String description; - String[] titles = new String[2]; // download titles. - - /* - * Add a second download to this notification item. - */ - void addItem(String title, int currentBytes, int totalBytes) { - totalCurrent += currentBytes; - if (totalBytes <= 0 || totalTotal == -1) { - totalTotal = -1; - } else { - totalTotal += totalBytes; - } - if (titleCount < 2) { - titles[titleCount] = title; - } - titleCount++; - } - } - - - /** - * Constructor - * @param ctx The context to use to obtain access to the - * Notification Service - */ - DownloadNotification(Context ctx) { - mContext = ctx; - mNotificationMgr = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - mNotifications = new HashMap(); - } - - /* - * Update the notification ui. - */ - public void updateNotification() { - updateActiveNotification(); - updateCompletedNotification(); - } - - private void updateActiveNotification() { - // Active downloads - Cursor c = mContext.getContentResolver().query( - Downloads.CONTENT_URI, new String [] { - Downloads._ID, Downloads.TITLE, Downloads.DESCRIPTION, - Downloads.NOTIFICATION_PACKAGE, - Downloads.NOTIFICATION_CLASS, - Downloads.CURRENT_BYTES, Downloads.TOTAL_BYTES, - Downloads.STATUS, Downloads._DATA - }, - WHERE_RUNNING, null, Downloads._ID); - - if (c == null) { - return; - } - - // Columns match projection in query above - final int idColumn = 0; - final int titleColumn = 1; - final int descColumn = 2; - final int ownerColumn = 3; - final int classOwnerColumn = 4; - final int currentBytesColumn = 5; - final int totalBytesColumn = 6; - final int statusColumn = 7; - final int filenameColumnId = 8; - - // Collate the notifications - mNotifications.clear(); - for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) { - String packageName = c.getString(ownerColumn); - int max = c.getInt(totalBytesColumn); - int progress = c.getInt(currentBytesColumn); - String title = c.getString(titleColumn); - if (title == null || title.length() == 0) { - title = mContext.getResources().getString( - R.string.download_unknown_title); - } - if (mNotifications.containsKey(packageName)) { - mNotifications.get(packageName).addItem(title, progress, max); - } else { - NotificationItem item = new NotificationItem(); - item.id = c.getInt(idColumn); - item.packageName = packageName; - item.description = c.getString(descColumn); - String className = c.getString(classOwnerColumn); - item.addItem(title, progress, max); - mNotifications.put(packageName, item); - } - - } - c.close(); - - // Add the notifications - for (NotificationItem item : mNotifications.values()) { - // Build the notification object - Notification n = new Notification(); - n.icon = android.R.drawable.stat_sys_download; - - n.flags |= Notification.FLAG_ONGOING_EVENT; - - // Build the RemoteView object - RemoteViews expandedView = new RemoteViews( - "com.android.providers.downloads", - R.layout.status_bar_ongoing_event_progress_bar); - StringBuilder title = new StringBuilder(item.titles[0]); - if (item.titleCount > 1) { - title.append(mContext.getString(R.string.notification_filename_separator)); - title.append(item.titles[1]); - n.number = item.titleCount; - if (item.titleCount > 2) { - title.append(mContext.getString(R.string.notification_filename_extras, - new Object[] { Integer.valueOf(item.titleCount - 2) })); - } - } else { - expandedView.setTextViewText(R.id.description, - item.description); - } - expandedView.setTextViewText(R.id.title, title); - expandedView.setProgressBar(R.id.progress_bar, - item.totalTotal, - item.totalCurrent, - item.totalTotal == -1); - expandedView.setTextViewText(R.id.progress_text, - getDownloadingText(item.totalTotal, item.totalCurrent)); - expandedView.setImageViewResource(R.id.appIcon, - android.R.drawable.stat_sys_download); - n.contentView = expandedView; - - Intent intent = new Intent(Constants.ACTION_LIST); - intent.setClassName("com.android.providers.downloads", - DownloadReceiver.class.getName()); - intent.setData(Uri.parse(Downloads.CONTENT_URI + "/" + item.id)); - intent.putExtra("multiple", item.titleCount > 1); - - n.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); - - mNotificationMgr.notify(item.id, n); - - } - } - - private void updateCompletedNotification() { - // Completed downloads - Cursor c = mContext.getContentResolver().query( - Downloads.CONTENT_URI, new String [] { - Downloads._ID, Downloads.TITLE, Downloads.DESCRIPTION, - Downloads.NOTIFICATION_PACKAGE, - Downloads.NOTIFICATION_CLASS, - Downloads.CURRENT_BYTES, Downloads.TOTAL_BYTES, - Downloads.STATUS, Downloads._DATA, - Downloads.LAST_MODIFICATION, Downloads.DESTINATION - }, - WHERE_COMPLETED, null, Downloads._ID); - - if (c == null) { - return; - } - - // Columns match projection in query above - final int idColumn = 0; - final int titleColumn = 1; - final int descColumn = 2; - final int ownerColumn = 3; - final int classOwnerColumn = 4; - final int currentBytesColumn = 5; - final int totalBytesColumn = 6; - final int statusColumn = 7; - final int filenameColumnId = 8; - final int lastModColumnId = 9; - final int destinationColumnId = 10; - - for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) { - // Add the notifications - Notification n = new Notification(); - n.icon = android.R.drawable.stat_sys_download_done; - - String title = c.getString(titleColumn); - if (title == null || title.length() == 0) { - title = mContext.getResources().getString( - R.string.download_unknown_title); - } - Uri contentUri = Uri.parse(Downloads.CONTENT_URI + "/" + c.getInt(idColumn)); - String caption; - Intent intent; - if (Downloads.isStatusError(c.getInt(statusColumn))) { - caption = mContext.getResources() - .getString(R.string.notification_download_failed); - intent = new Intent(Constants.ACTION_LIST); - } else { - caption = mContext.getResources() - .getString(R.string.notification_download_complete); - if (c.getInt(destinationColumnId) == Downloads.DESTINATION_EXTERNAL) { - intent = new Intent(Constants.ACTION_OPEN); - } else { - intent = new Intent(Constants.ACTION_LIST); - } - } - intent.setClassName("com.android.providers.downloads", - DownloadReceiver.class.getName()); - intent.setData(contentUri); - n.setLatestEventInfo(mContext, title, caption, - PendingIntent.getBroadcast(mContext, 0, intent, 0)); - - intent = new Intent(Constants.ACTION_HIDE); - intent.setClassName("com.android.providers.downloads", - DownloadReceiver.class.getName()); - intent.setData(contentUri); - n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); - - n.when = c.getLong(lastModColumnId); - - mNotificationMgr.notify(c.getInt(idColumn), n); - } - c.close(); - } - - /* - * Helper function to build the downloading text. - */ - private String getDownloadingText(long totalBytes, long currentBytes) { - if (totalBytes <= 0) { - return ""; - } - long progress = currentBytes * 100 / totalBytes; - StringBuilder sb = new StringBuilder(); - sb.append(progress); - sb.append('%'); - return sb.toString(); - } - -} diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java deleted file mode 100644 index f7cdd51e..00000000 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ /dev/null @@ -1,731 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.providers.downloads; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.UriMatcher; -import android.content.pm.PackageManager; -import android.database.CrossProcessCursor; -import android.database.Cursor; -import android.database.CursorWindow; -import android.database.CursorWrapper; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.database.SQLException; -import android.net.Uri; -import android.os.Binder; -import android.os.ParcelFileDescriptor; -import android.os.Process; -import android.provider.Downloads; -import android.util.Config; -import android.util.Log; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.HashSet; - - -/** - * Allows application to interact with the download manager. - */ -public final class DownloadProvider extends ContentProvider { - - /** Database filename */ - private static final String DB_NAME = "downloads.db"; - /** Current database version */ - private static final int DB_VERSION = 100; - /** Database version from which upgrading is a nop */ - private static final int DB_VERSION_NOP_UPGRADE_FROM = 31; - /** Database version to which upgrading is a nop */ - private static final int DB_VERSION_NOP_UPGRADE_TO = 100; - /** Name of table in the database */ - private static final String DB_TABLE = "downloads"; - - /** MIME type for the entire download list */ - private static final String DOWNLOAD_LIST_TYPE = "vnd.android.cursor.dir/download"; - /** MIME type for an individual download */ - private static final String DOWNLOAD_TYPE = "vnd.android.cursor.item/download"; - - /** URI matcher used to recognize URIs sent by applications */ - private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); - /** URI matcher constant for the URI of the entire download list */ - private static final int DOWNLOADS = 1; - /** URI matcher constant for the URI of an individual download */ - private static final int DOWNLOADS_ID = 2; - static { - sURIMatcher.addURI("downloads", "download", DOWNLOADS); - sURIMatcher.addURI("downloads", "download/#", DOWNLOADS_ID); - } - - private static final String[] sAppReadableColumnsArray = new String[] { - Downloads._ID, - Downloads.APP_DATA, - Downloads._DATA, - Downloads.MIMETYPE, - Downloads.VISIBILITY, - Downloads.CONTROL, - Downloads.STATUS, - Downloads.LAST_MODIFICATION, - Downloads.NOTIFICATION_PACKAGE, - Downloads.NOTIFICATION_CLASS, - Downloads.TOTAL_BYTES, - Downloads.CURRENT_BYTES, - Downloads.TITLE, - Downloads.DESCRIPTION - }; - - private static HashSet sAppReadableColumnsSet; - static { - sAppReadableColumnsSet = new HashSet(); - for (int i = 0; i < sAppReadableColumnsArray.length; ++i) { - sAppReadableColumnsSet.add(sAppReadableColumnsArray[i]); - } - } - - /** The database that lies underneath this content provider */ - private SQLiteOpenHelper mOpenHelper = null; - - /** - * Creates and updated database on demand when opening it. - * Helper class to create database the first time the provider is - * initialized and upgrade it when a new version of the provider needs - * an updated version of the database. - */ - private final class DatabaseHelper extends SQLiteOpenHelper { - - public DatabaseHelper(final Context context) { - super(context, DB_NAME, null, DB_VERSION); - } - - /** - * Creates database the first time we try to open it. - */ - @Override - public void onCreate(final SQLiteDatabase db) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "populating new database"); - } - createTable(db); - } - - /* (not a javadoc comment) - * Checks data integrity when opening the database. - */ - /* - * @Override - * public void onOpen(final SQLiteDatabase db) { - * super.onOpen(db); - * } - */ - - /** - * Updates the database format when a content provider is used - * with a database that was created with a different format. - */ - // Note: technically, this could also be a downgrade, so if we want - // to gracefully handle upgrades we should be careful about - // what to do on downgrades. - @Override - public void onUpgrade(final SQLiteDatabase db, int oldV, final int newV) { - if (oldV == DB_VERSION_NOP_UPGRADE_FROM) { - if (newV == DB_VERSION_NOP_UPGRADE_TO) { // that's a no-op upgrade. - return; - } - // NOP_FROM and NOP_TO are identical, just in different codelines. Upgrading - // from NOP_FROM is the same as upgrading from NOP_TO. - oldV = DB_VERSION_NOP_UPGRADE_TO; - } - Log.i(Constants.TAG, "Upgrading downloads database from version " + oldV + " to " + newV - + ", which will destroy all old data"); - dropTable(db); - createTable(db); - } - } - - /** - * Initializes the content provider when it is created. - */ - @Override - public boolean onCreate() { - mOpenHelper = new DatabaseHelper(getContext()); - return true; - } - - /** - * Returns the content-provider-style MIME types of the various - * types accessible through this content provider. - */ - @Override - public String getType(final Uri uri) { - int match = sURIMatcher.match(uri); - switch (match) { - case DOWNLOADS: { - return DOWNLOAD_LIST_TYPE; - } - case DOWNLOADS_ID: { - return DOWNLOAD_TYPE; - } - default: { - if (Constants.LOGV) { - Log.v(Constants.TAG, "calling getType on an unknown URI: " + uri); - } - throw new IllegalArgumentException("Unknown URI: " + uri); - } - } - } - - /** - * Creates the table that'll hold the download information. - */ - private void createTable(SQLiteDatabase db) { - try { - db.execSQL("CREATE TABLE " + DB_TABLE + "(" + - Downloads._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + - Downloads.URI + " TEXT, " + - Constants.RETRY_AFTER___REDIRECT_COUNT + " INTEGER, " + - Downloads.APP_DATA + " TEXT, " + - Downloads.NO_INTEGRITY + " BOOLEAN, " + - Downloads.FILENAME_HINT + " TEXT, " + - Constants.OTA_UPDATE + " BOOLEAN, " + - Downloads._DATA + " TEXT, " + - Downloads.MIMETYPE + " TEXT, " + - Downloads.DESTINATION + " INTEGER, " + - Constants.NO_SYSTEM_FILES + " BOOLEAN, " + - Downloads.VISIBILITY + " INTEGER, " + - Downloads.CONTROL + " INTEGER, " + - Downloads.STATUS + " INTEGER, " + - Constants.FAILED_CONNECTIONS + " INTEGER, " + - Downloads.LAST_MODIFICATION + " BIGINT, " + - Downloads.NOTIFICATION_PACKAGE + " TEXT, " + - Downloads.NOTIFICATION_CLASS + " TEXT, " + - Downloads.NOTIFICATION_EXTRAS + " TEXT, " + - Downloads.COOKIE_DATA + " TEXT, " + - Downloads.USER_AGENT + " TEXT, " + - Downloads.REFERER + " TEXT, " + - Downloads.TOTAL_BYTES + " INTEGER, " + - Downloads.CURRENT_BYTES + " INTEGER, " + - Constants.ETAG + " TEXT, " + - Constants.UID + " INTEGER, " + - Downloads.OTHER_UID + " INTEGER, " + - Downloads.TITLE + " TEXT, " + - Downloads.DESCRIPTION + " TEXT, " + - Constants.MEDIA_SCANNED + " BOOLEAN);"); - } catch (SQLException ex) { - Log.e(Constants.TAG, "couldn't create table in downloads database"); - throw ex; - } - } - - /** - * Deletes the table that holds the download information. - */ - private void dropTable(SQLiteDatabase db) { - try { - db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE); - } catch (SQLException ex) { - Log.e(Constants.TAG, "couldn't drop table in downloads database"); - throw ex; - } - } - - /** - * Inserts a row in the database - */ - @Override - public Uri insert(final Uri uri, final ContentValues values) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - - if (sURIMatcher.match(uri) != DOWNLOADS) { - if (Config.LOGD) { - Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri); - } - throw new IllegalArgumentException("Unknown/Invalid URI " + uri); - } - - ContentValues filteredValues = new ContentValues(); - - copyString(Downloads.URI, values, filteredValues); - copyString(Downloads.APP_DATA, values, filteredValues); - copyBoolean(Downloads.NO_INTEGRITY, values, filteredValues); - copyString(Downloads.FILENAME_HINT, values, filteredValues); - copyString(Downloads.MIMETYPE, values, filteredValues); - Integer dest = values.getAsInteger(Downloads.DESTINATION); - if (dest != null) { - if (getContext().checkCallingPermission(Downloads.PERMISSION_ACCESS_ADVANCED) - != PackageManager.PERMISSION_GRANTED - && dest != Downloads.DESTINATION_EXTERNAL - && dest != Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE) { - throw new SecurityException("unauthorized destination code"); - } - filteredValues.put(Downloads.DESTINATION, dest); - } - Integer vis = values.getAsInteger(Downloads.VISIBILITY); - if (vis == null) { - if (dest == Downloads.DESTINATION_EXTERNAL) { - filteredValues.put(Downloads.VISIBILITY, - Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - } else { - filteredValues.put(Downloads.VISIBILITY, Downloads.VISIBILITY_HIDDEN); - } - } else { - filteredValues.put(Downloads.VISIBILITY, vis); - } - copyInteger(Downloads.CONTROL, values, filteredValues); - filteredValues.put(Downloads.STATUS, Downloads.STATUS_PENDING); - filteredValues.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis()); - String pckg = values.getAsString(Downloads.NOTIFICATION_PACKAGE); - String clazz = values.getAsString(Downloads.NOTIFICATION_CLASS); - if (pckg != null && clazz != null) { - int uid = Binder.getCallingUid(); - try { - if (uid == 0 || - getContext().getPackageManager().getApplicationInfo(pckg, 0).uid == uid) { - filteredValues.put(Downloads.NOTIFICATION_PACKAGE, pckg); - filteredValues.put(Downloads.NOTIFICATION_CLASS, clazz); - } - } catch (PackageManager.NameNotFoundException ex) { - /* ignored for now */ - } - } - copyString(Downloads.NOTIFICATION_EXTRAS, values, filteredValues); - copyString(Downloads.COOKIE_DATA, values, filteredValues); - copyString(Downloads.USER_AGENT, values, filteredValues); - copyString(Downloads.REFERER, values, filteredValues); - if (getContext().checkCallingPermission(Downloads.PERMISSION_ACCESS_ADVANCED) - == PackageManager.PERMISSION_GRANTED) { - copyInteger(Downloads.OTHER_UID, values, filteredValues); - } - filteredValues.put(Constants.UID, Binder.getCallingUid()); - if (Binder.getCallingUid() == 0) { - copyInteger(Constants.UID, values, filteredValues); - } - copyString(Downloads.TITLE, values, filteredValues); - copyString(Downloads.DESCRIPTION, values, filteredValues); - - if (Constants.LOGVV) { - Log.v(Constants.TAG, "initiating download with UID " - + filteredValues.getAsInteger(Constants.UID)); - if (filteredValues.containsKey(Downloads.OTHER_UID)) { - Log.v(Constants.TAG, "other UID " + - filteredValues.getAsInteger(Downloads.OTHER_UID)); - } - } - - Context context = getContext(); - context.startService(new Intent(context, DownloadService.class)); - - long rowID = db.insert(DB_TABLE, null, filteredValues); - - Uri ret = null; - - if (rowID != -1) { - context.startService(new Intent(context, DownloadService.class)); - ret = Uri.parse(Downloads.CONTENT_URI + "/" + rowID); - context.getContentResolver().notifyChange(uri, null); - } else { - if (Config.LOGD) { - Log.d(Constants.TAG, "couldn't insert into downloads database"); - } - } - - return ret; - } - - /** - * Starts a database query - */ - @Override - public Cursor query(final Uri uri, String[] projection, - final String selection, final String[] selectionArgs, - final String sort) { - - Helpers.validateSelection(selection, sAppReadableColumnsSet); - - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - - int match = sURIMatcher.match(uri); - boolean emptyWhere = true; - switch (match) { - case DOWNLOADS: { - qb.setTables(DB_TABLE); - break; - } - case DOWNLOADS_ID: { - qb.setTables(DB_TABLE); - qb.appendWhere(Downloads._ID + "="); - qb.appendWhere(uri.getPathSegments().get(1)); - emptyWhere = false; - break; - } - default: { - if (Constants.LOGV) { - Log.v(Constants.TAG, "querying unknown URI: " + uri); - } - throw new IllegalArgumentException("Unknown URI: " + uri); - } - } - - if (Binder.getCallingPid() != Process.myPid() && Binder.getCallingUid() != 0) { - if (!emptyWhere) { - qb.appendWhere(" AND "); - } - qb.appendWhere("( " + Constants.UID + "=" + Binder.getCallingUid() + " OR " - + Downloads.OTHER_UID + "=" + Binder.getCallingUid() + " )"); - emptyWhere = false; - - if (projection == null) { - projection = sAppReadableColumnsArray; - } else { - for (int i = 0; i < projection.length; ++i) { - if (!sAppReadableColumnsSet.contains(projection[i])) { - throw new IllegalArgumentException( - "column " + projection[i] + " is not allowed in queries"); - } - } - } - } - - if (Constants.LOGVV) { - java.lang.StringBuilder sb = new java.lang.StringBuilder(); - sb.append("starting query, database is "); - if (db != null) { - sb.append("not "); - } - sb.append("null; "); - if (projection == null) { - sb.append("projection is null; "); - } else if (projection.length == 0) { - sb.append("projection is empty; "); - } else { - for (int i = 0; i < projection.length; ++i) { - sb.append("projection["); - sb.append(i); - sb.append("] is "); - sb.append(projection[i]); - sb.append("; "); - } - } - sb.append("selection is "); - sb.append(selection); - sb.append("; "); - if (selectionArgs == null) { - sb.append("selectionArgs is null; "); - } else if (selectionArgs.length == 0) { - sb.append("selectionArgs is empty; "); - } else { - for (int i = 0; i < selectionArgs.length; ++i) { - sb.append("selectionArgs["); - sb.append(i); - sb.append("] is "); - sb.append(selectionArgs[i]); - sb.append("; "); - } - } - sb.append("sort is "); - sb.append(sort); - sb.append("."); - Log.v(Constants.TAG, sb.toString()); - } - - Cursor ret = qb.query(db, projection, selection, selectionArgs, - null, null, sort); - - if (ret != null) { - ret = new ReadOnlyCursorWrapper(ret); - } - - if (ret != null) { - ret.setNotificationUri(getContext().getContentResolver(), uri); - if (Constants.LOGVV) { - Log.v(Constants.TAG, - "created cursor " + ret + " on behalf of " + Binder.getCallingPid()); - } - } else { - if (Constants.LOGV) { - Log.v(Constants.TAG, "query failed in downloads database"); - } - } - - return ret; - } - - /** - * Updates a row in the database - */ - @Override - public int update(final Uri uri, final ContentValues values, - final String where, final String[] whereArgs) { - - Helpers.validateSelection(where, sAppReadableColumnsSet); - - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - - int count; - long rowId = 0; - boolean startService = false; - - ContentValues filteredValues; - if (Binder.getCallingPid() != Process.myPid()) { - filteredValues = new ContentValues(); - copyString(Downloads.APP_DATA, values, filteredValues); - copyInteger(Downloads.VISIBILITY, values, filteredValues); - Integer i = values.getAsInteger(Downloads.CONTROL); - if (i != null) { - filteredValues.put(Downloads.CONTROL, i); - startService = true; - } - copyInteger(Downloads.CONTROL, values, filteredValues); - copyString(Downloads.TITLE, values, filteredValues); - copyString(Downloads.DESCRIPTION, values, filteredValues); - } else { - filteredValues = values; - } - int match = sURIMatcher.match(uri); - switch (match) { - case DOWNLOADS: - case DOWNLOADS_ID: { - String myWhere; - if (where != null) { - if (match == DOWNLOADS) { - myWhere = "( " + where + " )"; - } else { - myWhere = "( " + where + " ) AND "; - } - } else { - myWhere = ""; - } - if (match == DOWNLOADS_ID) { - String segment = uri.getPathSegments().get(1); - rowId = Long.parseLong(segment); - myWhere += " ( " + Downloads._ID + " = " + rowId + " ) "; - } - if (Binder.getCallingPid() != Process.myPid() && Binder.getCallingUid() != 0) { - myWhere += " AND ( " + Constants.UID + "=" + Binder.getCallingUid() + " OR " - + Downloads.OTHER_UID + "=" + Binder.getCallingUid() + " )"; - } - if (filteredValues.size() > 0) { - count = db.update(DB_TABLE, filteredValues, myWhere, whereArgs); - } else { - count = 0; - } - break; - } - default: { - if (Config.LOGD) { - Log.d(Constants.TAG, "updating unknown/invalid URI: " + uri); - } - throw new UnsupportedOperationException("Cannot update URI: " + uri); - } - } - getContext().getContentResolver().notifyChange(uri, null); - if (startService) { - Context context = getContext(); - context.startService(new Intent(context, DownloadService.class)); - } - return count; - } - - /** - * Deletes a row in the database - */ - @Override - public int delete(final Uri uri, final String where, - final String[] whereArgs) { - - Helpers.validateSelection(where, sAppReadableColumnsSet); - - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - int count; - int match = sURIMatcher.match(uri); - switch (match) { - case DOWNLOADS: - case DOWNLOADS_ID: { - String myWhere; - if (where != null) { - if (match == DOWNLOADS) { - myWhere = "( " + where + " )"; - } else { - myWhere = "( " + where + " ) AND "; - } - } else { - myWhere = ""; - } - if (match == DOWNLOADS_ID) { - String segment = uri.getPathSegments().get(1); - long rowId = Long.parseLong(segment); - myWhere += " ( " + Downloads._ID + " = " + rowId + " ) "; - } - if (Binder.getCallingPid() != Process.myPid() && Binder.getCallingUid() != 0) { - myWhere += " AND ( " + Constants.UID + "=" + Binder.getCallingUid() + " OR " - + Downloads.OTHER_UID + "=" + Binder.getCallingUid() + " )"; - } - count = db.delete(DB_TABLE, myWhere, whereArgs); - break; - } - default: { - if (Config.LOGD) { - Log.d(Constants.TAG, "deleting unknown/invalid URI: " + uri); - } - throw new UnsupportedOperationException("Cannot delete URI: " + uri); - } - } - getContext().getContentResolver().notifyChange(uri, null); - return count; - } - - /** - * Remotely opens a file - */ - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) - throws FileNotFoundException { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "openFile uri: " + uri + ", mode: " + mode - + ", uid: " + Binder.getCallingUid()); - Cursor cursor = query(Downloads.CONTENT_URI, new String[] { "_id" }, null, null, "_id"); - if (cursor == null) { - Log.v(Constants.TAG, "null cursor in openFile"); - } else { - if (!cursor.moveToFirst()) { - Log.v(Constants.TAG, "empty cursor in openFile"); - } else { - do { - Log.v(Constants.TAG, "row " + cursor.getInt(0) + " available"); - } while(cursor.moveToNext()); - } - cursor.close(); - } - cursor = query(uri, new String[] { "_data" }, null, null, null); - if (cursor == null) { - Log.v(Constants.TAG, "null cursor in openFile"); - } else { - if (!cursor.moveToFirst()) { - Log.v(Constants.TAG, "empty cursor in openFile"); - } else { - String filename = cursor.getString(0); - Log.v(Constants.TAG, "filename in openFile: " + filename); - if (new java.io.File(filename).isFile()) { - Log.v(Constants.TAG, "file exists in openFile"); - } - } - cursor.close(); - } - } - - // This logic is mostly copied form openFileHelper. If openFileHelper eventually - // gets split into small bits (to extract the filename and the modebits), - // this code could use the separate bits and be deeply simplified. - Cursor c = query(uri, new String[]{"_data"}, null, null, null); - int count = (c != null) ? c.getCount() : 0; - if (count != 1) { - // If there is not exactly one result, throw an appropriate exception. - if (c != null) { - c.close(); - } - if (count == 0) { - throw new FileNotFoundException("No entry for " + uri); - } - throw new FileNotFoundException("Multiple items at " + uri); - } - - c.moveToFirst(); - String path = c.getString(0); - c.close(); - if (path == null) { - throw new FileNotFoundException("No filename found."); - } - if (!Helpers.isFilenameValid(path)) { - throw new FileNotFoundException("Invalid filename."); - } - - if (!"r".equals(mode)) { - throw new FileNotFoundException("Bad mode for " + uri + ": " + mode); - } - ParcelFileDescriptor ret = ParcelFileDescriptor.open(new File(path), - ParcelFileDescriptor.MODE_READ_ONLY); - - if (ret == null) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "couldn't open file"); - } - throw new FileNotFoundException("couldn't open file"); - } else { - ContentValues values = new ContentValues(); - values.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis()); - update(uri, values, null, null); - } - return ret; - } - - private static final void copyInteger(String key, ContentValues from, ContentValues to) { - Integer i = from.getAsInteger(key); - if (i != null) { - to.put(key, i); - } - } - - private static final void copyBoolean(String key, ContentValues from, ContentValues to) { - Boolean b = from.getAsBoolean(key); - if (b != null) { - to.put(key, b); - } - } - - private static final void copyString(String key, ContentValues from, ContentValues to) { - String s = from.getAsString(key); - if (s != null) { - to.put(key, s); - } - } - - private class ReadOnlyCursorWrapper extends CursorWrapper implements CrossProcessCursor { - public ReadOnlyCursorWrapper(Cursor cursor) { - super(cursor); - mCursor = (CrossProcessCursor) cursor; - } - - public boolean deleteRow() { - throw new SecurityException("Download manager cursors are read-only"); - } - - public boolean commitUpdates() { - throw new SecurityException("Download manager cursors are read-only"); - } - - public void fillWindow(int pos, CursorWindow window) { - mCursor.fillWindow(pos, window); - } - - public CursorWindow getWindow() { - return mCursor.getWindow(); - } - - public boolean onMove(int oldPosition, int newPosition) { - return mCursor.onMove(oldPosition, newPosition); - } - - private CrossProcessCursor mCursor; - } - -} diff --git a/src/com/android/providers/downloads/DownloadReceiver.java b/src/com/android/providers/downloads/DownloadReceiver.java deleted file mode 100644 index 03a37186..00000000 --- a/src/com/android/providers/downloads/DownloadReceiver.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.providers.downloads; - -import android.app.NotificationManager; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.database.Cursor; -import android.provider.Downloads; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.Uri; -import android.util.Config; -import android.util.Log; - -import java.io.File; -import java.util.List; - -/** - * Receives system broadcasts (boot, network connectivity) - */ -public class DownloadReceiver extends BroadcastReceiver { - - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Receiver onBoot"); - } - context.startService(new Intent(context, DownloadService.class)); - } else if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Receiver onConnectivity"); - } - NetworkInfo info = (NetworkInfo) - intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); - if (info != null && info.isConnected()) { - context.startService(new Intent(context, DownloadService.class)); - } - } else if (intent.getAction().equals(Constants.ACTION_RETRY)) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Receiver retry"); - } - context.startService(new Intent(context, DownloadService.class)); - } else if (intent.getAction().equals(Constants.ACTION_OPEN) - || intent.getAction().equals(Constants.ACTION_LIST)) { - if (Constants.LOGVV) { - if (intent.getAction().equals(Constants.ACTION_OPEN)) { - Log.v(Constants.TAG, "Receiver open for " + intent.getData()); - } else { - Log.v(Constants.TAG, "Receiver list for " + intent.getData()); - } - } - Cursor cursor = context.getContentResolver().query( - intent.getData(), null, null, null, null); - if (cursor != null) { - if (cursor.moveToFirst()) { - int statusColumn = cursor.getColumnIndexOrThrow(Downloads.STATUS); - int status = cursor.getInt(statusColumn); - int visibilityColumn = cursor.getColumnIndexOrThrow(Downloads.VISIBILITY); - int visibility = cursor.getInt(visibilityColumn); - if (Downloads.isStatusCompleted(status) - && visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) { - ContentValues values = new ContentValues(); - values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE); - context.getContentResolver().update(intent.getData(), values, null, null); - } - - if (intent.getAction().equals(Constants.ACTION_OPEN)) { - int filenameColumn = cursor.getColumnIndexOrThrow(Downloads._DATA); - int mimetypeColumn = cursor.getColumnIndexOrThrow(Downloads.MIMETYPE); - String filename = cursor.getString(filenameColumn); - String mimetype = cursor.getString(mimetypeColumn); - Uri path = Uri.parse(filename); - // If there is no scheme, then it must be a file - if (path.getScheme() == null) { - path = Uri.fromFile(new File(filename)); - } - Intent activityIntent = new Intent(Intent.ACTION_VIEW); - activityIntent.setDataAndType(path, mimetype); - activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - context.startActivity(activityIntent); - } catch (ActivityNotFoundException ex) { - if (Config.LOGD) { - Log.d(Constants.TAG, "no activity for " + mimetype, ex); - } - // nothing anyone can do about this, but we're in a clean state, - // swallow the exception entirely - } - } else { - int packageColumn = - cursor.getColumnIndexOrThrow(Downloads.NOTIFICATION_PACKAGE); - int classColumn = - cursor.getColumnIndexOrThrow(Downloads.NOTIFICATION_CLASS); - String pckg = cursor.getString(packageColumn); - String clazz = cursor.getString(classColumn); - if (pckg != null && clazz != null) { - Intent appIntent = new Intent(Downloads.NOTIFICATION_CLICKED_ACTION); - appIntent.setClassName(pckg, clazz); - if (intent.getBooleanExtra("multiple", true)) { - appIntent.setData(Downloads.CONTENT_URI); - } else { - appIntent.setData(intent.getData()); - } - context.sendBroadcast(appIntent); - } - } - } - cursor.close(); - } - NotificationManager notMgr = (NotificationManager) context - .getSystemService(Context.NOTIFICATION_SERVICE); - if (notMgr != null) { - notMgr.cancel((int) ContentUris.parseId(intent.getData())); - } - } else if (intent.getAction().equals(Constants.ACTION_HIDE)) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Receiver hide for " + intent.getData()); - } - Cursor cursor = context.getContentResolver().query( - intent.getData(), null, null, null, null); - if (cursor != null) { - if (cursor.moveToFirst()) { - int statusColumn = cursor.getColumnIndexOrThrow(Downloads.STATUS); - int status = cursor.getInt(statusColumn); - int visibilityColumn = cursor.getColumnIndexOrThrow(Downloads.VISIBILITY); - int visibility = cursor.getInt(visibilityColumn); - if (Downloads.isStatusCompleted(status) - && visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) { - ContentValues values = new ContentValues(); - values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE); - context.getContentResolver().update(intent.getData(), values, null, null); - } - } - cursor.close(); - } - } - } -} diff --git a/src/com/android/providers/downloads/DownloadService.java b/src/com/android/providers/downloads/DownloadService.java deleted file mode 100644 index 0600cfb6..00000000 --- a/src/com/android/providers/downloads/DownloadService.java +++ /dev/null @@ -1,879 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.providers.downloads; - -import com.google.android.collect.Lists; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.ComponentName; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.ServiceConnection; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.CharArrayBuffer; -import android.drm.mobile1.DrmRawContent; -import android.media.IMediaScannerService; -import android.net.Uri; -import android.os.RemoteException; -import android.os.Environment; -import android.os.Handler; -import android.os.IBinder; -import android.os.Process; -import android.provider.Downloads; -import android.util.Config; -import android.util.Log; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; - - -/** - * Performs the background downloads requested by applications that use the Downloads provider. - */ -public class DownloadService extends Service { - - /* ------------ Constants ------------ */ - - /* ------------ Members ------------ */ - - /** Observer to get notified when the content observer's data changes */ - private DownloadManagerContentObserver mObserver; - - /** Class to handle Notification Manager updates */ - private DownloadNotification mNotifier; - - /** - * The 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 ArrayList mDownloads; - - /** - * The thread that updates the internal download list from the content - * provider. - */ - private UpdateThread updateThread; - - /** - * Whether the internal download list should be updated from the content - * provider. - */ - private boolean pendingUpdate; - - /** - * The ServiceConnection object that tells us when we're connected to and disconnected from - * the Media Scanner - */ - private MediaScannerConnection mMediaScannerConnection; - - private boolean mMediaScannerConnecting; - - /** - * The IPC interface to the Media Scanner - */ - private IMediaScannerService mMediaScannerService; - - /** - * Array used when extracting strings from content provider - */ - private CharArrayBuffer oldChars; - - /** - * Array used when extracting strings from content provider - */ - private CharArrayBuffer newChars; - - /* ------------ Inner Classes ------------ */ - - /** - * Receives notifications when the data in the content provider changes - */ - private class DownloadManagerContentObserver extends ContentObserver { - - public DownloadManagerContentObserver() { - super(new Handler()); - } - - /** - * Receives notification when the data in the observed content - * provider changes. - */ - public void onChange(final boolean selfChange) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Service ContentObserver received notification"); - } - updateFromProvider(); - } - - } - - /** - * Gets called back when the connection to the media - * scanner is established or lost. - */ - public class MediaScannerConnection implements ServiceConnection { - public void onServiceConnected(ComponentName className, IBinder service) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Connected to Media Scanner"); - } - mMediaScannerConnecting = false; - synchronized (DownloadService.this) { - mMediaScannerService = IMediaScannerService.Stub.asInterface(service); - if (mMediaScannerService != null) { - updateFromProvider(); - } - } - } - - public void disconnectMediaScanner() { - synchronized (DownloadService.this) { - if (mMediaScannerService != null) { - mMediaScannerService = null; - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Disconnecting from Media Scanner"); - } - try { - unbindService(this); - } catch (IllegalArgumentException ex) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "unbindService threw up: " + ex); - } - } - } - } - } - - public void onServiceDisconnected(ComponentName className) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Disconnected from Media Scanner"); - } - synchronized (DownloadService.this) { - mMediaScannerService = null; - } - } - } - - /* ------------ Methods ------------ */ - - /** - * Returns an IBinder instance when someone wants to connect to this - * service. Binding to this service is not allowed. - * - * @throws UnsupportedOperationException - */ - public IBinder onBind(Intent i) { - throw new UnsupportedOperationException("Cannot bind to Download Manager Service"); - } - - /** - * Initializes the service when it is first created - */ - public void onCreate() { - 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(); - } - - /** - * Responds to a call to startService - */ - public void onStart(Intent intent, int startId) { - super.onStart(intent, startId); - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Service onStart"); - } - - updateFromProvider(); - } - - /** - * Cleans up when the service is destroyed - */ - public void onDestroy() { - getContentResolver().unregisterContentObserver(mObserver); - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Service onDestroy"); - } - super.onDestroy(); - } - - /** - * Parses data from the content provider into private array - */ - private void updateFromProvider() { - synchronized (this) { - pendingUpdate = true; - if (updateThread == null) { - updateThread = new UpdateThread(); - updateThread.start(); - } - } - } - - private class UpdateThread extends Thread { - public UpdateThread() { - super("Download Service"); - } - - public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - boolean keepService = false; - // for each update from the database, remember which download is - // supposed to get restarted soonest in the future - long wakeUp = Long.MAX_VALUE; - for (;;) { - synchronized (DownloadService.this) { - if (updateThread != this) { - throw new IllegalStateException( - "multiple UpdateThreads in DownloadService"); - } - if (!pendingUpdate) { - updateThread = null; - if (!keepService) { - stopSelf(); - } - if (wakeUp != Long.MAX_VALUE) { - AlarmManager alarms = - (AlarmManager) getSystemService(Context.ALARM_SERVICE); - if (alarms == null) { - Log.e(Constants.TAG, "couldn't get alarm manager"); - } else { - if (Constants.LOGV) { - Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms"); - } - Intent intent = new Intent(Constants.ACTION_RETRY); - intent.setClassName("com.android.providers.downloads", - DownloadReceiver.class.getName()); - alarms.set( - AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + wakeUp, - PendingIntent.getBroadcast(DownloadService.this, 0, intent, - PendingIntent.FLAG_ONE_SHOT)); - } - } - oldChars = null; - newChars = null; - return; - } - pendingUpdate = false; - } - boolean networkAvailable = Helpers.isNetworkAvailable(DownloadService.this); - boolean networkRoaming = Helpers.isNetworkRoaming(DownloadService.this); - long now = System.currentTimeMillis(); - - Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI, - null, null, null, Downloads._ID); - - if (cursor == null) { - return; - } - - cursor.moveToFirst(); - - int arrayPos = 0; - - boolean mustScan = false; - keepService = false; - wakeUp = Long.MAX_VALUE; - - boolean isAfterLast = cursor.isAfterLast(); - - int idColumn = cursor.getColumnIndexOrThrow(Downloads._ID); - - /* - * Walk the cursor and the local array to keep them in sync. The key - * to the algorithm is that the ids are unique and sorted both in - * the cursor and in the array, so that they can be processed in - * order in both sources at the same time: at each step, both - * sources point to the lowest id that hasn't been processed from - * that source, and the algorithm processes the lowest id from - * those two possibilities. - * At each step: - * -If the array contains an entry that's not in the cursor, remove the - * entry, move to next entry in the array. - * -If the array contains an entry that's in the cursor, nothing to do, - * move to next cursor row and next array entry. - * -If the cursor contains an entry that's not in the array, insert - * a new entry in the array, move to next cursor row and next - * array entry. - */ - while (!isAfterLast || arrayPos < mDownloads.size()) { - if (isAfterLast) { - // We're beyond the end of the cursor but there's still some - // stuff in the local array, which can only be junk - if (Constants.LOGVV) { - int arrayId = ((DownloadInfo) mDownloads.get(arrayPos)).id; - Log.v(Constants.TAG, "Array update: trimming " + - arrayId + " @ " + arrayPos); - } - if (shouldScanFile(arrayPos) && mediaScannerConnected()) { - scanFile(null, arrayPos); - } - deleteDownload(arrayPos); // this advances in the array - } else { - int id = cursor.getInt(idColumn); - - if (arrayPos == mDownloads.size()) { - insertDownload(cursor, arrayPos, networkAvailable, networkRoaming, now); - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Array update: inserting " + - id + " @ " + arrayPos); - } - if (shouldScanFile(arrayPos) - && (!mediaScannerConnected() || !scanFile(cursor, arrayPos))) { - mustScan = true; - keepService = true; - } - if (visibleNotification(arrayPos)) { - keepService = true; - } - long next = nextAction(arrayPos, now); - if (next == 0) { - keepService = true; - } else if (next > 0 && next < wakeUp) { - wakeUp = next; - } - ++arrayPos; - cursor.moveToNext(); - isAfterLast = cursor.isAfterLast(); - } else { - int arrayId = mDownloads.get(arrayPos).id; - - if (arrayId < id) { - // The array entry isn't in the cursor - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Array update: removing " + arrayId - + " @ " + arrayPos); - } - if (shouldScanFile(arrayPos) && mediaScannerConnected()) { - scanFile(null, arrayPos); - } - deleteDownload(arrayPos); // this advances in the array - } else if (arrayId == id) { - // This cursor row already exists in the stored array - updateDownload( - cursor, arrayPos, - networkAvailable, networkRoaming, now); - if (shouldScanFile(arrayPos) - && (!mediaScannerConnected() - || !scanFile(cursor, arrayPos))) { - mustScan = true; - keepService = true; - } - if (visibleNotification(arrayPos)) { - keepService = true; - } - long next = nextAction(arrayPos, now); - if (next == 0) { - keepService = true; - } else if (next > 0 && next < wakeUp) { - wakeUp = next; - } - ++arrayPos; - cursor.moveToNext(); - isAfterLast = cursor.isAfterLast(); - } else { - // This cursor entry didn't exist in the stored array - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Array update: appending " + - id + " @ " + arrayPos); - } - insertDownload( - cursor, arrayPos, - networkAvailable, networkRoaming, now); - if (shouldScanFile(arrayPos) - && (!mediaScannerConnected() - || !scanFile(cursor, arrayPos))) { - mustScan = true; - keepService = true; - } - if (visibleNotification(arrayPos)) { - keepService = true; - } - long next = nextAction(arrayPos, now); - if (next == 0) { - keepService = true; - } else if (next > 0 && next < wakeUp) { - wakeUp = next; - } - ++arrayPos; - cursor.moveToNext(); - isAfterLast = cursor.isAfterLast(); - } - } - } - } - - mNotifier.updateNotification(); - - if (mustScan) { - if (!mMediaScannerConnecting) { - Intent intent = new Intent(); - intent.setClassName("com.android.providers.media", - "com.android.providers.media.MediaScannerService"); - mMediaScannerConnecting = true; - bindService(intent, mMediaScannerConnection, BIND_AUTO_CREATE); - } - } else { - mMediaScannerConnection.disconnectMediaScanner(); - } - - cursor.close(); - } - } - } - - /** - * Removes files that may have been left behind in the cache directory - */ - private void removeSpuriousFiles() { - 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 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 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(); - } - } - - /** - * Drops old rows from the database to prevent it from growing too large - */ - private void trimDatabase() { - 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(); - } - - /** - * Keeps a local copy of the info about a download, and initiates the - * download if appropriate. - */ - private void insertDownload( - Cursor cursor, int arrayPos, - boolean networkAvailable, boolean networkRoaming, long now) { - 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); - } - } - } - - /** - * Updates the local copy of the info about a download. - */ - private void updateDownload( - Cursor cursor, int arrayPos, - boolean networkAvailable, boolean networkRoaming, long now) { - 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(); - } - } - } - - /** - * Returns a String that holds the current value of the column, - * optimizing for the case where the value hasn't changed. - */ - private String stringFromCursor(String old, Cursor cursor, String column) { - 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; - } - - /** - * Removes the local copy of the info about a download. - */ - private void deleteDownload(int arrayPos) { - 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); - } - - /** - * 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" - */ - private long nextAction(int arrayPos, long 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; - } - - /** - * Returns whether there's a visible notification for this download - */ - private boolean visibleNotification(int arrayPos) { - DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); - return info.hasCompletionNotification(); - } - - /** - * Returns whether a file should be scanned - */ - private boolean shouldScanFile(int arrayPos) { - 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); - } - - /** - * Returns whether we have a live connection to the Media Scanner - */ - private boolean mediaScannerConnected() { - return mMediaScannerService != null; - } - - /** - * Attempts to scan the file if necessary. - * Returns true if the file has been properly scanned. - */ - private boolean scanFile(Cursor cursor, int arrayPos) { - 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; - } - -} diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java deleted file mode 100644 index 923e36d1..00000000 --- a/src/com/android/providers/downloads/DownloadThread.java +++ /dev/null @@ -1,710 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.providers.downloads; - -import org.apache.http.client.methods.AbortableHttpRequest; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.HttpClient; -import org.apache.http.entity.StringEntity; -import org.apache.http.Header; -import org.apache.http.HttpResponse; - -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.drm.mobile1.DrmRawContent; -import android.net.http.AndroidHttpClient; -import android.net.Uri; -import android.os.FileUtils; -import android.os.PowerManager; -import android.os.Process; -import android.provider.Downloads; -import android.provider.DrmStore; -import android.util.Config; -import android.util.Log; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URI; - -/** - * Runs an actual download - */ -public class DownloadThread extends Thread { - - private Context mContext; - private DownloadInfo mInfo; - - public DownloadThread(Context context, DownloadInfo info) { - mContext = context; - mInfo = info; - } - - /** - * Returns the user agent provided by the initiating app, or use the default one - */ - private String userAgent() { - String userAgent = mInfo.userAgent; - if (userAgent != null) { - } - if (userAgent == null) { - userAgent = Constants.DEFAULT_USER_AGENT; - } - return userAgent; - } - - /** - * Executes the download in a separate thread - */ - public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - int finalStatus = Downloads.STATUS_UNKNOWN_ERROR; - boolean countRetry = false; - int retryAfter = 0; - int redirectCount = mInfo.redirectCount; - String newUri = null; - boolean gotData = false; - String filename = null; - String mimeType = mInfo.mimetype; - FileOutputStream stream = null; - AndroidHttpClient client = null; - PowerManager.WakeLock wakeLock = null; - Uri contentUri = Uri.parse(Downloads.CONTENT_URI + "/" + mInfo.id); - - try { - boolean continuingDownload = false; - String headerAcceptRanges = null; - String headerContentDisposition = null; - String headerContentLength = null; - String headerContentLocation = null; - String headerETag = null; - String headerTransferEncoding = null; - - byte data[] = new byte[Constants.BUFFER_SIZE]; - - int bytesSoFar = 0; - - PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); - wakeLock.acquire(); - - filename = mInfo.filename; - if (filename != null) { - if (!Helpers.isFilenameValid(filename)) { - finalStatus = Downloads.STATUS_FILE_ERROR; - notifyDownloadCompleted( - finalStatus, false, 0, 0, false, filename, null, mInfo.mimetype); - return; - } - // We're resuming a download that got interrupted - File f = new File(filename); - if (f.exists()) { - long fileLength = f.length(); - if (fileLength == 0) { - // The download hadn't actually started, we can restart from scratch - f.delete(); - filename = null; - } else if (mInfo.etag == null && !mInfo.noIntegrity) { - // Tough luck, that's not a resumable download - if (Config.LOGD) { - Log.d(Constants.TAG, - "can't resume interrupted non-resumable download"); - } - f.delete(); - finalStatus = Downloads.STATUS_PRECONDITION_FAILED; - notifyDownloadCompleted( - finalStatus, false, 0, 0, false, filename, null, mInfo.mimetype); - return; - } else { - // All right, we'll be able to resume this download - stream = new FileOutputStream(filename, true); - bytesSoFar = (int) fileLength; - if (mInfo.totalBytes != -1) { - headerContentLength = Integer.toString(mInfo.totalBytes); - } - headerETag = mInfo.etag; - continuingDownload = true; - } - } - } - - int bytesNotified = bytesSoFar; - // starting with MIN_VALUE means that the first write will commit - // progress to the database - long timeLastNotification = 0; - - client = AndroidHttpClient.newInstance(userAgent()); - - if (stream != null && mInfo.destination == Downloads.DESTINATION_EXTERNAL - && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING - .equalsIgnoreCase(mimeType)) { - try { - stream.close(); - stream = null; - } catch (IOException ex) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "exception when closing the file before download : " + - ex); - } - // nothing can really be done if the file can't be closed - } - } - - /* - * This loop is run once for every individual HTTP request that gets sent. - * The very first HTTP request is a "virgin" request, while every subsequent - * request is done with the original ETag and a byte-range. - */ -http_request_loop: - while (true) { - // Prepares the request and fires it. - HttpGet request = new HttpGet(mInfo.uri); - - if (Constants.LOGV) { - Log.v(Constants.TAG, "initiating download for " + mInfo.uri); - } - - if (mInfo.cookies != null) { - request.addHeader("Cookie", mInfo.cookies); - } - if (mInfo.referer != null) { - request.addHeader("Referer", mInfo.referer); - } - if (continuingDownload) { - if (headerETag != null) { - request.addHeader("If-Match", headerETag); - } - request.addHeader("Range", "bytes=" + bytesSoFar + "-"); - } - - HttpResponse response; - try { - response = client.execute(request); - } catch (IllegalArgumentException ex) { - if (Constants.LOGV) { - Log.d(Constants.TAG, "Arg exception trying to execute request for " + - mInfo.uri + " : " + ex); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "Arg exception trying to execute request for " + - mInfo.id + " : " + ex); - } - finalStatus = Downloads.STATUS_BAD_REQUEST; - request.abort(); - break http_request_loop; - } catch (IOException ex) { - if (!Helpers.isNetworkAvailable(mContext)) { - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - } else if (mInfo.numFailed < Constants.MAX_RETRIES) { - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - countRetry = true; - } else { - if (Constants.LOGV) { - Log.d(Constants.TAG, "IOException trying to execute request for " + - mInfo.uri + " : " + ex); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "IOException trying to execute request for " + - mInfo.id + " : " + ex); - } - finalStatus = Downloads.STATUS_HTTP_DATA_ERROR; - } - request.abort(); - break http_request_loop; - } - - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode == 503 && mInfo.numFailed < Constants.MAX_RETRIES) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "got HTTP response code 503"); - } - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - countRetry = true; - Header header = response.getFirstHeader("Retry-After"); - if (header != null) { - try { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Retry-After :" + header.getValue()); - } - retryAfter = Integer.parseInt(header.getValue()); - if (retryAfter < 0) { - retryAfter = 0; - } else { - if (retryAfter < Constants.MIN_RETRY_AFTER) { - retryAfter = Constants.MIN_RETRY_AFTER; - } else if (retryAfter > Constants.MAX_RETRY_AFTER) { - retryAfter = Constants.MAX_RETRY_AFTER; - } - retryAfter += Helpers.rnd.nextInt(Constants.MIN_RETRY_AFTER + 1); - retryAfter *= 1000; - } - } catch (NumberFormatException ex) { - // ignored - retryAfter stays 0 in this case. - } - } - request.abort(); - break http_request_loop; - } - if (statusCode == 301 || - statusCode == 302 || - statusCode == 303 || - statusCode == 307) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "got HTTP redirect " + statusCode); - } - if (redirectCount >= Constants.MAX_REDIRECTS) { - if (Constants.LOGV) { - Log.d(Constants.TAG, "too many redirects for download " + mInfo.id + - " at " + mInfo.uri); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "too many redirects for download " + mInfo.id); - } - finalStatus = Downloads.STATUS_TOO_MANY_REDIRECTS; - request.abort(); - break http_request_loop; - } - Header header = response.getFirstHeader("Location"); - if (header != null) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Location :" + header.getValue()); - } - newUri = new URI(mInfo.uri).resolve(new URI(header.getValue())).toString(); - ++redirectCount; - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - request.abort(); - break http_request_loop; - } - } - if ((!continuingDownload && statusCode != Downloads.STATUS_SUCCESS) - || (continuingDownload && statusCode != 206)) { - if (Constants.LOGV) { - Log.d(Constants.TAG, "http error " + statusCode + " for " + mInfo.uri); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "http error " + statusCode + " for download " + - mInfo.id); - } - if (Downloads.isStatusError(statusCode)) { - finalStatus = statusCode; - } else if (statusCode >= 300 && statusCode < 400) { - finalStatus = Downloads.STATUS_UNHANDLED_REDIRECT; - } else if (continuingDownload && statusCode == Downloads.STATUS_SUCCESS) { - finalStatus = Downloads.STATUS_PRECONDITION_FAILED; - } else { - finalStatus = Downloads.STATUS_UNHANDLED_HTTP_CODE; - } - request.abort(); - break http_request_loop; - } else { - // Handles the response, saves the file - if (Constants.LOGV) { - Log.v(Constants.TAG, "received response for " + mInfo.uri); - } - - if (!continuingDownload) { - Header header = response.getFirstHeader("Accept-Ranges"); - if (header != null) { - headerAcceptRanges = header.getValue(); - } - header = response.getFirstHeader("Content-Disposition"); - if (header != null) { - headerContentDisposition = header.getValue(); - } - header = response.getFirstHeader("Content-Location"); - if (header != null) { - headerContentLocation = header.getValue(); - } - if (mimeType == null) { - header = response.getFirstHeader("Content-Type"); - if (header != null) { - mimeType = header.getValue(); - final int semicolonIndex = mimeType.indexOf(';'); - if (semicolonIndex != -1) { - mimeType = mimeType.substring(0, semicolonIndex); - } - } - } - header = response.getFirstHeader("ETag"); - if (header != null) { - headerETag = header.getValue(); - } - header = response.getFirstHeader("Transfer-Encoding"); - if (header != null) { - headerTransferEncoding = header.getValue(); - } - if (headerTransferEncoding == null) { - header = response.getFirstHeader("Content-Length"); - if (header != null) { - headerContentLength = header.getValue(); - } - } else { - // Ignore content-length with transfer-encoding - 2616 4.4 3 - if (Constants.LOGVV) { - Log.v(Constants.TAG, - "ignoring content-length because of xfer-encoding"); - } - } - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Accept-Ranges: " + headerAcceptRanges); - Log.v(Constants.TAG, "Content-Disposition: " + - headerContentDisposition); - Log.v(Constants.TAG, "Content-Length: " + headerContentLength); - Log.v(Constants.TAG, "Content-Location: " + headerContentLocation); - Log.v(Constants.TAG, "Content-Type: " + mimeType); - Log.v(Constants.TAG, "ETag: " + headerETag); - Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding); - } - - if (!mInfo.noIntegrity && headerContentLength == null && - (headerTransferEncoding == null - || !headerTransferEncoding.equalsIgnoreCase("chunked")) - ) { - if (Config.LOGD) { - Log.d(Constants.TAG, "can't know size of download, giving up"); - } - finalStatus = Downloads.STATUS_LENGTH_REQUIRED; - request.abort(); - break http_request_loop; - } - - DownloadFileInfo fileInfo = Helpers.generateSaveFile( - mContext, - mInfo.uri, - mInfo.hint, - headerContentDisposition, - headerContentLocation, - mimeType, - mInfo.destination, - (headerContentLength != null) ? - Integer.parseInt(headerContentLength) : 0); - if (fileInfo.filename == null) { - finalStatus = fileInfo.status; - request.abort(); - break http_request_loop; - } - filename = fileInfo.filename; - stream = fileInfo.stream; - if (Constants.LOGV) { - Log.v(Constants.TAG, "writing " + mInfo.uri + " to " + filename); - } - - ContentValues values = new ContentValues(); - values.put(Downloads._DATA, filename); - if (headerETag != null) { - values.put(Constants.ETAG, headerETag); - } - if (mimeType != null) { - values.put(Downloads.MIMETYPE, mimeType); - } - int contentLength = -1; - if (headerContentLength != null) { - contentLength = Integer.parseInt(headerContentLength); - } - values.put(Downloads.TOTAL_BYTES, contentLength); - mContext.getContentResolver().update(contentUri, values, null, null); - } - - InputStream entityStream; - try { - entityStream = response.getEntity().getContent(); - } catch (IOException ex) { - if (!Helpers.isNetworkAvailable(mContext)) { - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - } else if (mInfo.numFailed < Constants.MAX_RETRIES) { - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - countRetry = true; - } else { - if (Constants.LOGV) { - Log.d(Constants.TAG, "IOException getting entity for " + mInfo.uri + - " : " + ex); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "IOException getting entity for download " + - mInfo.id + " : " + ex); - } - finalStatus = Downloads.STATUS_HTTP_DATA_ERROR; - } - request.abort(); - break http_request_loop; - } - for (;;) { - int bytesRead; - try { - bytesRead = entityStream.read(data); - } catch (IOException ex) { - ContentValues values = new ContentValues(); - values.put(Downloads.CURRENT_BYTES, bytesSoFar); - mContext.getContentResolver().update(contentUri, values, null, null); - if (!mInfo.noIntegrity && headerETag == null) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "download IOException for " + mInfo.uri + - " : " + ex); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "download IOException for download " + - mInfo.id + " : " + ex); - } - if (Config.LOGD) { - Log.d(Constants.TAG, - "can't resume interrupted download with no ETag"); - } - finalStatus = Downloads.STATUS_PRECONDITION_FAILED; - } else if (!Helpers.isNetworkAvailable(mContext)) { - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - } else if (mInfo.numFailed < Constants.MAX_RETRIES) { - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - countRetry = true; - } else { - if (Constants.LOGV) { - Log.v(Constants.TAG, "download IOException for " + mInfo.uri + - " : " + ex); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "download IOException for download " + - mInfo.id + " : " + ex); - } - finalStatus = Downloads.STATUS_HTTP_DATA_ERROR; - } - request.abort(); - break http_request_loop; - } - if (bytesRead == -1) { // success - ContentValues values = new ContentValues(); - values.put(Downloads.CURRENT_BYTES, bytesSoFar); - if (headerContentLength == null) { - values.put(Downloads.TOTAL_BYTES, bytesSoFar); - } - mContext.getContentResolver().update(contentUri, values, null, null); - if ((headerContentLength != null) - && (bytesSoFar - != Integer.parseInt(headerContentLength))) { - if (!mInfo.noIntegrity && headerETag == null) { - if (Constants.LOGV) { - Log.d(Constants.TAG, "mismatched content length " + - mInfo.uri); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "mismatched content length for " + - mInfo.id); - } - finalStatus = Downloads.STATUS_LENGTH_REQUIRED; - } else if (!Helpers.isNetworkAvailable(mContext)) { - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - } else if (mInfo.numFailed < Constants.MAX_RETRIES) { - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - countRetry = true; - } else { - if (Constants.LOGV) { - Log.v(Constants.TAG, "closed socket for " + mInfo.uri); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "closed socket for download " + - mInfo.id); - } - finalStatus = Downloads.STATUS_HTTP_DATA_ERROR; - } - break http_request_loop; - } - break; - } - gotData = true; - for (;;) { - try { - if (stream == null) { - stream = new FileOutputStream(filename, true); - } - stream.write(data, 0, bytesRead); - if (mInfo.destination == Downloads.DESTINATION_EXTERNAL - && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING - .equalsIgnoreCase(mimeType)) { - try { - stream.close(); - stream = null; - } catch (IOException ex) { - if (Constants.LOGV) { - Log.v(Constants.TAG, - "exception when closing the file " + - "during download : " + ex); - } - // nothing can really be done if the file can't be closed - } - } - break; - } catch (IOException ex) { - if (!Helpers.discardPurgeableFiles( - mContext, Constants.BUFFER_SIZE)) { - finalStatus = Downloads.STATUS_FILE_ERROR; - break http_request_loop; - } - } - } - bytesSoFar += bytesRead; - long now = System.currentTimeMillis(); - if (bytesSoFar - bytesNotified > Constants.MIN_PROGRESS_STEP - && now - timeLastNotification - > Constants.MIN_PROGRESS_TIME) { - ContentValues values = new ContentValues(); - values.put(Downloads.CURRENT_BYTES, bytesSoFar); - mContext.getContentResolver().update( - contentUri, values, null, null); - bytesNotified = bytesSoFar; - timeLastNotification = now; - } - - if (Constants.LOGVV) { - Log.v(Constants.TAG, "downloaded " + bytesSoFar + " for " + mInfo.uri); - } - synchronized(mInfo) { - if (mInfo.control == Downloads.CONTROL_PAUSED) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "paused " + mInfo.uri); - } - finalStatus = Downloads.STATUS_RUNNING_PAUSED; - request.abort(); - break http_request_loop; - } - } - if (mInfo.status == Downloads.STATUS_CANCELED) { - if (Constants.LOGV) { - Log.d(Constants.TAG, "canceled " + mInfo.uri); - } else if (Config.LOGD) { - // Log.d(Constants.TAG, "canceled id " + mInfo.id); - } - finalStatus = Downloads.STATUS_CANCELED; - break http_request_loop; - } - } - if (Constants.LOGV) { - Log.v(Constants.TAG, "download completed for " + mInfo.uri); - } - finalStatus = Downloads.STATUS_SUCCESS; - } - break; - } - } catch (FileNotFoundException ex) { - if (Config.LOGD) { - Log.d(Constants.TAG, "FileNotFoundException for " + filename + " : " + ex); - } - finalStatus = Downloads.STATUS_FILE_ERROR; - // falls through to the code that reports an error - } catch (Exception ex) { //sometimes the socket code throws unchecked exceptions - if (Constants.LOGV) { - Log.d(Constants.TAG, "Exception for " + mInfo.uri, ex); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "Exception for id " + mInfo.id, ex); - } - finalStatus = Downloads.STATUS_UNKNOWN_ERROR; - // falls through to the code that reports an error - } finally { - mInfo.hasActiveThread = false; - if (wakeLock != null) { - wakeLock.release(); - wakeLock = null; - } - if (client != null) { - client.close(); - client = null; - } - try { - // close the file - if (stream != null) { - stream.close(); - } - } catch (IOException ex) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "exception when closing the file after download : " + ex); - } - // nothing can really be done if the file can't be closed - } - if (filename != null) { - // if the download wasn't successful, delete the file - if (Downloads.isStatusError(finalStatus)) { - new File(filename).delete(); - filename = null; - } else if (Downloads.isStatusSuccess(finalStatus) && - DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING - .equalsIgnoreCase(mimeType)) { - // transfer the file to the DRM content provider - File file = new File(filename); - Intent item = DrmStore.addDrmFile(mContext.getContentResolver(), file, null); - if (item == null) { - Log.w(Constants.TAG, "unable to add file " + filename + " to DrmProvider"); - finalStatus = Downloads.STATUS_UNKNOWN_ERROR; - } else { - filename = item.getDataString(); - mimeType = item.getType(); - } - - file.delete(); - } else if (Downloads.isStatusSuccess(finalStatus)) { - // make sure the file is readable - FileUtils.setPermissions(filename, 0644, -1, -1); - } - } - notifyDownloadCompleted(finalStatus, countRetry, retryAfter, redirectCount, - gotData, filename, newUri, mimeType); - } - } - - /** - * Stores information about the completed download, and notifies the initiating application. - */ - private void notifyDownloadCompleted( - int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData, - String filename, String uri, String mimeType) { - notifyThroughDatabase( - status, countRetry, retryAfter, redirectCount, gotData, filename, uri, mimeType); - if (Downloads.isStatusCompleted(status)) { - notifyThroughIntent(); - } - } - - private void notifyThroughDatabase( - int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData, - String filename, String uri, String mimeType) { - ContentValues values = new ContentValues(); - values.put(Downloads.STATUS, status); - values.put(Downloads._DATA, filename); - if (uri != null) { - values.put(Downloads.URI, uri); - } - values.put(Downloads.MIMETYPE, mimeType); - values.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis()); - values.put(Constants.RETRY_AFTER___REDIRECT_COUNT, retryAfter + (redirectCount << 28)); - if (!countRetry) { - values.put(Constants.FAILED_CONNECTIONS, 0); - } else if (gotData) { - values.put(Constants.FAILED_CONNECTIONS, 1); - } else { - values.put(Constants.FAILED_CONNECTIONS, mInfo.numFailed + 1); - } - - mContext.getContentResolver().update( - ContentUris.withAppendedId(Downloads.CONTENT_URI, mInfo.id), values, null, null); - } - - /** - * Notifies the initiating app if it requested it. That way, it can know that the - * download completed even if it's not actively watching the cursor. - */ - private void notifyThroughIntent() { - Uri uri = Uri.parse(Downloads.CONTENT_URI + "/" + mInfo.id); - mInfo.sendIntentIfRequested(uri, mContext); - } - -} diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java deleted file mode 100644 index 7c6070f3..00000000 --- a/src/com/android/providers/downloads/Helpers.java +++ /dev/null @@ -1,793 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.providers.downloads; - -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.database.Cursor; -import android.drm.mobile1.DrmRawContent; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.Uri; -import android.os.Environment; -import android.os.StatFs; -import android.os.SystemClock; -import android.provider.Downloads; -import android.telephony.TelephonyManager; -import android.util.Config; -import android.util.Log; -import android.webkit.MimeTypeMap; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.util.List; -import java.util.Random; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.Set; - -/** - * Some helper functions for the download manager - */ -public class Helpers { - - public static Random rnd = new Random(SystemClock.uptimeMillis()); - - /** Regex used to parse content-disposition headers */ - private static final Pattern CONTENT_DISPOSITION_PATTERN = - Pattern.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""); - - private Helpers() { - } - - /* - * Parse the Content-Disposition HTTP Header. The format of the header - * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html - * This header provides a filename for content that is going to be - * downloaded to the file system. We only support the attachment type. - */ - private static String parseContentDisposition(String contentDisposition) { - try { - Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); - if (m.find()) { - return m.group(1); - } - } catch (IllegalStateException ex) { - // This function is defined as returning null when it can't parse the header - } - return null; - } - - /** - * Creates a filename (where the file should be saved) from a uri. - */ - public static DownloadFileInfo generateSaveFile( - Context context, - String url, - String hint, - String contentDisposition, - String contentLocation, - String mimeType, - int destination, - int contentLength) throws FileNotFoundException { - - /* - * Don't download files that we won't be able to handle - */ - if (destination == Downloads.DESTINATION_EXTERNAL - || destination == Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE) { - if (mimeType == null) { - if (Config.LOGD) { - Log.d(Constants.TAG, "external download with no mime type not allowed"); - } - return new DownloadFileInfo(null, null, Downloads.STATUS_NOT_ACCEPTABLE); - } - if (!DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(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 intent = 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 - - PackageManager pm = context.getPackageManager(); - intent.setDataAndType(Uri.fromParts("file", "", null), mimeType); - ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); - //Log.i(Constants.TAG, "*** FILENAME QUERY " + intent + ": " + list); - - if (ri == null) { - if (Config.LOGD) { - Log.d(Constants.TAG, "no handler found for type " + mimeType); - } - return new DownloadFileInfo(null, null, Downloads.STATUS_NOT_ACCEPTABLE); - } - } - } - String filename = chooseFilename( - url, hint, contentDisposition, contentLocation, destination); - - // Split filename between base and extension - // Add an extension if filename does not have one - String extension = null; - int dotIndex = filename.indexOf('.'); - if (dotIndex < 0) { - extension = chooseExtensionFromMimeType(mimeType, true); - } else { - extension = chooseExtensionFromFilename( - mimeType, destination, filename, dotIndex); - filename = filename.substring(0, dotIndex); - } - - /* - * Locate the directory where the file will be saved - */ - - File base = null; - StatFs stat = null; - // DRM messages should be temporarily stored internally and then passed to - // the DRM content provider - if (destination == Downloads.DESTINATION_CACHE_PARTITION - || destination == Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE - || destination == Downloads.DESTINATION_CACHE_PARTITION_NOROAMING - || DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(mimeType)) { - base = Environment.getDownloadCacheDirectory(); - stat = new StatFs(base.getPath()); - - /* - * Check whether there's enough space on the target filesystem to save the file. - * Put a bit of margin (in case creating the file grows the system by a few blocks). - */ - int blockSize = stat.getBlockSize(); - for (;;) { - int availableBlocks = stat.getAvailableBlocks(); - if (blockSize * ((long) availableBlocks - 4) >= contentLength) { - break; - } - if (!discardPurgeableFiles(context, - contentLength - blockSize * ((long) availableBlocks - 4))) { - if (Config.LOGD) { - Log.d(Constants.TAG, - "download aborted - not enough free space in internal storage"); - } - return new DownloadFileInfo(null, null, Downloads.STATUS_FILE_ERROR); - } - stat.restat(base.getPath()); - } - - } else { - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - String root = Environment.getExternalStorageDirectory().getPath(); - base = new File(root + Constants.DEFAULT_DL_SUBDIR); - if (!base.isDirectory() && !base.mkdir()) { - if (Config.LOGD) { - Log.d(Constants.TAG, "download aborted - can't create base directory " - + base.getPath()); - } - return new DownloadFileInfo(null, null, Downloads.STATUS_FILE_ERROR); - } - stat = new StatFs(base.getPath()); - } else { - if (Config.LOGD) { - Log.d(Constants.TAG, "download aborted - no external storage"); - } - return new DownloadFileInfo(null, null, Downloads.STATUS_FILE_ERROR); - } - - /* - * Check whether there's enough space on the target filesystem to save the file. - * Put a bit of margin (in case creating the file grows the system by a few blocks). - */ - if (stat.getBlockSize() * ((long) stat.getAvailableBlocks() - 4) < contentLength) { - if (Config.LOGD) { - Log.d(Constants.TAG, "download aborted - not enough free space"); - } - return new DownloadFileInfo(null, null, Downloads.STATUS_FILE_ERROR); - } - - } - - boolean recoveryDir = Constants.RECOVERY_DIRECTORY.equalsIgnoreCase(filename + extension); - - filename = base.getPath() + File.separator + filename; - - /* - * Generate a unique filename, create the file, return it. - */ - if (Constants.LOGVV) { - Log.v(Constants.TAG, "target file: " + filename + extension); - } - - String fullFilename = chooseUniqueFilename( - destination, filename, extension, recoveryDir); - if (fullFilename != null) { - return new DownloadFileInfo(fullFilename, new FileOutputStream(fullFilename), 0); - } else { - return new DownloadFileInfo(null, null, Downloads.STATUS_FILE_ERROR); - } - } - - private static String chooseFilename(String url, String hint, String contentDisposition, - String contentLocation, int destination) { - String filename = null; - - // First, try to use the hint from the application, if there's one - if (filename == null && hint != null && !hint.endsWith("/")) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "getting filename from hint"); - } - int index = hint.lastIndexOf('/') + 1; - if (index > 0) { - filename = hint.substring(index); - } else { - filename = hint; - } - } - - // If we couldn't do anything with the hint, move toward the content disposition - if (filename == null && contentDisposition != null) { - filename = parseContentDisposition(contentDisposition); - if (filename != null) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "getting filename from content-disposition"); - } - int index = filename.lastIndexOf('/') + 1; - if (index > 0) { - filename = filename.substring(index); - } - } - } - - // If we still have nothing at this point, try the content location - if (filename == null && contentLocation != null) { - String decodedContentLocation = Uri.decode(contentLocation); - if (decodedContentLocation != null - && !decodedContentLocation.endsWith("/") - && decodedContentLocation.indexOf('?') < 0) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "getting filename from content-location"); - } - int index = decodedContentLocation.lastIndexOf('/') + 1; - if (index > 0) { - filename = decodedContentLocation.substring(index); - } else { - filename = decodedContentLocation; - } - } - } - - // If all the other http-related approaches failed, use the plain uri - if (filename == null) { - String decodedUrl = Uri.decode(url); - if (decodedUrl != null - && !decodedUrl.endsWith("/") && decodedUrl.indexOf('?') < 0) { - int index = decodedUrl.lastIndexOf('/') + 1; - if (index > 0) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "getting filename from uri"); - } - filename = decodedUrl.substring(index); - } - } - } - - // Finally, if couldn't get filename from URI, get a generic filename - if (filename == null) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "using default filename"); - } - filename = Constants.DEFAULT_DL_FILENAME; - } - - filename = filename.replaceAll("[^a-zA-Z0-9\\.\\-_]+", "_"); - - - return filename; - } - - private static String chooseExtensionFromMimeType(String mimeType, boolean useDefaults) { - String extension = null; - if (mimeType != null) { - extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); - if (extension != null) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "adding extension from type"); - } - extension = "." + extension; - } else { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "couldn't find extension for " + mimeType); - } - } - } - if (extension == null) { - if (mimeType != null && mimeType.toLowerCase().startsWith("text/")) { - if (mimeType.equalsIgnoreCase("text/html")) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "adding default html extension"); - } - extension = Constants.DEFAULT_DL_HTML_EXTENSION; - } else if (useDefaults) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "adding default text extension"); - } - extension = Constants.DEFAULT_DL_TEXT_EXTENSION; - } - } else if (useDefaults) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "adding default binary extension"); - } - extension = Constants.DEFAULT_DL_BINARY_EXTENSION; - } - } - return extension; - } - - private static String chooseExtensionFromFilename(String mimeType, int destination, - String filename, int dotIndex) { - String extension = null; - if (mimeType != null) { - // Compare the last segment of the extension against the mime type. - // If there's a mismatch, discard the entire extension. - int lastDotIndex = filename.lastIndexOf('.'); - String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension( - filename.substring(lastDotIndex + 1)); - if (typeFromExt == null || !typeFromExt.equalsIgnoreCase(mimeType)) { - extension = chooseExtensionFromMimeType(mimeType, false); - if (extension != null) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "substituting extension from type"); - } - } else { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "couldn't find extension for " + mimeType); - } - } - } - } - if (extension == null) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "keeping extension"); - } - extension = filename.substring(dotIndex); - } - return extension; - } - - private static String chooseUniqueFilename(int destination, String filename, - String extension, boolean recoveryDir) { - String fullFilename = filename + extension; - if (!new File(fullFilename).exists() - && (!recoveryDir || - (destination != Downloads.DESTINATION_CACHE_PARTITION && - destination != Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE && - destination != Downloads.DESTINATION_CACHE_PARTITION_NOROAMING))) { - return fullFilename; - } - filename = filename + Constants.FILENAME_SEQUENCE_SEPARATOR; - /* - * This number is used to generate partially randomized filenames to avoid - * collisions. - * It starts at 1. - * The next 9 iterations increment it by 1 at a time (up to 10). - * The next 9 iterations increment it by 1 to 10 (random) at a time. - * The next 9 iterations increment it by 1 to 100 (random) at a time. - * ... Up to the point where it increases by 100000000 at a time. - * (the maximum value that can be reached is 1000000000) - * As soon as a number is reached that generates a filename that doesn't exist, - * that filename is used. - * If the filename coming in is [base].[ext], the generated filenames are - * [base]-[sequence].[ext]. - */ - int sequence = 1; - for (int magnitude = 1; magnitude < 1000000000; magnitude *= 10) { - for (int iteration = 0; iteration < 9; ++iteration) { - fullFilename = filename + sequence + extension; - if (!new File(fullFilename).exists()) { - return fullFilename; - } - if (Constants.LOGVV) { - Log.v(Constants.TAG, "file with sequence number " + sequence + " exists"); - } - sequence += rnd.nextInt(magnitude) + 1; - } - } - return null; - } - - /** - * Deletes purgeable files from the cache partition. This also deletes - * the matching database entries. Files are deleted in LRU order until - * the total byte size is greater than targetBytes. - */ - public static final boolean discardPurgeableFiles(Context context, long targetBytes) { - Cursor cursor = context.getContentResolver().query( - Downloads.CONTENT_URI, - null, - "( " + - Downloads.STATUS + " = " + Downloads.STATUS_SUCCESS + " AND " + - Downloads.DESTINATION + " = " + Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE - + " )", - null, - Downloads.LAST_MODIFICATION); - if (cursor == null) { - return false; - } - long totalFreed = 0; - try { - cursor.moveToFirst(); - while (!cursor.isAfterLast() && totalFreed < targetBytes) { - File file = new File(cursor.getString(cursor.getColumnIndex(Downloads._DATA))); - if (Constants.LOGVV) { - Log.v(Constants.TAG, "purging " + file.getAbsolutePath() + " for " + - file.length() + " bytes"); - } - totalFreed += file.length(); - file.delete(); - long id = cursor.getLong(cursor.getColumnIndex(Downloads._ID)); - context.getContentResolver().delete( - ContentUris.withAppendedId(Downloads.CONTENT_URI, id), null, null); - cursor.moveToNext(); - } - } finally { - cursor.close(); - } - if (Constants.LOGV) { - if (totalFreed > 0) { - Log.v(Constants.TAG, "Purged files, freed " + totalFreed + " for " + - targetBytes + " requested"); - } - } - return totalFreed > 0; - } - - /** - * Returns whether the network is available - */ - public static boolean isNetworkAvailable(Context context) { - ConnectivityManager connectivity = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity == null) { - Log.w(Constants.TAG, "couldn't get connectivity manager"); - } else { - NetworkInfo[] info = connectivity.getAllNetworkInfo(); - if (info != null) { - for (int i = 0; i < info.length; i++) { - if (info[i].getState() == NetworkInfo.State.CONNECTED) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "network is available"); - } - return true; - } - } - } - } - if (Constants.LOGVV) { - Log.v(Constants.TAG, "network is not available"); - } - return false; - } - - /** - * Returns whether the network is roaming - */ - public static boolean isNetworkRoaming(Context context) { - ConnectivityManager connectivity = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity == null) { - Log.w(Constants.TAG, "couldn't get connectivity manager"); - } else { - NetworkInfo info = connectivity.getActiveNetworkInfo(); - if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE) { - if (TelephonyManager.getDefault().isNetworkRoaming()) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "network is roaming"); - } - return true; - } else { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "network is not roaming"); - } - } - } else { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "not using mobile network"); - } - } - } - return false; - } - - /** - * Checks whether the filename looks legitimate - */ - public static boolean isFilenameValid(String filename) { - File dir = new File(filename).getParentFile(); - return dir.equals(Environment.getDownloadCacheDirectory()) - || dir.equals(new File(Environment.getExternalStorageDirectory() - + Constants.DEFAULT_DL_SUBDIR)); - } - - /** - * Checks whether this looks like a legitimate selection parameter - */ - public static void validateSelection(String selection, Set allowedColumns) { - try { - if (selection == null) { - return; - } - Lexer lexer = new Lexer(selection, allowedColumns); - parseExpression(lexer); - if (lexer.currentToken() != Lexer.TOKEN_END) { - throw new IllegalArgumentException("syntax error"); - } - } catch (RuntimeException ex) { - if (Constants.LOGV) { - Log.d(Constants.TAG, "invalid selection [" + selection + "] triggered " + ex); - } else if (Config.LOGD) { - Log.d(Constants.TAG, "invalid selection triggered " + ex); - } - throw ex; - } - - } - - // expression <- ( expression ) | statement [AND_OR ( expression ) | statement] * - // | statement [AND_OR expression]* - private static void parseExpression(Lexer lexer) { - for (;;) { - // ( expression ) - if (lexer.currentToken() == Lexer.TOKEN_OPEN_PAREN) { - lexer.advance(); - parseExpression(lexer); - if (lexer.currentToken() != Lexer.TOKEN_CLOSE_PAREN) { - throw new IllegalArgumentException("syntax error, unmatched parenthese"); - } - lexer.advance(); - } else { - // statement - parseStatement(lexer); - } - if (lexer.currentToken() != Lexer.TOKEN_AND_OR) { - break; - } - lexer.advance(); - } - } - - // statement <- COLUMN COMPARE VALUE - // | COLUMN IS NULL - private static void parseStatement(Lexer lexer) { - // both possibilities start with COLUMN - if (lexer.currentToken() != Lexer.TOKEN_COLUMN) { - throw new IllegalArgumentException("syntax error, expected column name"); - } - lexer.advance(); - - // statement <- COLUMN COMPARE VALUE - if (lexer.currentToken() == Lexer.TOKEN_COMPARE) { - lexer.advance(); - if (lexer.currentToken() != Lexer.TOKEN_VALUE) { - throw new IllegalArgumentException("syntax error, expected quoted string"); - } - lexer.advance(); - return; - } - - // statement <- COLUMN IS NULL - if (lexer.currentToken() == Lexer.TOKEN_IS) { - lexer.advance(); - if (lexer.currentToken() != Lexer.TOKEN_NULL) { - throw new IllegalArgumentException("syntax error, expected NULL"); - } - lexer.advance(); - return; - } - - // didn't get anything good after COLUMN - throw new IllegalArgumentException("syntax error after column name"); - } - - /** - * A simple lexer that recognizes the words of our restricted subset of SQL where clauses - */ - private static class Lexer { - public static final int TOKEN_START = 0; - public static final int TOKEN_OPEN_PAREN = 1; - public static final int TOKEN_CLOSE_PAREN = 2; - public static final int TOKEN_AND_OR = 3; - public static final int TOKEN_COLUMN = 4; - public static final int TOKEN_COMPARE = 5; - public static final int TOKEN_VALUE = 6; - public static final int TOKEN_IS = 7; - public static final int TOKEN_NULL = 8; - public static final int TOKEN_END = 9; - - private final String mSelection; - private final Set mAllowedColumns; - private int mOffset = 0; - private int mCurrentToken = TOKEN_START; - private final char[] mChars; - - public Lexer(String selection, Set allowedColumns) { - mSelection = selection; - mAllowedColumns = allowedColumns; - mChars = new char[mSelection.length()]; - mSelection.getChars(0, mChars.length, mChars, 0); - advance(); - } - - public int currentToken() { - return mCurrentToken; - } - - public void advance() { - char[] chars = mChars; - - // consume whitespace - while (mOffset < chars.length && chars[mOffset] == ' ') { - ++mOffset; - } - - // end of input - if (mOffset == chars.length) { - mCurrentToken = TOKEN_END; - return; - } - - // "(" - if (chars[mOffset] == '(') { - ++mOffset; - mCurrentToken = TOKEN_OPEN_PAREN; - return; - } - - // ")" - if (chars[mOffset] == ')') { - ++mOffset; - mCurrentToken = TOKEN_CLOSE_PAREN; - return; - } - - // "?" - if (chars[mOffset] == '?') { - ++mOffset; - mCurrentToken = TOKEN_VALUE; - return; - } - - // "=" and "==" - if (chars[mOffset] == '=') { - ++mOffset; - mCurrentToken = TOKEN_COMPARE; - if (mOffset < chars.length && chars[mOffset] == '=') { - ++mOffset; - } - return; - } - - // ">" and ">=" - if (chars[mOffset] == '>') { - ++mOffset; - mCurrentToken = TOKEN_COMPARE; - if (mOffset < chars.length && chars[mOffset] == '=') { - ++mOffset; - } - return; - } - - // "<", "<=" and "<>" - if (chars[mOffset] == '<') { - ++mOffset; - mCurrentToken = TOKEN_COMPARE; - if (mOffset < chars.length && (chars[mOffset] == '=' || chars[mOffset] == '>')) { - ++mOffset; - } - return; - } - - // "!=" - if (chars[mOffset] == '!') { - ++mOffset; - mCurrentToken = TOKEN_COMPARE; - if (mOffset < chars.length && chars[mOffset] == '=') { - ++mOffset; - return; - } - throw new IllegalArgumentException("Unexpected character after !"); - } - - // columns and keywords - // first look for anything that looks like an identifier or a keyword - // and then recognize the individual words. - // no attempt is made at discarding sequences of underscores with no alphanumeric - // characters, even though it's not clear that they'd be legal column names. - if (isIdentifierStart(chars[mOffset])) { - int startOffset = mOffset; - ++mOffset; - while (mOffset < chars.length && isIdentifierChar(chars[mOffset])) { - ++mOffset; - } - String word = mSelection.substring(startOffset, mOffset); - if (mOffset - startOffset <= 4) { - if (word.equals("IS")) { - mCurrentToken = TOKEN_IS; - return; - } - if (word.equals("OR") || word.equals("AND")) { - mCurrentToken = TOKEN_AND_OR; - return; - } - if (word.equals("NULL")) { - mCurrentToken = TOKEN_NULL; - return; - } - } - if (mAllowedColumns.contains(word)) { - mCurrentToken = TOKEN_COLUMN; - return; - } - throw new IllegalArgumentException("unrecognized column or keyword"); - } - - // quoted strings - if (chars[mOffset] == '\'') { - ++mOffset; - while(mOffset < chars.length) { - if (chars[mOffset] == '\'') { - if (mOffset + 1 < chars.length && chars[mOffset + 1] == '\'') { - ++mOffset; - } else { - break; - } - } - ++mOffset; - } - if (mOffset == chars.length) { - throw new IllegalArgumentException("unterminated string"); - } - ++mOffset; - mCurrentToken = TOKEN_VALUE; - return; - } - - // anything we don't recognize - throw new IllegalArgumentException("illegal character"); - } - - private static final boolean isIdentifierStart(char c) { - return c == '_' || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z'); - } - - private static final boolean isIdentifierChar(char c) { - return c == '_' || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9'); - } - } -} -- cgit v1.2.3