summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/providers/downloads/Constants.java151
-rw-r--r--src/com/android/providers/downloads/DownloadFileInfo.java34
-rw-r--r--src/com/android/providers/downloads/DownloadInfo.java212
-rw-r--r--src/com/android/providers/downloads/DownloadNotification.java300
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java731
-rw-r--r--src/com/android/providers/downloads/DownloadReceiver.java159
-rw-r--r--src/com/android/providers/downloads/DownloadService.java879
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java710
-rw-r--r--src/com/android/providers/downloads/Helpers.java793
9 files changed, 0 insertions, 3969 deletions
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 <String, NotificationItem> 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<String, NotificationItem>();
- }
-
- /*
- * 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<String> sAppReadableColumnsSet;
- static {
- sAppReadableColumnsSet = new HashSet<String>();
- 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<DownloadInfo> 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<String> fileSet = new HashSet();
- for (int i = 0; i < files.length; i++) {
- if (files[i].getName().equals(Constants.KNOWN_SPURIOUS_FILENAME)) {
- continue;
- }
- if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) {
- continue;
- }
- fileSet.add(files[i].getPath());
- }
-
- Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
- new String[] { Downloads._DATA }, null, null, null);
- if (cursor != null) {
- if (cursor.moveToFirst()) {
- do {
- fileSet.remove(cursor.getString(0));
- } while (cursor.moveToNext());
- }
- cursor.close();
- }
- Iterator<String> iterator = fileSet.iterator();
- while (iterator.hasNext()) {
- String filename = iterator.next();
- if (Constants.LOGV) {
- Log.v(Constants.TAG, "deleting spurious file " + filename);
- }
- new File(filename).delete();
- }
- }
-
- /**
- * 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<String> 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<String> mAllowedColumns;
- private int mOffset = 0;
- private int mCurrentToken = TOKEN_START;
- private final char[] mChars;
-
- public Lexer(String selection, Set<String> 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');
- }
- }
-}