LocalStorageProvider.java 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. package com.ianhanniballake.localstorage;
  2. import android.content.res.AssetFileDescriptor;
  3. import android.database.Cursor;
  4. import android.database.MatrixCursor;
  5. import android.graphics.Bitmap;
  6. import android.graphics.BitmapFactory;
  7. import android.graphics.Point;
  8. import android.os.CancellationSignal;
  9. import android.os.Environment;
  10. import android.os.ParcelFileDescriptor;
  11. import android.provider.DocumentsContract.Document;
  12. import android.provider.DocumentsContract.Root;
  13. import android.provider.DocumentsProvider;
  14. import android.util.Log;
  15. import android.webkit.MimeTypeMap;
  16. import com.ipaulpro.afilechooser.R;
  17. import java.io.File;
  18. import java.io.FileNotFoundException;
  19. import java.io.FileOutputStream;
  20. import java.io.IOException;
  21. public class LocalStorageProvider extends DocumentsProvider {
  22. public static final String AUTHORITY = "com.ianhanniballake.localstorage.documents";
  23. /**
  24. * Default root projection: everything but Root.COLUMN_MIME_TYPES
  25. */
  26. private final static String[] DEFAULT_ROOT_PROJECTION = new String[] {
  27. Root.COLUMN_ROOT_ID,
  28. Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_ICON,
  29. Root.COLUMN_AVAILABLE_BYTES
  30. };
  31. /**
  32. * Default document projection: everything but Document.COLUMN_ICON and
  33. * Document.COLUMN_SUMMARY
  34. */
  35. private final static String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
  36. Document.COLUMN_DOCUMENT_ID,
  37. Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS, Document.COLUMN_MIME_TYPE,
  38. Document.COLUMN_SIZE,
  39. Document.COLUMN_LAST_MODIFIED
  40. };
  41. @Override
  42. public Cursor queryRoots(final String[] projection) throws FileNotFoundException {
  43. // Create a cursor with either the requested fields, or the default
  44. // projection if "projection" is null.
  45. final MatrixCursor result = new MatrixCursor(projection != null ? projection
  46. : DEFAULT_ROOT_PROJECTION);
  47. // Add Home directory
  48. File homeDir = Environment.getExternalStorageDirectory();
  49. final MatrixCursor.RowBuilder row = result.newRow();
  50. // These columns are required
  51. row.add(Root.COLUMN_ROOT_ID, homeDir.getAbsolutePath());
  52. row.add(Root.COLUMN_DOCUMENT_ID, homeDir.getAbsolutePath());
  53. row.add(Root.COLUMN_TITLE, getContext().getString(R.string.internal_storage));
  54. row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE);
  55. row.add(Root.COLUMN_ICON, R.drawable.ic_provider);
  56. // These columns are optional
  57. row.add(Root.COLUMN_AVAILABLE_BYTES, homeDir.getFreeSpace());
  58. // Root.COLUMN_MIME_TYPE is another optional column and useful if you
  59. // have multiple roots with different
  60. // types of mime types (roots that don't match the requested mime type
  61. // are automatically hidden)
  62. return result;
  63. }
  64. @Override
  65. public String createDocument(final String parentDocumentId, final String mimeType,
  66. final String displayName) throws FileNotFoundException {
  67. File newFile = new File(parentDocumentId, displayName);
  68. try {
  69. newFile.createNewFile();
  70. return newFile.getAbsolutePath();
  71. } catch (IOException e) {
  72. Log.e(LocalStorageProvider.class.getSimpleName(), "Error creating new file " + newFile);
  73. }
  74. return null;
  75. }
  76. @Override
  77. public AssetFileDescriptor openDocumentThumbnail(final String documentId, final Point sizeHint,
  78. final CancellationSignal signal) throws FileNotFoundException {
  79. // Assume documentId points to an image file. Build a thumbnail no
  80. // larger than twice the sizeHint
  81. BitmapFactory.Options options = new BitmapFactory.Options();
  82. options.inJustDecodeBounds = true;
  83. BitmapFactory.decodeFile(documentId, options);
  84. final int targetHeight = 2 * sizeHint.y;
  85. final int targetWidth = 2 * sizeHint.x;
  86. final int height = options.outHeight;
  87. final int width = options.outWidth;
  88. options.inSampleSize = 1;
  89. if (height > targetHeight || width > targetWidth) {
  90. final int halfHeight = height / 2;
  91. final int halfWidth = width / 2;
  92. // Calculate the largest inSampleSize value that is a power of 2 and
  93. // keeps both
  94. // height and width larger than the requested height and width.
  95. while ((halfHeight / options.inSampleSize) > targetHeight
  96. || (halfWidth / options.inSampleSize) > targetWidth) {
  97. options.inSampleSize *= 2;
  98. }
  99. }
  100. options.inJustDecodeBounds = false;
  101. Bitmap bitmap = BitmapFactory.decodeFile(documentId, options);
  102. // Write out the thumbnail to a temporary file
  103. File tempFile = null;
  104. FileOutputStream out = null;
  105. try {
  106. tempFile = File.createTempFile("thumbnail", null, getContext().getCacheDir());
  107. out = new FileOutputStream(tempFile);
  108. bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
  109. } catch (IOException e) {
  110. Log.e(LocalStorageProvider.class.getSimpleName(), "Error writing thumbnail", e);
  111. return null;
  112. } finally {
  113. if (out != null)
  114. try {
  115. out.close();
  116. } catch (IOException e) {
  117. Log.e(LocalStorageProvider.class.getSimpleName(), "Error closing thumbnail", e);
  118. }
  119. }
  120. // It appears the Storage Framework UI caches these results quite
  121. // aggressively so there is little reason to
  122. // write your own caching layer beyond what you need to return a single
  123. // AssetFileDescriptor
  124. return new AssetFileDescriptor(ParcelFileDescriptor.open(tempFile,
  125. ParcelFileDescriptor.MODE_READ_ONLY), 0,
  126. AssetFileDescriptor.UNKNOWN_LENGTH);
  127. }
  128. @Override
  129. public Cursor queryChildDocuments(final String parentDocumentId, final String[] projection,
  130. final String sortOrder) throws FileNotFoundException {
  131. // Create a cursor with either the requested fields, or the default
  132. // projection if "projection" is null.
  133. final MatrixCursor result = new MatrixCursor(projection != null ? projection
  134. : DEFAULT_DOCUMENT_PROJECTION);
  135. final File parent = new File(parentDocumentId);
  136. for (File file : parent.listFiles()) {
  137. // Don't show hidden files/folders
  138. if (!file.getName().startsWith(".")) {
  139. // Adds the file's display name, MIME type, size, and so on.
  140. includeFile(result, file);
  141. }
  142. }
  143. return result;
  144. }
  145. @Override
  146. public Cursor queryDocument(final String documentId, final String[] projection)
  147. throws FileNotFoundException {
  148. // Create a cursor with either the requested fields, or the default
  149. // projection if "projection" is null.
  150. final MatrixCursor result = new MatrixCursor(projection != null ? projection
  151. : DEFAULT_DOCUMENT_PROJECTION);
  152. includeFile(result, new File(documentId));
  153. return result;
  154. }
  155. private void includeFile(final MatrixCursor result, final File file)
  156. throws FileNotFoundException {
  157. final MatrixCursor.RowBuilder row = result.newRow();
  158. // These columns are required
  159. row.add(Document.COLUMN_DOCUMENT_ID, file.getAbsolutePath());
  160. row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
  161. String mimeType = getDocumentType(file.getAbsolutePath());
  162. row.add(Document.COLUMN_MIME_TYPE, mimeType);
  163. int flags = file.canWrite() ? Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE
  164. : 0;
  165. // We only show thumbnails for image files - expect a call to
  166. // openDocumentThumbnail for each file that has
  167. // this flag set
  168. if (mimeType.startsWith("image/"))
  169. flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
  170. row.add(Document.COLUMN_FLAGS, flags);
  171. // COLUMN_SIZE is required, but can be null
  172. row.add(Document.COLUMN_SIZE, file.length());
  173. // These columns are optional
  174. row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
  175. // Document.COLUMN_ICON can be a resource id identifying a custom icon.
  176. // The system provides default icons
  177. // based on mime type
  178. // Document.COLUMN_SUMMARY is optional additional information about the
  179. // file
  180. }
  181. @Override
  182. public String getDocumentType(final String documentId) throws FileNotFoundException {
  183. File file = new File(documentId);
  184. if (file.isDirectory())
  185. return Document.MIME_TYPE_DIR;
  186. // From FileProvider.getType(Uri)
  187. final int lastDot = file.getName().lastIndexOf('.');
  188. if (lastDot >= 0) {
  189. final String extension = file.getName().substring(lastDot + 1);
  190. final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
  191. if (mime != null) {
  192. return mime;
  193. }
  194. }
  195. return "application/octet-stream";
  196. }
  197. @Override
  198. public void deleteDocument(final String documentId) throws FileNotFoundException {
  199. new File(documentId).delete();
  200. }
  201. @Override
  202. public ParcelFileDescriptor openDocument(final String documentId, final String mode,
  203. final CancellationSignal signal) throws FileNotFoundException {
  204. File file = new File(documentId);
  205. final boolean isWrite = (mode.indexOf('w') != -1);
  206. if (isWrite) {
  207. return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
  208. } else {
  209. return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
  210. }
  211. }
  212. @Override
  213. public boolean onCreate() {
  214. return true;
  215. }
  216. }