com.amaze.filemanager.utils.files.FileUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.amaze.filemanager.utils.files.FileUtils.java

Source

/*
 * Copyright (C) 2014 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>
 *
 * This file is part of Amaze File Manager.
 *
 * Amaze File Manager is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.amaze.filemanager.utils.files;

import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.CountDownTimer;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.provider.DocumentFile;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.afollestad.materialdialogs.MaterialDialog;
import com.amaze.filemanager.R;
import com.amaze.filemanager.activities.DatabaseViewerActivity;
import com.amaze.filemanager.activities.MainActivity;
import com.amaze.filemanager.activities.superclasses.PermissionsActivity;
import com.amaze.filemanager.activities.superclasses.ThemedActivity;
import com.amaze.filemanager.filesystem.HybridFile;
import com.amaze.filemanager.filesystem.HybridFileParcelable;
import com.amaze.filemanager.filesystem.Operations;
import com.amaze.filemanager.filesystem.RootHelper;
import com.amaze.filemanager.fragments.preference_fragments.PreferencesConstants;
import com.amaze.filemanager.filesystem.compressed.CompressedHelper;
import com.amaze.filemanager.fragments.preference_fragments.PrefFrag;
import com.amaze.filemanager.ui.dialogs.GeneralDialogCreation;
import com.amaze.filemanager.ui.icons.Icons;
import com.amaze.filemanager.ui.icons.MimeTypes;
import com.amaze.filemanager.utils.DataUtils;
import com.amaze.filemanager.utils.OTGUtil;
import com.amaze.filemanager.utils.OnFileFound;
import com.amaze.filemanager.utils.OnProgressUpdate;
import com.amaze.filemanager.utils.OpenMode;
import com.amaze.filemanager.utils.application.AppConfig;
import com.amaze.filemanager.utils.cloud.CloudUtil;
import com.amaze.filemanager.utils.share.ShareTask;
import com.amaze.filemanager.utils.theme.AppTheme;
import com.cloudrail.si.interfaces.CloudStorage;
import com.cloudrail.si.types.CloudMetaData;
import com.googlecode.concurrenttrees.radix.ConcurrentRadixTree;
import com.googlecode.concurrenttrees.radix.node.concrete.voidvalue.VoidValue;

import net.schmizz.sshj.sftp.RemoteResourceInfo;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.sftp.SFTPException;

import java.io.File;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicLong;

import jcifs.smb.SmbFile;

/**
 * Functions that deal with files
 */
public class FileUtils {

