summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorBen Lin <linben@google.com>2017-02-16 14:07:14 -0800
committerBen Lin <linben@google.com>2017-02-24 17:11:15 -0800
commit74d1965590fdaad79ba4c2f4631d187086501d43 (patch)
treec282bf236ca3f28860b66baf205bcf86c5e26f18 /src/com
parentb87a0a6da8e90ec5097e4a10f989c7253d47236c (diff)
downloadandroid_packages_providers_DownloadProvider-74d1965590fdaad79ba4c2f4631d187086501d43.tar.gz
android_packages_providers_DownloadProvider-74d1965590fdaad79ba4c2f4631d187086501d43.tar.bz2
android_packages_providers_DownloadProvider-74d1965590fdaad79ba4c2f4631d187086501d43.zip
Incorporating FileSystemProvider logic into DownloadStorageProvider.
This grants DownloadStorageProvider a couple of features: 1. Ability to now create folders 2. Surfacing raw file contents on disk under Shared Storage while avoiding duplicates from DownloadManager The following are kept: 1. Using DownloadManager as internal database to keep separation between raw files and user-downloaded files Test: build & smoke tested. Bug: 35157759 Change-Id: Id27e15e5bcb31ab1064ce9e3984f122754d1d5fd
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/providers/downloads/DownloadStorageProvider.java287
1 files changed, 198 insertions, 89 deletions
diff --git a/src/com/android/providers/downloads/DownloadStorageProvider.java b/src/com/android/providers/downloads/DownloadStorageProvider.java
index edf667d8..cbadeb49 100644
--- a/src/com/android/providers/downloads/DownloadStorageProvider.java
+++ b/src/com/android/providers/downloads/DownloadStorageProvider.java
@@ -35,27 +35,33 @@ import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
-import android.provider.DocumentsProvider;
import android.provider.Downloads;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.content.FileSystemProvider;
+
+import libcore.io.IoUtils;
+
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.IOException;
import java.text.NumberFormat;
+import java.util.HashSet;
+import java.util.Set;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
-import libcore.io.IoUtils;
-
/**
- * Presents a {@link DocumentsContract} view of {@link DownloadManager}
- * contents.
+ * Presents files located in {@link Environment#DIRECTORY_DOWNLOADS} and contents from
+ * {@link DownloadManager}. {@link DownloadManager} contents include active downloads and completed
+ * downloads added by other applications using
+ * {@link DownloadManager#addCompletedDownload(String, String, boolean, String, String, long, boolean, boolean, Uri, Uri)}
+ * .
*/
-public class DownloadStorageProvider extends DocumentsProvider {
+public class DownloadStorageProvider extends FileSystemProvider {
private static final String TAG = "DownloadStorageProvider";
+ private static final String RAW_PREFIX = "raw:";
private static final boolean DEBUG = false;
private static final String AUTHORITY = Constants.STORAGE_AUTHORITY;
@@ -76,6 +82,7 @@ public class DownloadStorageProvider extends DocumentsProvider {
@Override
public boolean onCreate() {
+ super.onCreate(DEFAULT_DOCUMENT_PROJECTION);
mDm = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
mDm.setAccessAllDownloads(true);
mDm.setAccessFilename(true);
@@ -95,6 +102,10 @@ public class DownloadStorageProvider extends DocumentsProvider {
result.setNotificationUri(getContext().getContentResolver(), cursor.getNotificationUri());
}
+ /**
+ * Called by {@link DownloadProvider} when deleting a row in the {@link DownloadManager}
+ * database.
+ */
static void onDownloadProviderDelete(Context context, long id) {
final Uri uri = DocumentsContract.buildDocumentUri(AUTHORITY, Long.toString(id));
context.revokeUriPermission(uri, ~0);
@@ -113,35 +124,25 @@ public class DownloadStorageProvider extends DocumentsProvider {
return result;
}
+ /**
+ * Calls on {@link FileSystemProvider#createDocument(String, String, String)}, and then creates
+ * a new database entry in {@link DownloadManager} if it is not a raw file and not a folder.
+ */
@Override
- public String createDocument(String docId, String mimeType, String displayName)
+ public String createDocument(String parentDocId, String mimeType, String displayName)
throws FileNotFoundException {
- displayName = FileUtils.buildValidFatFilename(displayName);
-
- if (Document.MIME_TYPE_DIR.equals(mimeType)) {
- throw new FileNotFoundException("Directory creation not supported");
- }
-
- final File parent = Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_DOWNLOADS);
- parent.mkdirs();
-
// Delegate to real provider
final long token = Binder.clearCallingIdentity();
try {
- final File file = FileUtils.buildUniqueFile(parent, mimeType, displayName);
-
- try {
- if (!file.createNewFile()) {
- throw new IllegalStateException("Failed to touch " + file);
- }
- } catch (IOException e) {
- throw new IllegalStateException("Failed to touch " + file + ": " + e);
+ String newDocumentId = super.createDocument(parentDocId, mimeType, displayName);
+ if (!Document.MIME_TYPE_DIR.equals(mimeType) && !isRawDocId(parentDocId)) {
+ File newFile = getFileForDocId(newDocumentId);
+ newDocumentId = Long.toString(mDm.addCompletedDownload(
+ newFile.getName(), newFile.getName(), true, mimeType,
+ newFile.getAbsolutePath(), 0L,
+ false, true));
}
-
- return Long.toString(mDm.addCompletedDownload(
- file.getName(), file.getName(), true, mimeType, file.getAbsolutePath(), 0L,
- false, true));
+ return newDocumentId;
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -152,6 +153,10 @@ public class DownloadStorageProvider extends DocumentsProvider {
// Delegate to real provider
final long token = Binder.clearCallingIdentity();
try {
+ if (isRawDocId(docId)) {
+ super.deleteDocument(docId);
+ return;
+ }
if (mDm.remove(Long.parseLong(docId)) != 1) {
throw new IllegalStateException("Failed to delete " + docId);
}
@@ -161,108 +166,113 @@ public class DownloadStorageProvider extends DocumentsProvider {
}
@Override
- public String renameDocument(String documentId, String displayName)
+ public String renameDocument(String docId, String displayName)
throws FileNotFoundException {
- displayName = FileUtils.buildValidFatFilename(displayName);
-
final long token = Binder.clearCallingIdentity();
+
try {
- final long id = Long.parseLong(documentId);
+ if (isRawDocId(docId)) {
+ return super.renameDocument(docId, displayName);
+ }
+ displayName = FileUtils.buildValidFatFilename(displayName);
+ final long id = Long.parseLong(docId);
if (!mDm.rename(getContext(), id, displayName)) {
throw new IllegalStateException(
"Failed to rename to " + displayName + " in downloadsManager");
}
+ return null;
} finally {
Binder.restoreCallingIdentity(token);
}
- return null;
}
@Override
public Cursor queryDocument(String docId, String[] projection) throws FileNotFoundException {
- final DownloadsCursor result =
- new DownloadsCursor(projection, getContext().getContentResolver());
+ // Delegate to real provider
+ final long token = Binder.clearCallingIdentity();
+ Cursor cursor = null;
+ try {
+ if (isRawDocId(docId)) {
+ return super.queryDocument(docId, projection);
+ }
- if (DOC_ID_ROOT.equals(docId)) {
- includeDefaultDocument(result);
- } else {
- // Delegate to real provider
- final long token = Binder.clearCallingIdentity();
- Cursor cursor = null;
- try {
+ final DownloadsCursor result = new DownloadsCursor(projection,
+ getContext().getContentResolver());
+
+ if (DOC_ID_ROOT.equals(docId)) {
+ includeDefaultDocument(result);
+ } else {
cursor = mDm.query(new Query().setFilterById(Long.parseLong(docId)));
copyNotificationUri(result, cursor);
+ Set<String> filePaths = new HashSet<>();
if (cursor.moveToFirst()) {
// We don't know if this queryDocument() call is from Downloads (manage)
// or Files. Safely assume it's Files.
- includeDownloadFromCursor(result, cursor);
+ includeDownloadFromCursor(result, cursor, filePaths);
}
- } finally {
- IoUtils.closeQuietly(cursor);
- Binder.restoreCallingIdentity(token);
- }
- }
-
- result.start();
- return result;
- }
-
- @Override
- public Cursor queryChildDocuments(String docId, String[] projection, String sortOrder)
- throws FileNotFoundException {
- final DownloadsCursor result =
- new DownloadsCursor(projection, getContext().getContentResolver());
-
- // Delegate to real provider
- final long token = Binder.clearCallingIdentity();
- Cursor cursor = null;
- try {
- cursor = mDm.query(new DownloadManager.Query().setOnlyIncludeVisibleInDownloadsUi(true)
- .setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL));
- copyNotificationUri(result, cursor);
- while (cursor.moveToNext()) {
- includeDownloadFromCursor(result, cursor);
}
+ result.start();
+ return result;
} finally {
IoUtils.closeQuietly(cursor);
Binder.restoreCallingIdentity(token);
}
+ }
- result.start();
- return result;
+ @Override
+ public Cursor queryChildDocuments(String parentDocId, String[] projection, String sortOrder)
+ throws FileNotFoundException {
+ return queryChildDocuments(parentDocId, projection, sortOrder, false);
}
@Override
public Cursor queryChildDocumentsForManage(
- String parentDocumentId, String[] projection, String sortOrder)
+ String parentDocId, String[] projection, String sortOrder)
throws FileNotFoundException {
- final DownloadsCursor result =
- new DownloadsCursor(projection, getContext().getContentResolver());
+ return queryChildDocuments(parentDocId, projection, sortOrder, true);
+ }
+
+ private Cursor queryChildDocuments(String parentDocId, String[] projection,
+ String sortOrder, boolean manage) throws FileNotFoundException {
// Delegate to real provider
final long token = Binder.clearCallingIdentity();
Cursor cursor = null;
try {
- cursor = mDm.query(
- new DownloadManager.Query().setOnlyIncludeVisibleInDownloadsUi(true));
+ if (isRawDocId(parentDocId)) {
+ return super.queryChildDocuments(parentDocId, projection, sortOrder);
+ }
+
+ assert (DOC_ID_ROOT.equals(parentDocId));
+ final DownloadsCursor result = new DownloadsCursor(projection,
+ getContext().getContentResolver());
+ if (manage) {
+ cursor = mDm.query(
+ new DownloadManager.Query().setOnlyIncludeVisibleInDownloadsUi(true));
+ } else {
+ cursor = mDm
+ .query(new DownloadManager.Query().setOnlyIncludeVisibleInDownloadsUi(true)
+ .setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL));
+ }
copyNotificationUri(result, cursor);
+ Set<String> filePaths = new HashSet<>();
while (cursor.moveToNext()) {
- includeDownloadFromCursor(result, cursor);
+ includeDownloadFromCursor(result, cursor, filePaths);
}
+ includeFilesFromSharedStorage(result, filePaths, null);
+
+ result.start();
+ return result;
} finally {
IoUtils.closeQuietly(cursor);
Binder.restoreCallingIdentity(token);
}
-
- result.start();
- return result;
}
@Override
public Cursor queryRecentDocuments(String rootId, String[] projection)
throws FileNotFoundException {
-
final DownloadsCursor result =
new DownloadsCursor(projection, getContext().getContentResolver());
@@ -285,8 +295,6 @@ public class DownloadStorageProvider extends DocumentsProvider {
|| (mimeType.startsWith("image/") && !TextUtils.isEmpty(uri))) {
continue;
}
-
- includeDownloadFromCursor(result, cursor);
}
} finally {
IoUtils.closeQuietly(cursor);
@@ -311,8 +319,17 @@ public class DownloadStorageProvider extends DocumentsProvider {
cursor = mDm.query(new DownloadManager.Query().setOnlyIncludeVisibleInDownloadsUi(true)
.setFilterByString(query));
copyNotificationUri(result, cursor);
+ Set<String> filePaths = new HashSet<>();
while (cursor.moveToNext()) {
- includeDownloadFromCursor(result, cursor);
+ includeDownloadFromCursor(result, cursor, filePaths);
+ }
+ Cursor rawFilesCursor = super.querySearchDocuments(getDownloadsDirectory(), query,
+ projection, filePaths);
+ while (rawFilesCursor.moveToNext()) {
+ String docId = rawFilesCursor.getString(
+ rawFilesCursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID));
+ File rawFile = getFileForDocId(docId);
+ includeFileFromSharedStorage(result, rawFile);
}
} finally {
IoUtils.closeQuietly(cursor);
@@ -329,6 +346,10 @@ public class DownloadStorageProvider extends DocumentsProvider {
// Delegate to real provider
final long token = Binder.clearCallingIdentity();
try {
+ if (isRawDocId(docId)) {
+ return super.openDocument(docId, mode, signal);
+ }
+
final long id = Long.parseLong(docId);
final ContentResolver resolver = getContext().getContentResolver();
return resolver.openFileDescriptor(mDm.getDownloadUri(id), mode, signal);
@@ -345,6 +366,46 @@ public class DownloadStorageProvider extends DocumentsProvider {
return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
}
+ @Override
+ protected File getFileForDocId(String docId, boolean visible) throws FileNotFoundException {
+ if (isRawDocId(docId)) {
+ return new File(getAbsoluteFilePath(docId));
+ }
+
+ if (DOC_ID_ROOT.equals(docId)) {
+ return getDownloadsDirectory();
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ Cursor cursor = null;
+ String localFilePath = null;
+ try {
+ cursor = mDm.query(new Query().setFilterById(Long.parseLong(docId)));
+ if (cursor.moveToFirst()) {
+ localFilePath = cursor.getString(
+ cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME));
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ Binder.restoreCallingIdentity(token);
+ }
+
+ if (localFilePath == null) {
+ throw new IllegalStateException("File has no filepath. Could not be found.");
+ }
+ return new File(localFilePath);
+ }
+
+ @Override
+ protected String getDocIdForFile(File file) throws FileNotFoundException {
+ return RAW_PREFIX + file.getAbsolutePath();
+ }
+
+ @Override
+ protected Uri buildNotificationUri(String docId) {
+ return DocumentsContract.buildChildDocumentsUri(AUTHORITY, docId);
+ }
+
private void includeDefaultDocument(MatrixCursor result) {
final RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
@@ -353,11 +414,16 @@ public class DownloadStorageProvider extends DocumentsProvider {
Document.FLAG_DIR_PREFERS_LAST_MODIFIED | Document.FLAG_DIR_SUPPORTS_CREATE);
}
+ private static String getAbsoluteFilePath(String rawDocumentId) {
+ return rawDocumentId.substring(RAW_PREFIX.length());
+ }
+
/**
* Adds the entry from the cursor to the result only if the entry is valid. That is,
* if the file exists in the file system.
*/
- private void includeDownloadFromCursor(MatrixCursor result, Cursor cursor) {
+ private void includeDownloadFromCursor(MatrixCursor result, Cursor cursor,
+ Set<String> filePaths) {
final long id = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID));
final String docId = String.valueOf(id);
@@ -435,6 +501,51 @@ public class DownloadStorageProvider extends DocumentsProvider {
if (status != DownloadManager.STATUS_RUNNING) {
row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
}
+ filePaths.add(localFilePath);
+ }
+
+ /**
+ * Takes all the top-level files from the Downloads directory and adds them to the result.
+ *
+ * @param result cursor containing all documents to be returned by queryChildDocuments or
+ * queryChildDocumentsForManage.
+ * @param downloadedFilePaths The absolute file paths of all the files in the result Cursor.
+ * @param searchString query used to filter out unwanted results.
+ */
+ private void includeFilesFromSharedStorage(MatrixCursor result,
+ Set<String> downloadedFilePaths, @Nullable String searchString)
+ throws FileNotFoundException {
+ File downloadsDir = getDownloadsDirectory();
+ // Add every file from the Downloads directory to the result cursor. Ignore files that
+ // were in the supplied downloaded file paths.
+ for (File file : downloadsDir.listFiles()) {
+ boolean inResultsAlready = downloadedFilePaths.contains(file.getAbsolutePath());
+ boolean containsQuery = searchString == null || file.getName().contains(searchString);
+ if (!inResultsAlready && containsQuery) {
+ includeFileFromSharedStorage(result, file);
+ }
+ }
+ }
+
+ /**
+ * Adds a file to the result cursor. It uses a combination of {@code #RAW_PREFIX} and its
+ * absolute file path for its id. Directories are not to be included.
+ *
+ * @param result cursor containing all documents to be returned by queryChildDocuments or
+ * queryChildDocumentsForManage.
+ * @param file file to be included in the result cursor.
+ */
+ private void includeFileFromSharedStorage(MatrixCursor result, File file)
+ throws FileNotFoundException {
+ includeFile(result, null, file);
+ }
+
+ private boolean isRawDocId(String docId) {
+ return docId != null && docId.startsWith(RAW_PREFIX);
+ }
+
+ private static File getDownloadsDirectory() {
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
}
/**
@@ -494,9 +605,7 @@ public class DownloadStorageProvider extends DocumentsProvider {
private static final int NOTIFY_EVENTS = ATTRIB | CLOSE_WRITE | MOVED_FROM | MOVED_TO
| CREATE | DELETE | DELETE_SELF | MOVE_SELF;
- private static final String DOWNLOADS_PATH =
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
- .getAbsolutePath();
+ private static final String DOWNLOADS_PATH = getDownloadsDirectory().getAbsolutePath();
private final ContentResolver mResolver;
public ContentChangedRelay(ContentResolver resolver) {