diff options
author | Ben Lin <linben@google.com> | 2017-02-16 14:07:14 -0800 |
---|---|---|
committer | Ben Lin <linben@google.com> | 2017-02-24 17:11:15 -0800 |
commit | 74d1965590fdaad79ba4c2f4631d187086501d43 (patch) | |
tree | c282bf236ca3f28860b66baf205bcf86c5e26f18 /src/com | |
parent | b87a0a6da8e90ec5097e4a10f989c7253d47236c (diff) | |
download | android_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.java | 287 |
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) { |