summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/mipmap-hdpi/ic_launcher_download.pngbin10593 -> 9282 bytes
-rw-r--r--res/mipmap-mdpi/ic_launcher_download.pngbin23509 -> 5420 bytes
-rw-r--r--res/mipmap-xhdpi/ic_launcher_download.pngbin32416 -> 13962 bytes
-rw-r--r--res/mipmap-xxhdpi/ic_launcher_download.pngbin21228 -> 40279 bytes
-rw-r--r--res/mipmap-xxxhdpi/ic_launcher_download.pngbin0 -> 35468 bytes
-rw-r--r--res/values-am/strings.xml2
-rw-r--r--res/values-ar/strings.xml4
-rw-r--r--res/values-iw/strings.xml14
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java89
-rw-r--r--src/com/android/providers/downloads/DownloadStorageProvider.java100
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java2
-rw-r--r--src/com/android/providers/downloads/Helpers.java26
-rw-r--r--src/com/android/providers/downloads/OpenHelper.java4
-rw-r--r--ui/res/mipmap-hdpi/ic_launcher_download.pngbin10593 -> 9282 bytes
-rw-r--r--ui/res/mipmap-mdpi/ic_launcher_download.pngbin23509 -> 5420 bytes
-rw-r--r--ui/res/mipmap-xhdpi/ic_launcher_download.pngbin32416 -> 13962 bytes
-rw-r--r--ui/res/mipmap-xxhdpi/ic_launcher_download.pngbin21228 -> 40279 bytes
-rw-r--r--ui/res/mipmap-xxxhdpi/ic_launcher_download.pngbin0 -> 35468 bytes
-rw-r--r--ui/res/values-am/strings.xml2
19 files changed, 180 insertions, 63 deletions
diff --git a/res/mipmap-hdpi/ic_launcher_download.png b/res/mipmap-hdpi/ic_launcher_download.png
index 3f092d39..2450cfbb 100644
--- a/res/mipmap-hdpi/ic_launcher_download.png
+++ b/res/mipmap-hdpi/ic_launcher_download.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_download.png b/res/mipmap-mdpi/ic_launcher_download.png
index 76652fb9..7b56ada3 100644
--- a/res/mipmap-mdpi/ic_launcher_download.png
+++ b/res/mipmap-mdpi/ic_launcher_download.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_download.png b/res/mipmap-xhdpi/ic_launcher_download.png
index 7d7b1b11..775004f2 100644
--- a/res/mipmap-xhdpi/ic_launcher_download.png
+++ b/res/mipmap-xhdpi/ic_launcher_download.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_download.png b/res/mipmap-xxhdpi/ic_launcher_download.png
index 0921c124..ed8c7827 100644
--- a/res/mipmap-xxhdpi/ic_launcher_download.png
+++ b/res/mipmap-xxhdpi/ic_launcher_download.png
Binary files differ
diff --git a/res/mipmap-xxxhdpi/ic_launcher_download.png b/res/mipmap-xxxhdpi/ic_launcher_download.png
new file mode 100644
index 00000000..2dbe0f2d
--- /dev/null
+++ b/res/mipmap-xxxhdpi/ic_launcher_download.png
Binary files differ
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index fd5a94f9..26947fd1 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -42,7 +42,7 @@
<string name="wifi_recommended_title" msgid="7441589306734687400">"ለኋላ አውርድ ወረፋ?"</string>
<string name="wifi_recommended_body" msgid="1314735166699936073">"ይህን <xliff:g id="SIZE">%s </xliff:g> አውርድ አሁን መጀመር የባትሪህን ዕድሜ ሊያሳጥረው ይችል ይሆናል እና/ወይም የአንተን ተንቀሳቃሽ ውሂብ ግንኙነት ከልክ ያለፈ አጠቃቀም ሊያስከስት ይችል ይሆናል፣ ይህም የአንተ ተንቀሳቃሽ ከዋኝ በአንተ ውሂብ ክፍያ ዕቅድ ስምምነት መሰረት ተጨማሪ ክፍያ እንድትከፍል ሊጠይቅህ ይዳርገው ይሆናል፡፡ ይህን አውርድ በሚቀጥለው ጊዜ ከWi-Fi አውታረመረብ ጋር ስትገናኝ ለመጀመር \n\n ንካ<xliff:g id="QUEUE_TEXT">%s</xliff:g>፡፡"</string>
<string name="button_queue_for_wifi" msgid="422576726189179221">"ወረፋ"</string>
- <string name="button_cancel_download" msgid="2430166148737975604">"ይቅር"</string>
+ <string name="button_cancel_download" msgid="2430166148737975604">"ሰርዝ"</string>
<string name="button_start_now" msgid="792123674007840864">"አሁን ጀምር"</string>
<string name="download_percent" msgid="6889426633242976698">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<plurals name="notif_summary_active">
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index dd6694b7..4d3325d1 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -21,8 +21,8 @@
<string name="permdesc_downloadManager" msgid="4237406545998908947">"للسماح للتطبيق بالدخول إلى إدارة التنزيل واستخدامها لتنزيل الملفات. يمكن للتطبيقات الضارة استخدام هذا لتعطيل التنزيلات والدخول إلى المعلومات الخاصة."</string>
<string name="permlab_downloadManagerAdvanced" msgid="7103642833308809655">"وظائف إدارة التنزيل المتقدمة."</string>
<string name="permdesc_downloadManagerAdvanced" msgid="2659546004160962761">"للسماح للتطبيق بالدخول إلى وظائف إدارة التنزيل المتقدمة. يمكن للتطبيقات الضارة استخدام هذا لتعطيل التنزيلات والدخول إلى المعلومات الخاصة."</string>
- <string name="permlab_downloadCompletedIntent" msgid="945913803765675685">"إرسال تنبيهات بالتنزيل."</string>
- <string name="permdesc_downloadCompletedIntent" msgid="2094706189855699533">"للسماح للتطبيق بإرسال تنبيهات عن التنزيلات المكتملة. يمكن للتطبيقات الضارة استخدام هذا لإرباك التطبيقات الأخرى التي تنزل الملفات."</string>
+ <string name="permlab_downloadCompletedIntent" msgid="945913803765675685">"إرسال اشعارات بالتنزيل."</string>
+ <string name="permdesc_downloadCompletedIntent" msgid="2094706189855699533">"للسماح للتطبيق بإرسال اشعارات عن التنزيلات المكتملة. يمكن للتطبيقات الضارة استخدام هذا لإرباك التطبيقات الأخرى التي تنزل الملفات."</string>
<string name="permlab_seeAllExternal" product="nosdcard" msgid="4084575448409212628">"عرض جميع التنزيلات إلى وحدة تخزين USB"</string>
<string name="permlab_seeAllExternal" product="default" msgid="140058400609165726">"مشاهدة جميع التنزيلات على بطاقة SD"</string>
<string name="permdesc_seeAllExternal" msgid="1672759909065511233">"للسماح للتطبيق بمشاهدة جميع التنزيلات على بطاقة SD، بغض النظر عن التطبيق الذي نزلها."</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ffaa5ea3..5eae498b 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -18,20 +18,20 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3658948994665187911">"מנהל ההורדות"</string>
<string name="permlab_downloadManager" msgid="7779544811202855500">"גישה למנהל ההורדות."</string>
- <string name="permdesc_downloadManager" msgid="4237406545998908947">"מאפשר ליישום לגשת למנהל ההורדות ולהשתמש בו לצורך הורדת קבצים. יישומים זדוניים עלולים לנצל אפשרות זו כדי לשבש הורדות ולגשת למידע פרטי."</string>
+ <string name="permdesc_downloadManager" msgid="4237406545998908947">"מאפשר לאפליקציה לגשת למנהל ההורדות ולהשתמש בו לצורך הורדת קבצים. אפליקציות זדוניות עלולות לנצל אפשרות זו כדי לשבש הורדות ולגשת למידע פרטי."</string>
<string name="permlab_downloadManagerAdvanced" msgid="7103642833308809655">"פונקציות מתקדמות של מנהל ההורדות."</string>
- <string name="permdesc_downloadManagerAdvanced" msgid="2659546004160962761">"מאפשר ליישום לגשת לפונקציות המתקדמות של מנהל ההורדות. יישומים זדוניים עלולים לנצל אפשרות זו כדי לשבש הורדות ולגשת למידע פרטי."</string>
+ <string name="permdesc_downloadManagerAdvanced" msgid="2659546004160962761">"מאפשר לאפליקציה לגשת לפונקציות המתקדמות של מנהל ההורדות. אפליקציות זדוניות עלולות לנצל אפשרות זו כדי לשבש הורדות ולגשת למידע פרטי."</string>
<string name="permlab_downloadCompletedIntent" msgid="945913803765675685">"שלח התראות על הורדות."</string>
- <string name="permdesc_downloadCompletedIntent" msgid="2094706189855699533">"מאפשר ליישום לשלוח התראות לגבי הורדות שהסתיימו. יישומים זדוניים עלולים לנצל אפשרות זו כדי לבלבל יישומים אחרים המורידים קבצים."</string>
+ <string name="permdesc_downloadCompletedIntent" msgid="2094706189855699533">"מאפשר לאפליקציה לשלוח התראות לגבי הורדות שהסתיימו. אפליקציות זדוניות עלולות לנצל אפשרות זו כדי לבלבל אפליקציות אחרות המורידות קבצים."</string>
<string name="permlab_seeAllExternal" product="nosdcard" msgid="4084575448409212628">"הצג את כל ההורדות לאמצעי אחסון מסוג USB"</string>
<string name="permlab_seeAllExternal" product="default" msgid="140058400609165726">"הצג את כל ההורדות לכרטיס SD"</string>
- <string name="permdesc_seeAllExternal" msgid="1672759909065511233">"מאפשר ליישום לראות את כל ההורדות לכרטיס ה-SD, בלי קשר ליישום שהוריד אותן."</string>
+ <string name="permdesc_seeAllExternal" msgid="1672759909065511233">"מאפשר לאפליקציה לראות את כל ההורדות לכרטיס ה-SD, בלי קשר לאפליקציה שהורידה אותן."</string>
<string name="permlab_downloadCacheNonPurgeable" msgid="3069534308882047412">"שמור מקום בקובץ השמור של ההורדות"</string>
- <string name="permdesc_downloadCacheNonPurgeable" msgid="2408760720334570420">"מאפשר ליישום להוריד קבצים לקובץ השמור של הורדות שלא ניתן למחוק באופן אוטומטי כאשר דרוש למנהל ההורדות שטח נוסף."</string>
+ <string name="permdesc_downloadCacheNonPurgeable" msgid="2408760720334570420">"מאפשר לאפליקציה להוריד קבצים לקובץ השמור של הורדות שלא ניתן למחוק באופן אוטומטי כאשר דרוש למנהל ההורדות שטח נוסף."</string>
<string name="permlab_downloadWithoutNotification" msgid="8837971946078327262">"הורד קבצים ללא התראה"</string>
- <string name="permdesc_downloadWithoutNotification" msgid="8483135034298639727">"מאפשר ליישום להוריד קבצים דרך מנהל ההורדות מבלי להציג התראות כלשהן בפני המשתמש."</string>
+ <string name="permdesc_downloadWithoutNotification" msgid="8483135034298639727">"מאפשר לאפליקציה להוריד קבצים דרך מנהל ההורדות מבלי להציג התראות כלשהן בפני המשתמש."</string>
<string name="permlab_accessAllDownloads" msgid="2436240495424393717">"גישה לכל הורדות המערכת"</string>
- <string name="permdesc_accessAllDownloads" msgid="1871832254578267128">"מאפשר ליישום להציג ולשנות את כל ההורדות שהופעלו על ידי יישום כלשהו במערכת."</string>
+ <string name="permdesc_accessAllDownloads" msgid="1871832254578267128">"מאפשר לאפליקציה להציג ולשנות את כל ההורדות שהופעלו על ידי אפליקציה כלשהי במערכת."</string>
<string name="download_unknown_title" msgid="7015124071247271585">"&lt;ללא כותרת&gt;"</string>
<string name="notification_download_complete" msgid="5443563299253103667">"ההורדה הסתיימה."</string>
<string name="notification_download_failed" msgid="8612136111952014978">"ההורדה נכשלה."</string>
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index e0b5842d..ad3cf7ac 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -35,7 +35,9 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
+import android.os.Handler;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.OnCloseListener;
import android.os.Process;
import android.os.SELinux;
import android.provider.BaseColumns;
@@ -49,6 +51,8 @@ import com.android.internal.util.IndentingPrintWriter;
import com.google.android.collect.Maps;
import com.google.common.annotations.VisibleForTesting;
+import libcore.io.IoUtils;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -69,7 +73,7 @@ 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 = 108;
+ private static final int DB_VERSION = 109;
/** Name of table in the database */
private static final String DB_TABLE = "downloads";
@@ -166,6 +170,8 @@ public final class DownloadProvider extends ContentProvider {
private static final List<String> downloadManagerColumnsList =
Arrays.asList(DownloadManager.UNDERLYING_COLUMNS);
+ private Handler mHandler;
+
/** The database that lies underneath this content provider */
private SQLiteOpenHelper mOpenHelper = null;
@@ -319,6 +325,11 @@ public final class DownloadProvider extends ContentProvider {
"INTEGER NOT NULL DEFAULT 1");
break;
+ case 109:
+ addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_ALLOW_WRITE,
+ "BOOLEAN NOT NULL DEFAULT 0");
+ break;
+
default:
throw new IllegalStateException("Don't know how to upgrade to " + version);
}
@@ -432,6 +443,8 @@ public final class DownloadProvider extends ContentProvider {
mSystemFacade = new RealSystemFacade(getContext());
}
+ mHandler = new Handler();
+
mOpenHelper = new DatabaseHelper(getContext());
// Initialize the system uid
mSystemUid = Process.SYSTEM_UID;
@@ -590,6 +603,7 @@ public final class DownloadProvider extends ContentProvider {
filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
copyInteger(Downloads.Impl.COLUMN_MEDIA_SCANNED, values, filteredValues);
copyString(Downloads.Impl._DATA, values, filteredValues);
+ copyBoolean(Downloads.Impl.COLUMN_ALLOW_WRITE, values, filteredValues);
} else {
filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
@@ -669,23 +683,12 @@ public final class DownloadProvider extends ContentProvider {
}
insertRequestHeaders(db, rowID, values);
- /*
- * requests coming from
- * DownloadManager.addCompletedDownload(String, String, String,
- * boolean, String, String, long) need special treatment
- */
- Context context = getContext();
- if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) ==
- Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
- // When notification is requested, kick off service to process all
- // relevant downloads.
- if (Downloads.Impl.isNotificationToBeDisplayed(vis)) {
- context.startService(new Intent(context, DownloadService.class));
- }
- } else {
- context.startService(new Intent(context, DownloadService.class));
- }
notifyContentChanged(uri, match);
+
+ // Always start service to handle notifications and/or scanning
+ final Context context = getContext();
+ context.startService(new Intent(context, DownloadService.class));
+
return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
}
@@ -784,6 +787,7 @@ public final class DownloadProvider extends ContentProvider {
values.remove(Downloads.Impl.COLUMN_ALLOW_METERED);
values.remove(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI);
values.remove(Downloads.Impl.COLUMN_MEDIA_SCANNED);
+ values.remove(Downloads.Impl.COLUMN_ALLOW_WRITE);
Iterator<Map.Entry<String, Object>> iterator = values.valueSet().iterator();
while (iterator.hasNext()) {
String key = iterator.next().getKey();
@@ -1147,6 +1151,19 @@ public final class DownloadProvider extends ContentProvider {
case ALL_DOWNLOADS_ID:
SqlSelection selection = getWhereClause(uri, where, whereArgs, match);
deleteRequestHeaders(db, selection.getSelection(), selection.getParameters());
+
+ final Cursor cursor = db.query(DB_TABLE, new String[] {
+ Downloads.Impl._ID }, selection.getSelection(), selection.getParameters(),
+ null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ final long id = cursor.getLong(0);
+ DownloadStorageProvider.onDownloadProviderDelete(getContext(), id);
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+
count = db.delete(DB_TABLE, selection.getSelection(), selection.getParameters());
break;
@@ -1162,12 +1179,12 @@ public final class DownloadProvider extends ContentProvider {
* Remotely opens a file
*/
@Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ public ParcelFileDescriptor openFile(final Uri uri, String mode) throws FileNotFoundException {
if (Constants.LOGVV) {
logVerboseOpenFileInfo(uri, mode);
}
- Cursor cursor = query(uri, new String[] {"_data"}, null, null, null);
+ final Cursor cursor = query(uri, new String[] { Downloads.Impl._DATA }, null, null, null);
String path;
try {
int count = (cursor != null) ? cursor.getCount() : 0;
@@ -1182,9 +1199,7 @@ public final class DownloadProvider extends ContentProvider {
cursor.moveToFirst();
path = cursor.getString(0);
} finally {
- if (cursor != null) {
- cursor.close();
- }
+ IoUtils.closeQuietly(cursor);
}
if (path == null) {
@@ -1193,20 +1208,28 @@ public final class DownloadProvider extends ContentProvider {
if (!Helpers.isFilenameValid(path, mDownloadsDataDir)) {
throw new FileNotFoundException("Invalid filename: " + path);
}
- 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");
+ final File file = new File(path);
+ if ("r".equals(mode)) {
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ } else {
+ try {
+ // When finished writing, update size and timestamp
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode),
+ mHandler, new OnCloseListener() {
+ @Override
+ public void onClose(IOException e) {
+ final ContentValues values = new ContentValues();
+ values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, file.length());
+ values.put(Downloads.Impl.COLUMN_LAST_MODIFICATION,
+ System.currentTimeMillis());
+ update(uri, values, null, null);
+ }
+ });
+ } catch (IOException e) {
+ throw new FileNotFoundException("Failed to open for writing: " + e);
}
- throw new FileNotFoundException("couldn't open file");
}
- return ret;
}
@Override
diff --git a/src/com/android/providers/downloads/DownloadStorageProvider.java b/src/com/android/providers/downloads/DownloadStorageProvider.java
index 7268c5c6..d9a0aa63 100644
--- a/src/com/android/providers/downloads/DownloadStorageProvider.java
+++ b/src/com/android/providers/downloads/DownloadStorageProvider.java
@@ -18,36 +18,42 @@ package com.android.providers.downloads;
import android.app.DownloadManager;
import android.app.DownloadManager.Query;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.graphics.Point;
+import android.net.Uri;
import android.os.Binder;
import android.os.CancellationSignal;
+import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.text.TextUtils;
+import android.webkit.MimeTypeMap;
import libcore.io.IoUtils;
+import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
/**
* Presents a {@link DocumentsContract} view of {@link DownloadManager}
* contents.
*/
public class DownloadStorageProvider extends DocumentsProvider {
+ private static final String AUTHORITY = Constants.STORAGE_AUTHORITY;
private static final String DOC_ID_ROOT = Constants.STORAGE_ROOT_ID;
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
- Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
- Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
- Root.COLUMN_AVAILABLE_BYTES,
+ Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
+ Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
@@ -77,13 +83,18 @@ public class DownloadStorageProvider extends DocumentsProvider {
result.setNotificationUri(getContext().getContentResolver(), cursor.getNotificationUri());
}
+ static void onDownloadProviderDelete(Context context, long id) {
+ final Uri uri = DocumentsContract.buildDocumentUri(AUTHORITY, Long.toString(id));
+ context.revokeUriPermission(uri, ~0);
+ }
+
@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
final RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT);
- row.add(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SHORTCUT);
- row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_RECENTS);
+ row.add(Root.COLUMN_FLAGS,
+ Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_CREATE);
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher_download);
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.root_downloads));
row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
@@ -91,6 +102,41 @@ public class DownloadStorageProvider extends DocumentsProvider {
}
@Override
+ public String createDocument(String docId, String mimeType, String displayName)
+ throws FileNotFoundException {
+ final File parent = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOWNLOADS);
+ parent.mkdirs();
+
+ // Delegate to real provider
+ final long token = Binder.clearCallingIdentity();
+ try {
+ displayName = removeExtension(mimeType, displayName);
+ File file = new File(parent, addExtension(mimeType, displayName));
+
+ // If conflicting file, try adding counter suffix
+ int n = 0;
+ while (file.exists() && n++ < 32) {
+ file = new File(parent, addExtension(mimeType, displayName + " (" + n + ")"));
+ }
+
+ try {
+ if (!file.createNewFile()) {
+ throw new IllegalStateException("Failed to touch " + file);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to touch " + file + ": " + e);
+ }
+
+ return Long.toString(mDm.addCompletedDownload(
+ file.getName(), file.getName(), false, mimeType, file.getAbsolutePath(), 0L,
+ false, true));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void deleteDocument(String docId) throws FileNotFoundException {
// Delegate to real provider
final long token = Binder.clearCallingIdentity();
@@ -209,14 +255,12 @@ public class DownloadStorageProvider extends DocumentsProvider {
@Override
public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
throws FileNotFoundException {
- if (!"r".equals(mode)) {
- throw new IllegalArgumentException("Downloads are read-only");
- }
-
// Delegate to real provider
final long token = Binder.clearCallingIdentity();
try {
- return mDm.openDownloadedFile(Long.parseLong(docId));
+ final long id = Long.parseLong(docId);
+ final ContentResolver resolver = getContext().getContentResolver();
+ return resolver.openFileDescriptor(mDm.getDownloadUri(id), mode, signal);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -234,7 +278,8 @@ public class DownloadStorageProvider extends DocumentsProvider {
final RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
- row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED);
+ row.add(Document.COLUMN_FLAGS,
+ Document.FLAG_DIR_PREFERS_LAST_MODIFIED | Document.FLAG_DIR_SUPPORTS_CREATE);
}
private void includeDownloadFromCursor(MatrixCursor result, Cursor cursor) {
@@ -248,7 +293,8 @@ public class DownloadStorageProvider extends DocumentsProvider {
String mimeType = cursor.getString(
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_MEDIA_TYPE));
if (mimeType == null) {
- mimeType = "application/octet-stream";
+ // Provide fake MIME type so it's openable
+ mimeType = "vnd.android.document/file";
}
Long size = cursor.getLong(
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
@@ -283,7 +329,7 @@ public class DownloadStorageProvider extends DocumentsProvider {
break;
}
- int flags = Document.FLAG_SUPPORTS_DELETE;
+ int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE;
if (mimeType != null && mimeType.startsWith("image/")) {
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
}
@@ -300,4 +346,32 @@ public class DownloadStorageProvider extends DocumentsProvider {
row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
row.add(Document.COLUMN_FLAGS, flags);
}
+
+ /**
+ * Remove file extension from name, but only if exact MIME type mapping
+ * exists. This means we can reapply the extension later.
+ */
+ private static String removeExtension(String mimeType, String name) {
+ final int lastDot = name.lastIndexOf('.');
+ if (lastDot >= 0) {
+ final String extension = name.substring(lastDot + 1);
+ final String nameMime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mimeType.equals(nameMime)) {
+ return name.substring(0, lastDot);
+ }
+ }
+ return name;
+ }
+
+ /**
+ * Add file extension to name, but only if exact MIME type mapping exists.
+ */
+ private static String addExtension(String mimeType, String name) {
+ final String extension = MimeTypeMap.getSingleton()
+ .getExtensionFromMimeType(mimeType);
+ if (extension != null) {
+ return name + "." + extension;
+ }
+ return name;
+ }
}
diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java
index def4d7fe..93f8d650 100644
--- a/src/com/android/providers/downloads/DownloadThread.java
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -519,7 +519,7 @@ public class DownloadThread implements Runnable {
throw new StopRequestException(
Downloads.Impl.STATUS_PAUSED_BY_APP, "download paused by owner");
}
- if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED) {
+ if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED || mInfo.mDeleted) {
throw new StopRequestException(Downloads.Impl.STATUS_CANCELED, "download canceled");
}
}
diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java
index 33205557..013faf27 100644
--- a/src/com/android/providers/downloads/Helpers.java
+++ b/src/com/android/providers/downloads/Helpers.java
@@ -16,6 +16,8 @@
package com.android.providers.downloads;
+import static com.android.providers.downloads.Constants.TAG;
+
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
@@ -342,10 +344,26 @@ public class Helpers {
* Checks whether the filename looks legitimate
*/
static boolean isFilenameValid(String filename, File downloadsDataDir) {
- filename = filename.replaceFirst("/+", "/"); // normalize leading slashes
- return filename.startsWith(Environment.getDownloadCacheDirectory().toString())
- || filename.startsWith(downloadsDataDir.toString())
- || filename.startsWith(Environment.getExternalStorageDirectory().toString());
+ final String[] whitelist;
+ try {
+ filename = new File(filename).getCanonicalPath();
+ whitelist = new String[] {
+ downloadsDataDir.getCanonicalPath(),
+ Environment.getDownloadCacheDirectory().getCanonicalPath(),
+ Environment.getExternalStorageDirectory().getCanonicalPath(),
+ };
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to resolve canonical path: " + e);
+ return false;
+ }
+
+ for (String test : whitelist) {
+ if (filename.startsWith(test)) {
+ return true;
+ }
+ }
+
+ return false;
}
/**
diff --git a/src/com/android/providers/downloads/OpenHelper.java b/src/com/android/providers/downloads/OpenHelper.java
index af7a37f2..4eb319c4 100644
--- a/src/com/android/providers/downloads/OpenHelper.java
+++ b/src/com/android/providers/downloads/OpenHelper.java
@@ -78,7 +78,6 @@ public class OpenHelper {
mimeType = DownloadDrmHelper.getOriginalMimeType(context, file, mimeType);
final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if ("application/vnd.android.package-archive".equals(mimeType)) {
// PackageInstaller doesn't like content URIs, so open file
@@ -90,9 +89,12 @@ public class OpenHelper {
intent.putExtra(Intent.EXTRA_REFERRER, getRefererUri(context, id));
intent.putExtra(Intent.EXTRA_ORIGINATING_UID, getOriginatingUid(context, id));
} else if ("file".equals(localUri.getScheme())) {
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(
ContentUris.withAppendedId(ALL_DOWNLOADS_CONTENT_URI, id), mimeType);
} else {
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(localUri, mimeType);
}
diff --git a/ui/res/mipmap-hdpi/ic_launcher_download.png b/ui/res/mipmap-hdpi/ic_launcher_download.png
index 3f092d39..2450cfbb 100644
--- a/ui/res/mipmap-hdpi/ic_launcher_download.png
+++ b/ui/res/mipmap-hdpi/ic_launcher_download.png
Binary files differ
diff --git a/ui/res/mipmap-mdpi/ic_launcher_download.png b/ui/res/mipmap-mdpi/ic_launcher_download.png
index 76652fb9..7b56ada3 100644
--- a/ui/res/mipmap-mdpi/ic_launcher_download.png
+++ b/ui/res/mipmap-mdpi/ic_launcher_download.png
Binary files differ
diff --git a/ui/res/mipmap-xhdpi/ic_launcher_download.png b/ui/res/mipmap-xhdpi/ic_launcher_download.png
index 7d7b1b11..775004f2 100644
--- a/ui/res/mipmap-xhdpi/ic_launcher_download.png
+++ b/ui/res/mipmap-xhdpi/ic_launcher_download.png
Binary files differ
diff --git a/ui/res/mipmap-xxhdpi/ic_launcher_download.png b/ui/res/mipmap-xxhdpi/ic_launcher_download.png
index 0921c124..ed8c7827 100644
--- a/ui/res/mipmap-xxhdpi/ic_launcher_download.png
+++ b/ui/res/mipmap-xxhdpi/ic_launcher_download.png
Binary files differ
diff --git a/ui/res/mipmap-xxxhdpi/ic_launcher_download.png b/ui/res/mipmap-xxxhdpi/ic_launcher_download.png
new file mode 100644
index 00000000..2dbe0f2d
--- /dev/null
+++ b/ui/res/mipmap-xxxhdpi/ic_launcher_download.png
Binary files differ
diff --git a/ui/res/values-am/strings.xml b/ui/res/values-am/strings.xml
index 42626740..82c6038d 100644
--- a/ui/res/values-am/strings.xml
+++ b/ui/res/values-am/strings.xml
@@ -41,7 +41,7 @@
<string name="remove_download" msgid="6372920256257247857">"አስወግድ"</string>
<string name="delete_download" msgid="76629022653866471">"ሰርዝ"</string>
<string name="keep_queued_download" msgid="5144882786014818569">"ጠብቅ"</string>
- <string name="cancel_running_download" msgid="5232704030969221112">"ይቅር"</string>
+ <string name="cancel_running_download" msgid="5232704030969221112">"ሰርዝ"</string>
<string name="retry_download" msgid="7617100787922717912">"እንደገና ሞክር"</string>
<string name="deselect_all" msgid="6348198946254776764">"ሁሉንም አትምረጥ"</string>
<string name="select_all" msgid="634074918366265804">"ሁሉንም ምረጥ"</string>