    public static long folderSize(File directory, OnProgressUpdate<Long> updateState) {
        long length = 0;
        try {
            for (File file : directory.listFiles()) {
                if (file.isFile())
                    length += file.length();
                else
                    length += folderSize(file, null); //null because updateState would be called for children dirs

                if (updateState != null)
                    updateState.onUpdate(length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return length;
    }

    public static long folderSize(HybridFile directory, OnProgressUpdate<Long> updateState) {
        if (directory.isSimpleFile())
            return folderSize(new File(directory.getPath()), updateState);
        else
            return directory.folderSize(AppConfig.getInstance());
    }

    public static long folderSize(SmbFile directory) {
        long length = 0;
        try {
            for (SmbFile file : directory.listFiles()) {

                if (file.isFile())
                    length += file.length();
                else
                    length += folderSize(file);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return length;
    }

    /**
     * Use recursive <code>ls</code> to get folder size.
     *
     * It is slow, it is stupid, and may be inaccurate (because of permission problems).
     * Only for fallback use when <code>du</code> is not available.
     *
     * @see HybridFile#folderSize(Context)
     * @return Folder size in bytes
     */
    public static Long folderSizeSftp(SFTPClient client, String remotePath) {
        Long retval = 0L;
        try {
            for (RemoteResourceInfo info : client.ls(remotePath)) {
                if (info.isDirectory())
                    retval += folderSizeSftp(client, info.getPath());
                else
                    retval += info.getAttributes().getSize();
            }
        } catch (SFTPException e) {
            //Usually happens when permission denied listing files in directory
            Log.e("folderSizeSftp", "Problem accessing " + remotePath, e);
        } finally {
            return retval;
        }
    }

    public static long folderSizeCloud(OpenMode openMode, CloudMetaData sourceFileMeta) {

        DataUtils dataUtils = DataUtils.getInstance();
        long length = 0;
        CloudStorage cloudStorage = dataUtils.getAccount(openMode);
        for (CloudMetaData metaData : cloudStorage
                .getChildren(CloudUtil.stripPath(openMode, sourceFileMeta.getPath()))) {

            if (metaData.getFolder()) {
                length += folderSizeCloud(openMode, metaData);
            } else {
                length += metaData.getSize();
            }
        }

        return length;
    }

    /**
     * Helper method to get size of an otg folder
     */
    public static long otgFolderSize(String path, final Context context) {
        final AtomicLong totalBytes = new AtomicLong(0);
        OTGUtil.getDocumentFiles(path, context, file -> totalBytes.addAndGet(getBaseFileSize(file, context)));
        return totalBytes.longValue();
    }

    /**
     * Helper method to calculate source files size
     */
    public static long getTotalBytes(ArrayList<HybridFileParcelable> files, Context context) {
        long totalBytes = 0L;
        for (HybridFileParcelable file : files) {
            totalBytes += getBaseFileSize(file, context);
        }
        return totalBytes;
    }

    private static long getBaseFileSize(HybridFileParcelable baseFile, Context context) {
        if (baseFile.isDirectory(context)) {
            return baseFile.folderSize(context);
        } else {
            return baseFile.length(context);
        }
    }

    /**
     * Calls {@link #scanFile(Uri, Context)} using {@link Uri}.
     *
     * @see {@link #scanFile(Uri, Context)}
     * @param file File to scan
     * @param c {@link Context}
     */
    public static void scanFile(@NonNull File file, @NonNull Context c) {
        scanFile(Uri.fromFile(file), c);
    }

    /**
     * Triggers {@link Intent#ACTION_MEDIA_SCANNER_SCAN_FILE} intent to refresh the media store.
     *
     * @param uri File's {@link Uri}
     * @param c {@link Context}
     */
    public static void scanFile(@NonNull Uri uri, @NonNull Context c) {
        Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
        c.sendBroadcast(mediaScanIntent);
    }

    public static void crossfade(View buttons, final View pathbar) {
        // Set the content view to 0% opacity but visible, so that it is visible
        // (but fully transparent) during the animation.
        buttons.setAlpha(0f);
        buttons.setVisibility(View.VISIBLE);

        // Animate the content view to 100% opacity, and clear any animation
        // listener set on the view.
        buttons.animate().alpha(1f).setDuration(100).setListener(null);
        pathbar.animate().alpha(0f).setDuration(100).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                pathbar.setVisibility(View.GONE);
            }
        });
        // Animate the loading view to 0% opacity. After the animation ends,
        // set its visibility to GONE as an optimization step (it won't
        // participate in layout passes, etc.)
    }

    public static void revealShow(final View view, boolean reveal) {
        if (reveal) {
            ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA, 0f, 1f);
            animator.setDuration(300); //ms
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    view.setVisibility(View.VISIBLE);
                }
            });
            animator.start();
        } else {

            ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA, 1f, 0f);
            animator.setDuration(300); //ms
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    view.setVisibility(View.GONE);
                }
            });
            animator.start();
        }
    }

    public static void crossfadeInverse(final View buttons, final View pathbar) {
        // Set the content view to 0% opacity but visible, so that it is visible
        // (but fully transparent) during the animation.

        pathbar.setAlpha(0f);
        pathbar.setVisibility(View.VISIBLE);

        // Animate the content view to 100% opacity, and clear any animation
        // listener set on the view.
        pathbar.animate().alpha(1f).setDuration(500).setListener(null);
        buttons.animate().alpha(0f).setDuration(500).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                buttons.setVisibility(View.GONE);
            }
        });
        // Animate the loading view to 0% opacity. After the animation ends,
        // set its visibility to GONE as an optimization step (it won't
        // participate in layout passes, etc.)
    }

    public static void shareCloudFile(String path, final OpenMode openMode, final Context context) {
        new AsyncTask<String, Void, String>() {

            @Override
            protected String doInBackground(String... params) {
                String shareFilePath = params[0];
                CloudStorage cloudStorage = DataUtils.getInstance().getAccount(openMode);
                return cloudStorage.createShareLink(CloudUtil.stripPath(openMode, shareFilePath));
            }

            @Override
            protected void onPostExecute(String s) {
                super.onPostExecute(s);

                FileUtils.copyToClipboard(context, s);
                Toast.makeText(context, context.getString(R.string.cloud_share_copied), Toast.LENGTH_LONG).show();
            }
        }.execute(path);
    }

    public static void shareFiles(ArrayList<File> a, Activity c, AppTheme appTheme, int fab_skin) {

        ArrayList<Uri> uris = new ArrayList<>();
        boolean b = true;
        for (File f : a) {
            uris.add(Uri.fromFile(f));
        }

        String mime = MimeTypes.getMimeType(a.get(0).getPath(), a.get(0).isDirectory());
        if (a.size() > 1)
            for (File f : a) {
                if (!mime.equals(MimeTypes.getMimeType(f.getPath(), f.isDirectory()))) {
                    b = false;
                }
            }

        if (!b || mime == (null))
            mime = "*/*";
        try {

            new ShareTask(c, uris, appTheme, fab_skin).execute(mime);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static float readableFileSizeFloat(long size) {
        if (size <= 0)
            return 0;
        return (float) (size / (1024 * 1024));
    }

    /**
     * Install .apk file.
     * @param permissionsActivity needed to ask for {@link Manifest.permission#REQUEST_INSTALL_PACKAGES} permission
     */
    public static void installApk(final @NonNull File f, final @NonNull PermissionsActivity permissionsActivity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                && !permissionsActivity.getPackageManager().canRequestPackageInstalls()) {
            permissionsActivity.requestInstallApkPermission(() -> installApk(f, permissionsActivity));
        }

        Intent chooserIntent = new Intent();
        chooserIntent.setAction(Intent.ACTION_INSTALL_PACKAGE);
        chooserIntent.setData(Uri.fromFile(f));

        try {
            permissionsActivity.startActivity(chooserIntent);
        } catch (ActivityNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(permissionsActivity, R.string.error, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Open a file not supported by Amaze
     *
     * @param f the file
     * @param forcechooser force the chooser to show up even when set default by user
     */
    public static void openunknown(File f, Context c, boolean forcechooser, boolean useNewStack) {
        Intent chooserIntent = new Intent();
        chooserIntent.setAction(Intent.ACTION_VIEW);

        String type = MimeTypes.getMimeType(f.getPath(), f.isDirectory());
        if (type != null && type.trim().length() != 0 && !type.equals("*/*")) {
            Uri uri = fileToContentUri(c, f);
            if (uri == null)
                uri = Uri.fromFile(f);
            chooserIntent.setDataAndType(uri, type);

            Intent activityIntent;
            if (forcechooser) {
                if (useNewStack)
                    applyNewDocFlag(chooserIntent);
                activityIntent = Intent.createChooser(chooserIntent, c.getString(R.string.openwith));
            } else {
                activityIntent = chooserIntent;
                if (useNewStack)
                    applyNewDocFlag(activityIntent);
            }

            try {
                c.startActivity(activityIntent);
            } catch (ActivityNotFoundException e) {
                e.printStackTrace();
                Toast.makeText(c, R.string.noappfound, Toast.LENGTH_SHORT).show();
                openWith(f, c, useNewStack);
            }
        } else {
            // failed to load mime type
            openWith(f, c, useNewStack);
        }
    }

    /**
     * Open file from OTG
     */
    public static void openunknown(DocumentFile f, Context c, boolean forcechooser, boolean useNewStack) {
        Intent chooserIntent = new Intent();
        chooserIntent.setAction(Intent.ACTION_VIEW);
        chooserIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        String type = f.getType();
        if (type != null && type.trim().length() != 0 && !type.equals("*/*")) {
            chooserIntent.setDataAndType(f.getUri(), type);
            Intent activityIntent;
            if (forcechooser) {
                if (useNewStack)
                    applyNewDocFlag(chooserIntent);
                activityIntent = Intent.createChooser(chooserIntent, c.getString(R.string.openwith));
            } else {
                activityIntent = chooserIntent;
                if (useNewStack)
                    applyNewDocFlag(chooserIntent);
            }

            try {
                c.startActivity(activityIntent);
            } catch (ActivityNotFoundException e) {
                e.printStackTrace();
                Toast.makeText(c, R.string.noappfound, Toast.LENGTH_SHORT).show();
                openWith(f, c, useNewStack);
            }
        } else {
            openWith(f, c, useNewStack);
        }
    }

    private static void applyNewDocFlag(Intent i) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            i.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
        } else {

            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
                    | Intent.FLAG_ACTIVITY_TASK_ON_HOME | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
        }
    }

    private static final String INTERNAL_VOLUME = "internal";
    public static final String EXTERNAL_VOLUME = "external";

    private static final String EMULATED_STORAGE_SOURCE = System.getenv("EMULATED_STORAGE_SOURCE");
    private static final String EMULATED_STORAGE_TARGET = System.getenv("EMULATED_STORAGE_TARGET");
    private static final String EXTERNAL_STORAGE = System.getenv("EXTERNAL_STORAGE");

    public static String normalizeMediaPath(String path) {
        // Retrieve all the paths and check that we have this environment vars
        if (TextUtils.isEmpty(EMULATED_STORAGE_SOURCE) || TextUtils.isEmpty(EMULATED_STORAGE_TARGET)
                || TextUtils.isEmpty(EXTERNAL_STORAGE)) {
            return path;
        }

        // We need to convert EMULATED_STORAGE_SOURCE -> EMULATED_STORAGE_TARGET
        if (path.startsWith(EMULATED_STORAGE_SOURCE)) {
            path = path.replace(EMULATED_STORAGE_SOURCE, EMULATED_STORAGE_TARGET);
        }
        return path;
    }

    public static Uri fileToContentUri(Context context, File file) {
        // Normalize the path to ensure media search
        final String normalizedPath = normalizeMediaPath(file.getAbsolutePath());

        // Check in external and internal storages
        Uri uri = fileToContentUri(context, normalizedPath, file.isDirectory(), EXTERNAL_VOLUME);
        if (uri != null) {
            return uri;
        }
        uri = fileToContentUri(context, normalizedPath, file.isDirectory(), INTERNAL_VOLUME);
        if (uri != null) {
            return uri;
        }
        return null;
    }

    private static Uri fileToContentUri(Context context, String path, boolean isDirectory, String volume) {
        final String where = MediaStore.MediaColumns.DATA + " = ?";
        Uri baseUri;
        String[] projection;
        int mimeType = Icons.getTypeOfFile(path, isDirectory);

        switch (mimeType) {
        case Icons.IMAGE:
            baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            projection = new String[] { BaseColumns._ID };
            break;
        case Icons.VIDEO:
            baseUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            projection = new String[] { BaseColumns._ID };
            break;
        case Icons.AUDIO:
            baseUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            projection = new String[] { BaseColumns._ID };
            break;
        default:
            baseUri = MediaStore.Files.getContentUri(volume);
            projection = new String[] { BaseColumns._ID, MediaStore.Files.FileColumns.MEDIA_TYPE };
        }

        ContentResolver cr = context.getContentResolver();
        Cursor c = cr.query(baseUri, projection, where, new String[] { path }, null);
        try {
            if (c != null && c.moveToNext()) {
                boolean isValid = false;
                if (mimeType == Icons.IMAGE || mimeType == Icons.VIDEO || mimeType == Icons.AUDIO) {
                    isValid = true;
                } else {
                    int type = c.getInt(c.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE));
                    isValid = type != 0;
                }

                if (isValid) {
                    // Do not force to use content uri for no media files
                    long id = c.getLong(c.getColumnIndexOrThrow(BaseColumns._ID));
                    return Uri.withAppendedPath(baseUri, String.valueOf(id));
                }
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
        return null;
    }

    /**
     * Method supports showing a UI to ask user to open a file without any extension/mime
     */
    public static void openWith(final File f, final Context c, final boolean useNewStack) {
        MaterialDialog.Builder a = new MaterialDialog.Builder(c);
        a.title(c.getString(R.string.openas));
        String[] items = new String[] { c.getString(R.string.text), c.getString(R.string.image),
                c.getString(R.string.video), c.getString(R.string.audio), c.getString(R.string.database),
                c.getString(R.string.other) };

        a.items(items).itemsCallback((materialDialog, view, i, charSequence) -> {
            Uri uri = fileToContentUri(c, f);
            if (uri == null)
                uri = Uri.fromFile(f);
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_VIEW);
            switch (i) {
            case 0:
                if (useNewStack)
                    applyNewDocFlag(intent);
                intent.setDataAndType(uri, "text/*");
                break;
            case 1:
                intent.setDataAndType(uri, "image/*");
                break;
            case 2:
                intent.setDataAndType(uri, "video/*");
                break;
            case 3:
                intent.setDataAndType(uri, "audio/*");
                break;
            case 4:
                intent = new Intent(c, DatabaseViewerActivity.class);
                intent.putExtra("path", f.getPath());
                break;
            case 5:
                intent.setDataAndType(uri, "*/*");
                break;
            }
            try {
                c.startActivity(intent);
            } catch (Exception e) {
                Toast.makeText(c, R.string.noappfound, Toast.LENGTH_SHORT).show();
                openWith(f, c, useNewStack);
            }
        });
        try {
            a.build().show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void openWith(final DocumentFile f, final Context c, final boolean useNewStack) {
        MaterialDialog.Builder a = new MaterialDialog.Builder(c);
        a.title(c.getString(R.string.openas));
        String[] items = new String[] { c.getString(R.string.text), c.getString(R.string.image),
                c.getString(R.string.video), c.getString(R.string.audio), c.getString(R.string.database),
                c.getString(R.string.other) };

        a.items(items).itemsCallback((materialDialog, view, i, charSequence) -> {
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_VIEW);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            switch (i) {
            case 0:
                if (useNewStack)
                    applyNewDocFlag(intent);
                intent.setDataAndType(f.getUri(), "text/*");
                break;
            case 1:
                intent.setDataAndType(f.getUri(), "image/*");
                break;
            case 2:
                intent.setDataAndType(f.getUri(), "video/*");
                break;
            case 3:
                intent.setDataAndType(f.getUri(), "audio/*");
                break;
            case 4:
                intent = new Intent(c, DatabaseViewerActivity.class);
                intent.putExtra("path", f.getUri());
                break;
            case 5:
                intent.setDataAndType(f.getUri(), "*/*");
                break;
            }
            try {
                c.startActivity(intent);
            } catch (Exception e) {
                Toast.makeText(c, R.string.noappfound, Toast.LENGTH_SHORT).show();
                openWith(f, c, useNewStack);
            }
        });

        a.build().show();
    }

    /**
     * Method determines if there is something to go back to
     */
    public static boolean canGoBack(Context context, HybridFile currentFile) {
        switch (currentFile.getMode()) {

        // we're on main thread and can't list the cloud files
        case DROPBOX:
        case BOX:
        case GDRIVE:
        case ONEDRIVE:
        case OTG:
        case SFTP:
            return true;
        default:
            return true;// TODO: 29/9/2017 there might be nothing to go back to (check parent)
        }
    }

    public static long[] getSpaces(HybridFile hFile, Context context, final OnProgressUpdate<Long[]> updateState) {
        long totalSpace = hFile.getTotal(context);
        long freeSpace = hFile.getUsableSpace();
        long fileSize = 0l;

        if (hFile.isDirectory(context)) {
            fileSize = hFile.folderSize(context);
        } else {
            fileSize = hFile.length(context);
        }
        return new long[] { totalSpace, freeSpace, fileSize };
    }

    public static boolean copyToClipboard(Context context, String text) {
        try {
            android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context
                    .getSystemService(Context.CLIPBOARD_SERVICE);
            android.content.ClipData clip = android.content.ClipData
                    .newPlainText(context.getString(R.string.clipboard_path_copy), text);
            clipboard.setPrimaryClip(clip);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public static String[] getFolderNamesInPath(String path) {
        if (!path.endsWith("/"))
            path += "/";
        return ("root" + path).split("/");
    }

    public static String[] getPathsInPath(String path) {
        if (path.endsWith("/"))
            path = path.substring(0, path.length() - 1);

        ArrayList<String> paths = new ArrayList<>();

        while (path.length() > 0) {
            paths.add(path);
            path = path.substring(0, path.lastIndexOf("/"));
        }

        paths.add("/");
        Collections.reverse(paths);

        return paths.toArray(new String[paths.size()]);
    }

    public static boolean canListFiles(File f) {
        return f.canRead() && f.isDirectory();
    }

    public static void openFile(final File f, final MainActivity m, SharedPreferences sharedPrefs) {
        boolean useNewStack = sharedPrefs.getBoolean(PreferencesConstants.PREFERENCE_TEXTEDITOR_NEWSTACK, false);
        boolean defaultHandler = isSelfDefault(f, m);
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(m);
        final Toast[] studioCount = { null };

        if (f.getName().toLowerCase().endsWith(".apk")) {
            GeneralDialogCreation.showPackageDialog(f, m);
        } else if (defaultHandler && CompressedHelper.isFileExtractable(f.getPath())) {
            GeneralDialogCreation.showArchiveDialog(f, m);
        } else if (defaultHandler && f.getName().toLowerCase().endsWith(".db")) {
            Intent intent = new Intent(m, DatabaseViewerActivity.class);
            intent.putExtra("path", f.getPath());
            m.startActivity(intent);
        } else if (Icons.getTypeOfFile(f.getPath(), f.isDirectory()) == Icons.AUDIO) {
            final int studio_count = sharedPreferences.getInt("studio", 0);
            Uri uri = Uri.fromFile(f);
            final Intent intent = new Intent();
            intent.setAction(Intent.ACTION_VIEW);
            intent.setDataAndType(uri, "audio/*");

            // Behold! It's the  legendary easter egg!
            if (studio_count != 0) {
                new CountDownTimer(studio_count, 1000) {
                    @Override
                    public void onTick(long millisUntilFinished) {
                        int sec = (int) millisUntilFinished / 1000;
                        if (studioCount[0] != null)
                            studioCount[0].cancel();
                        studioCount[0] = Toast.makeText(m, sec + "", Toast.LENGTH_LONG);
                        studioCount[0].show();
                    }

                    @Override
                    public void onFinish() {
                        if (studioCount[0] != null)
                            studioCount[0].cancel();
                        studioCount[0] = Toast.makeText(m, m.getString(R.string.opening), Toast.LENGTH_LONG);
                        studioCount[0].show();
                        m.startActivity(intent);
                    }
                }.start();
            } else
                m.startActivity(intent);
        } else {
            try {
                openunknown(f, m, false, useNewStack);
            } catch (Exception e) {
                Toast.makeText(m, m.getString(R.string.noappfound), Toast.LENGTH_LONG).show();
                openWith(f, m, useNewStack);
            }
        }
    }

    private static boolean isSelfDefault(File f, Context c) {
        Intent intent = new Intent();
        intent.setAction(android.content.Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(f), MimeTypes.getMimeType(f.getPath(), f.isDirectory()));
        String s = "";
        ResolveInfo rii = c.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
        if (rii != null && rii.activityInfo != null)
            s = rii.activityInfo.packageName;

        return s.equals("com.amaze.filemanager") || rii == null;
    }

    /**
     * Support file opening for {@link DocumentFile} (eg. OTG)
     */
    public static void openFile(final DocumentFile f, final MainActivity m, SharedPreferences sharedPrefs) {
        boolean useNewStack = sharedPrefs.getBoolean(PreferencesConstants.PREFERENCE_TEXTEDITOR_NEWSTACK, false);
        try {
            openunknown(f, m, false, useNewStack);
        } catch (Exception e) {
            Toast.makeText(m, m.getString(R.string.noappfound), Toast.LENGTH_LONG).show();
            openWith(f, m, useNewStack);
        }

        // not supporting inbuilt activities for now
        /*if (f.getName().toLowerCase().endsWith(".zip") ||
            f.getName().toLowerCase().endsWith(".jar") ||
            f.getName().toLowerCase().endsWith(".rar")||
            f.getName().toLowerCase().endsWith(".tar") ||
            f.getName().toLowerCase().endsWith(".tar.gz")) {
        //showArchiveDialog(f, m);
        } else if(f.getName().toLowerCase().endsWith(".apk")) {
        //showPackageDialog(f, m);
        } else if (f.getName().toLowerCase().endsWith(".db")) {
        Intent intent = new Intent(m, DatabaseViewerActivity.class);
        intent.putExtra("path", f.getUri());
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        m.startActivity(intent);
        }  else if (Icons.isAudio(f.getName())) {
        final int studio_count = sharedPref.getInt("studio", 0);
        final Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.setDataAndType(f.getUri(), "audio*//*");
                                                       
                                                   // Behold! It's the  legendary easter egg!
                                                   if (studio_count!=0) {
                                                   new CountDownTimer(studio_count, 1000) {
                                                   @Override
                                                   public void onTick(long millisUntilFinished) {
                                                   int sec = (int)millisUntilFinished/1000;
                                                   if (studioCount!=null)
                                                   studioCount.cancel();
                                                   studioCount = Toast.makeText(m, sec + "", Toast.LENGTH_LONG);
                                                   studioCount.show();
                                                   }
                                                       
                                                   @Override
                                                   public void onFinish() {
                                                   if (studioCount!=null)
                                                   studioCount.cancel();
                                                   studioCount = Toast.makeText(m, m.getString(R.string.opening), Toast.LENGTH_LONG);
                                                   studioCount.show();
                                                   m.startActivity(intent);
                                                   }
                                                   }.start();
                                                   } else
                                                   m.startActivity(intent);
                                                   } else {
                                                   try {
                                                   openunknown(f, m, false);
                                                   } catch (Exception e) {
                                                   Toast.makeText(m, m.getString(R.string.noappfound),Toast.LENGTH_LONG).show();
                                                   openWith(f, m);
                                                   }
                                                   }*/
    }

    public static ArrayList<HybridFile> toHybridFileConcurrentRadixTree(ConcurrentRadixTree<VoidValue> a) {
        ArrayList<HybridFile> b = new ArrayList<>();
        for (CharSequence o : a.getKeysStartingWith("")) {
            HybridFile hFile = new HybridFile(OpenMode.UNKNOWN, o.toString());
            hFile.generateMode(null);
            b.add(hFile);
        }
        return b;
    }

    public static ArrayList<HybridFile> toHybridFileArrayList(LinkedList<String> a) {
        ArrayList<HybridFile> b = new ArrayList<>();
        for (String s : a) {
            HybridFile hFile = new HybridFile(OpenMode.UNKNOWN, s);
            hFile.generateMode(null);
            b.add(hFile);
        }
        return b;
    }

    /**
     * We're parsing a line returned from a stdout of shell.
     * @param line must be the line returned from a 'ls' command
     */
    public static HybridFileParcelable parseName(String line) {
        boolean linked = false;
        StringBuilder name = new StringBuilder();
        StringBuilder link = new StringBuilder();
        String size = "-1";
        String date = "";
        String[] array = line.split(" ");
        if (array.length < 6)
            return null;
        for (String anArray : array) {
            if (anArray.contains("->") && array[0].startsWith("l")) {
                linked = true;
            }
        }
        int p = getColonPosition(array);
        if (p != -1) {
            date = array[p - 1] + " | " + array[p];
            size = array[p - 2];
        }
        if (!linked) {
            for (int i = p + 1; i < array.length; i++) {
                name.append(" ").append(array[i]);
            }
            name = new StringBuilder(name.toString().trim());
        } else {
            int q = getLinkPosition(array);
            for (int i = p + 1; i < q; i++) {
                name.append(" ").append(array[i]);
            }
            name = new StringBuilder(name.toString().trim());
            for (int i = q + 1; i < array.length; i++) {
                link.append(" ").append(array[i]);
            }
        }
        long Size = (size == null || size.trim().length() == 0) ? -1 : Long.parseLong(size);
        if (date.trim().length() > 0) {
            ParsePosition pos = new ParsePosition(0);
            SimpleDateFormat simpledateformat = new SimpleDateFormat("yyyy-MM-dd | HH:mm");
            Date stringDate = simpledateformat.parse(date, pos);
            HybridFileParcelable baseFile = new HybridFileParcelable(name.toString(), array[0],
                    stringDate.getTime(), Size, true);
            baseFile.setLink(link.toString());
            return baseFile;
        } else {
            HybridFileParcelable baseFile = new HybridFileParcelable(name.toString(), array[0],
                    new File("/").lastModified(), Size, true);
            baseFile.setLink(link.toString());
            return baseFile;
        }
    }

    private static int getLinkPosition(String[] array) {
        for (int i = 0; i < array.length; i++) {
            if (array[i].contains("->"))
                return i;
        }
        return 0;
    }

    private static int getColonPosition(String[] array) {
        for (int i = 0; i < array.length; i++) {
            if (array[i].contains(":"))
                return i;
        }
        return -1;
    }

    public static ArrayList<Boolean[]> parse(String permLine) {
        ArrayList<Boolean[]> arrayList = new ArrayList<>(3);
        Boolean[] read = new Boolean[] { permLine.charAt(1) == 'r', permLine.charAt(4) == 'r',
                permLine.charAt(7) == 'r' };

        Boolean[] write = new Boolean[] { permLine.charAt(2) == 'w', permLine.charAt(5) == 'w',
                permLine.charAt(8) == 'w' };

        Boolean[] execute = new Boolean[] { permLine.charAt(3) == 'x', permLine.charAt(6) == 'x',
                permLine.charAt(9) == 'x' };

        arrayList.add(read);
        arrayList.add(write);
        arrayList.add(execute);
        return arrayList;
    }

    public static boolean isStorage(String path) {
        for (String s : DataUtils.getInstance().getStorages())
            if (s.equals(path))
                return true;
        return false;
    }

    public static boolean isPathAccessible(String dir, SharedPreferences pref) {
        File f = new File(dir);
        boolean showIfHidden = pref.getBoolean(PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES, false),
                isDirSelfOrParent = dir.endsWith("/.") || dir.endsWith("/.."),
                showIfRoot = pref.getBoolean(PreferencesConstants.PREFERENCE_ROOTMODE, false);

        return f.exists() && f.isDirectory() && (!f.isHidden() || (showIfHidden && !isDirSelfOrParent))
                && (!isRoot(dir) || showIfRoot);

        // TODO: 2/5/2017 use another system that doesn't create new object
    }

    public static boolean isRoot(String dir) {// TODO: 5/5/2017 hardcoding root might lead to problems down the line
        return !dir.contains(OTGUtil.PREFIX_OTG) && !dir.startsWith("/storage");
    }

    /**
     * Converts ArrayList of HybridFileParcelable to ArrayList of File
     */
    public static ArrayList<File> hybridListToFileArrayList(ArrayList<HybridFileParcelable> a) {
        ArrayList<File> b = new ArrayList<>();
        for (int i = 0; i < a.size(); i++) {
            b.add(new File(a.get(i).getPath()));
        }
        return b;
    }

    /**
     * Checks whether path for bookmark exists
     * If path is not found, empty directory is created
     */
    public static void checkForPath(Context context, String path, boolean isRootExplorer) {
        // TODO: Add support for SMB and OTG in this function
        if (!new File(path).exists()) {
            Toast.makeText(context, context.getString(R.string.bookmark_lost), Toast.LENGTH_SHORT).show();
            Operations.mkdir(RootHelper.generateBaseFile(new File(path), true), context, isRootExplorer,
                    new Operations.ErrorCallBack() {
                        //TODO empty
                        @Override
                        public void exists(HybridFile file) {

                        }

                        @Override
                        public void launchSAF(HybridFile file) {

                        }

                        @Override
                        public void launchSAF(HybridFile file, HybridFile file1) {

                        }

                        @Override
                        public void done(HybridFile hFile, boolean b) {

                        }

                        @Override
                        public void invalidName(HybridFile file) {

                        }
                    });
        }
    }

}