Java tutorial
/* * Copyright (C) 2007-2008 OpenIntents.org * * 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.raspi.chatapp.util.storage.file; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; import android.os.Build; import android.os.CancellationSignal; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.os.StatFs; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsProvider; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.support.v4.os.EnvironmentCompat; import android.text.TextUtils; import android.util.Log; import android.webkit.MimeTypeMap; import com.raspi.chatapp.R; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class LocalStorageProvider extends DocumentsProvider { /** * Authority that matches the authority in the AndroidManifest.xml for LocalStorageProvider */ public final static String AUTHORITY = "com.ianhanniballake.localstorage.documents"; /** * Default root projection: everything but Root.COLUMN_MIME_TYPES */ private final static String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_SUMMARY, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_ICON, Root.COLUMN_AVAILABLE_BYTES }; /** * Default document projection: everything but Document.COLUMN_ICON and Document.COLUMN_SUMMARY */ private final static String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS, Document.COLUMN_MIME_TYPE, Document.COLUMN_SIZE, Document.COLUMN_LAST_MODIFIED }; /** * Check to see if we are missing the Storage permission group. In those cases, we cannot access local files and * must invalidate any root URIs currently available. * * @param context The current Context * @return whether the permission has been granted it is safe to proceed */ static boolean isMissingPermission(@Nullable Context context) { if (context == null) { return true; } if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // Make sure that our root is invalidated as apparently we lost permission context.getContentResolver() .notifyChange(DocumentsContract.buildRootsUri(LocalStorageProvider.AUTHORITY), null); return true; } return false; } @Override public Cursor queryRoots(final String[] projection) throws FileNotFoundException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (getContext() == null || ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { return null; } // Create a cursor with either the requested fields, or the default projection if "projection" is null. final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION); // Add Home directory File homeDir = Environment.getExternalStorageDirectory(); if (TextUtils.equals(Environment.getExternalStorageState(), Environment.MEDIA_MOUNTED)) { final MatrixCursor.RowBuilder row = result.newRow(); // These columns are required row.add(Root.COLUMN_ROOT_ID, homeDir.getAbsolutePath()); row.add(Root.COLUMN_DOCUMENT_ID, homeDir.getAbsolutePath()); row.add(Root.COLUMN_TITLE, getContext().getString(R.string.home)); row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD); row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher); // These columns are optional row.add(Root.COLUMN_SUMMARY, homeDir.getAbsolutePath()); row.add(Root.COLUMN_AVAILABLE_BYTES, new StatFs(homeDir.getAbsolutePath()).getAvailableBytes()); // Root.COLUMN_MIME_TYPE is another optional column and useful if you have multiple roots with different // types of mime types (roots that don't match the requested mime type are automatically hidden) } // Add SD card directory File sdCard = new File("/storage/extSdCard"); String storageState = EnvironmentCompat.getStorageState(sdCard); if (TextUtils.equals(storageState, Environment.MEDIA_MOUNTED) || TextUtils.equals(storageState, Environment.MEDIA_MOUNTED_READ_ONLY)) { final MatrixCursor.RowBuilder row = result.newRow(); // These columns are required row.add(Root.COLUMN_ROOT_ID, sdCard.getAbsolutePath()); row.add(Root.COLUMN_DOCUMENT_ID, sdCard.getAbsolutePath()); row.add(Root.COLUMN_TITLE, getContext().getString(R.string.sd_card)); // Always assume SD Card is read-only row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY); row.add(Root.COLUMN_ICON, R.drawable.ic_sd_card); row.add(Root.COLUMN_SUMMARY, sdCard.getAbsolutePath()); row.add(Root.COLUMN_AVAILABLE_BYTES, new StatFs(sdCard.getAbsolutePath()).getAvailableBytes()); } return result; } else return null; } @Override public String createDocument(final String parentDocumentId, final String mimeType, final String displayName) throws FileNotFoundException { if (LocalStorageProvider.isMissingPermission(getContext())) { return null; } File newFile = new File(parentDocumentId, displayName); try { newFile.createNewFile(); return newFile.getAbsolutePath(); } catch (IOException e) { Log.e(LocalStorageProvider.class.getSimpleName(), "Error creating new file " + newFile); } return null; } @Override public AssetFileDescriptor openDocumentThumbnail(final String documentId, final Point sizeHint, final CancellationSignal signal) throws FileNotFoundException { if (LocalStorageProvider.isMissingPermission(getContext())) { return null; } // Assume documentId points to an image file. Build a thumbnail no larger than twice the sizeHint BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(documentId, options); final int targetHeight = 2 * sizeHint.y; final int targetWidth = 2 * sizeHint.x; final int height = options.outHeight; final int width = options.outWidth; options.inSampleSize = 1; if (height > targetHeight || width > targetWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / options.inSampleSize) > targetHeight || (halfWidth / options.inSampleSize) > targetWidth) { options.inSampleSize *= 2; } } options.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeFile(documentId, options); // Write out the thumbnail to a temporary file File tempFile = null; FileOutputStream out = null; try { tempFile = File.createTempFile("thumbnail", null, getContext().getCacheDir()); out = new FileOutputStream(tempFile); bitmap.compress(Bitmap.CompressFormat.PNG, 90, out); } catch (IOException e) { Log.e(LocalStorageProvider.class.getSimpleName(), "Error writing thumbnail", e); return null; } finally { if (out != null) try { out.close(); } catch (IOException e) { Log.e(LocalStorageProvider.class.getSimpleName(), "Error closing thumbnail", e); } } // It appears the Storage Framework UI caches these results quite aggressively so there is little reason to // write your own caching layer beyond what you need to return a single AssetFileDescriptor return new AssetFileDescriptor(ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_ONLY), 0, AssetFileDescriptor.UNKNOWN_LENGTH); } @Override public boolean isChildDocument(final String parentDocumentId, final String documentId) { return documentId.startsWith(parentDocumentId); } @Override public Cursor queryChildDocuments(final String parentDocumentId, final String[] projection, final String sortOrder) throws FileNotFoundException { if (LocalStorageProvider.isMissingPermission(getContext())) { return null; } // Create a cursor with either the requested fields, or the default projection if "projection" is null. final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); final File parent = new File(parentDocumentId); for (File file : parent.listFiles()) { // Don't show hidden files/folders if (!file.getName().startsWith(".")) { // Adds the file's display name, MIME type, size, and so on. includeFile(result, file); } } return result; } @Override public Cursor queryDocument(final String documentId, final String[] projection) throws FileNotFoundException { if (LocalStorageProvider.isMissingPermission(getContext())) { return null; } // Create a cursor with either the requested fields, or the default projection if "projection" is null. final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); includeFile(result, new File(documentId)); return result; } private void includeFile(final MatrixCursor result, final File file) throws FileNotFoundException { final MatrixCursor.RowBuilder row = result.newRow(); // These columns are required row.add(Document.COLUMN_DOCUMENT_ID, file.getAbsolutePath()); row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); String mimeType = getDocumentType(file.getAbsolutePath()); row.add(Document.COLUMN_MIME_TYPE, mimeType); int flags = file.canWrite() ? Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME | (mimeType.equals(Document.MIME_TYPE_DIR) ? Document.FLAG_DIR_SUPPORTS_CREATE : 0) : 0; // We only show thumbnails for image files - expect a call to openDocumentThumbnail for each file that has // this flag set if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL; row.add(Document.COLUMN_FLAGS, flags); // COLUMN_SIZE is required, but can be null row.add(Document.COLUMN_SIZE, file.length()); // These columns are optional row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); // Document.COLUMN_ICON can be a resource id identifying a custom icon. The system provides default icons // based on mime type // Document.COLUMN_SUMMARY is optional additional information about the file } @Override public String getDocumentType(final String documentId) throws FileNotFoundException { if (LocalStorageProvider.isMissingPermission(getContext())) { return null; } File file = new File(documentId); if (file.isDirectory()) return Document.MIME_TYPE_DIR; // From FileProvider.getType(Uri) final int lastDot = file.getName().lastIndexOf('.'); if (lastDot >= 0) { final String extension = file.getName().substring(lastDot + 1); final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); if (mime != null) { return mime; } } return "application/octet-stream"; } @Override public void deleteDocument(final String documentId) throws FileNotFoundException { if (LocalStorageProvider.isMissingPermission(getContext())) { return; } new File(documentId).delete(); } @Override public String renameDocument(final String documentId, final String displayName) throws FileNotFoundException { if (LocalStorageProvider.isMissingPermission(getContext())) { return null; } File existingFile = new File(documentId); if (!existingFile.exists()) { throw new FileNotFoundException(documentId + " does not exist"); } if (existingFile.getName().equals(displayName)) { return null; } File parentDirectory = existingFile.getParentFile(); File newFile = new File(parentDirectory, displayName); int conflictIndex = 1; while (newFile.exists()) { newFile = new File(parentDirectory, displayName + "_" + conflictIndex++); } boolean success = existingFile.renameTo(newFile); if (!success) { throw new FileNotFoundException( "Unable to rename " + documentId + " to " + existingFile.getAbsolutePath()); } return existingFile.getAbsolutePath(); } @Override public ParcelFileDescriptor openDocument(final String documentId, final String mode, final CancellationSignal signal) throws FileNotFoundException { if (LocalStorageProvider.isMissingPermission(getContext())) { return null; } File file = new File(documentId); final boolean isWrite = (mode.indexOf('w') != -1); if (isWrite) { return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); } else { return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); } } @Override public boolean onCreate() { return true; } }