com.raspi.chatapp.util.storage.file.LocalStorageProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.raspi.chatapp.util.storage.file.LocalStorageProvider.java

Source

/*
 * 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;
    }
}