public class Move extends FileOperation {

    public static final String MOVED_FILES_PATHS = "MOVED_FILES_PATHS";

    private ArrayList<String> movedFilePaths;

    @Override
    String getNotificationTitle() {
        return getString(R.string.move);
    }

    @Override
    public int getNotificationSmallIconRes() {
        return R.drawable.ic_folder_move_white;
    }

    @Override
    public void execute(Intent workIntent) {
        File_POJO[] files = getFiles(workIntent);
        File_POJO target = workIntent.getParcelableExtra(TARGET);

        movedFilePaths = new ArrayList<>();

        if (target == null) {
            return;
        }

        int success_count = 0;

        onProgress(success_count, files.length);

        //check if file is on removable storage
        boolean movingOntoRemovableStorage = Util.isOnRemovableStorage(target.getPath());

        /*if (movingOntoRemovableStorage) {
            //failed = true;
            Uri treeUri = getTreeUri(workIntent, target.getPath());
            if (treeUri == null) {
                return;
            }
        } else {*/
        for (int i = files.length - 1; i >= 0; i--) {
            boolean movingFromRemovableStorage = Util.isOnRemovableStorage(files[i].getPath());

            boolean result;
            if (movingFromRemovableStorage || movingOntoRemovableStorage) {
                //failed = true;
                Uri treeUri;
                if (movingFromRemovableStorage) {
                    treeUri = getTreeUri(workIntent, files[i].getPath());
                } else {
                    treeUri = getTreeUri(workIntent, target.getPath());
                }

                if (treeUri == null) {
                    return;
                }
                result = copyAndDeleteFiles(getApplicationContext(), treeUri,
                        files[i].getPath(), target.getPath());
                //break;
            } else {
                result = moveFile(files[i].getPath(), target.getPath());
            }

            //boolean result = moveFile(files[i].getPath(), target.getPath());
            if (result) {
                movedFilePaths.add(files[i].getPath());
            }
            success_count += result ? 1 : 0;
            onProgress(success_count, files.length);
        }
        //}

        /*if (failed) {
            showRemovableStorageToast();
        } else */
        if (success_count == 0) {
            onProgress(success_count, files.length);
        }
    }

    @Override
    public int getType() {
        return FileOperation.MOVE;
    }

    private boolean moveFile(String path, String destination) {
        ArrayList<String> oldPaths = Util.getAllChildPaths(new ArrayList<String>(), path);

        File file = new File(path);
        File newFile = new File(destination, file.getName());

        //moving file
        boolean success = renameFile(file, newFile);

        //re-scan all paths
        ArrayList<String> newPaths = Util.getAllChildPaths(new ArrayList<String>(), newFile.getPath());
        addPathsToScan(oldPaths);
        addPathsToScan(newPaths);
        return success;
    }

    private boolean copyAndDeleteFiles(Context context, Uri treeUri,
                                       String path, String destination) {
        Copy copy = new Copy();
        boolean result;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
                Environment.isExternalStorageRemovable(new File(path))) {
            result = copy.copyFilesRecursively(context, null,
                    path, destination, true);
        } else {
            result = copy.copyFilesRecursively(context, treeUri,
                    path, destination, true);
        }
        addPathsToScan(copy.getPathsToScan());
        Log.d("Move", "copyAndDeleteFiles(): " + result);
        if (result) {
            Delete delete = new Delete();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
                    && Environment.isExternalStorageRemovable(new File(path))) {
                result = delete.deleteFileOnRemovableStorage(context, treeUri, path);
            } else {
                result = delete.deleteFile(path);
            }
            addPathsToScan(delete.getPathsToScan());
        }
        return result;
    }

    private static boolean renameFile(File file, File newFile) {
        //moving file
        return file.renameTo(newFile);
    }

    @Override
    public Intent getDoneIntent() {
        Intent intent = super.getDoneIntent();
        intent.putExtra(MOVED_FILES_PATHS, movedFilePaths);
        return intent;
    }
} public class VirtualAlbum extends Album {

    public static final String VIRTUAL_ALBUMS_DIR = "virtual_directory:";
    private static final String NAME = "NAME";
    private static final String REAL_DIRS = "REAL_DIRS";

    private String name;

    private ArrayList<String> directories;

    public VirtualAlbum(String name, String[] dirs) {
        super();
        this.name = name;
        directories = new ArrayList<>();
        directories.addAll(Arrays.asList(dirs));
    }

    public void create(Context context, ArrayList<Album> albums) {
        getAlbumItems().clear();
        for (int i = 0; i < albums.size(); i++) {
            Album album = albums.get(i);
            if (contains(album.getPath())) {
                getAlbumItems().addAll(album.getAlbumItems());
            }
        }
        Settings s = Settings.getInstance(context);
        int sortBy = s.sortAlbumBy();
        SortUtil.sort(getAlbumItems(), sortBy);

        pinned = Provider.isAlbumPinned(getPath(),
                Provider.getPinnedPaths());
    }

    public boolean contains(String path) {
        if (directories == null || path == null) {
            return false;
        }

        for (int i = 0; i < directories.size(); i++) {
            if (path.startsWith(directories.get(i))) {
                return true;
            }
        }
        return false;
    }

    public ArrayList<String> getDirectories() {
        return directories;
    }

    private void addDirectory(String path) {
        if (!directories.contains(path)) {
            directories.add(path);
        }
    }

    public void removeDirectory(String path) {
        directories.remove(path);
    }

    @Override
    public boolean isHidden() {
        return false;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Album setPath(String path) {
        super.setPath(path);
        return this;
    }

    @Override
    public String getPath() {
        return VIRTUAL_ALBUMS_DIR + getName();
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        super.writeToParcel(parcel, i);
        parcel.writeStringList(directories);
    }

    @SuppressWarnings("unused")
    public VirtualAlbum(Parcel parcel) {
        super(parcel);
        directories = parcel.createStringArrayList();
    }

    public VirtualAlbum(String jsonString) {
        try {
            JSONObject jsonObject = new JSONObject(jsonString);
            name = jsonObject.getString(NAME);
            JSONArray array = jsonObject.getJSONArray(REAL_DIRS);
            directories = new ArrayList<>();
            for (int i = 0; i < array.length(); i++) {
                directories.add(array.getString(i));
            }
        } catch (JSONException e) {
            e.printStackTrace();
            directories = new ArrayList<>();
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof VirtualAlbum) {
            VirtualAlbum virtualAlbum = (VirtualAlbum) obj;
            return virtualAlbum.getName().equals(getName());
        }
        return super.equals(obj);
    }

    @Override
    public String toString() {
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put(NAME, getName());
            JSONArray array = new JSONArray();
            for (int i = 0; i < directories.size(); i++) {
                array.put(directories.get(i));
            }
            jsonObject.put(REAL_DIRS, array);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return jsonObject.toString();
    }

    public static class Util {

        public interface OnCreateVirtualAlbumCallback {
            void onVirtualAlbumCreated(VirtualAlbum virtualAlbum);
        }

        public static AlertDialog getCreateVirtualAlbumDialog(final Context context, final OnCreateVirtualAlbumCallback callback) {
            @SuppressLint("InflateParams")
            View dialogLayout = LayoutInflater.from(context)
                    .inflate(R.layout.input_dialog_layout, null, false);

            final EditText editText = dialogLayout.findViewById(R.id.edit_text);

            Theme theme = Settings.getInstance(context).getThemeInstance(context);

            final AlertDialog dialog = new AlertDialog.Builder(context, theme.getDialogThemeRes())
                    .setTitle(R.string.create_virtual_album)
                    .setView(dialogLayout)
                    .setPositiveButton(R.string.create, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int which) {
                            String name = editText.getText().toString();
                            ArrayList<VirtualAlbum> virtualAlbums = Provider.getVirtualAlbums(context);
                            for (int i = 0; i < virtualAlbums.size(); i++) {
                                if (virtualAlbums.get(i).getName().equals(name)) {
                                    Toast.makeText(context, R.string.virtual_album_different_name, Toast.LENGTH_SHORT).show();
                                    return;
                                }
                            }
                            VirtualAlbum virtualAlbum = new VirtualAlbum(name, new String[]{});
                            Provider.addVirtualAlbum(context, virtualAlbum);
                            Provider.saveVirtualAlbums(context);
                            callback.onVirtualAlbumCreated(virtualAlbum);
                            String message = context.getString(R.string.virtual_album_created, name);
                            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
                        }
                    })
                    .setNegativeButton(R.string.cancel, null)
                    .create();
            //noinspection ConstantConditions
            dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
            return dialog;
        }

        public static AlertDialog getAddToVirtualAlbumDialog(final Context context, final String path) {
            ArrayList virtualAlbums = Provider.getVirtualAlbums(context);
            if (virtualAlbums.size() == 0) {
                return getCreateVirtualAlbumDialog(context, new OnCreateVirtualAlbumCallback() {
                    @Override
                    public void onVirtualAlbumCreated(VirtualAlbum virtualAlbum) {
                        virtualAlbum.addDirectory(path);
                        Provider.saveVirtualAlbums(context);
                    }
                });
            }

            @SuppressLint("InflateParams")
            View dialogLayout = LayoutInflater.from(context)
                    .inflate(R.layout.add_to_virtual_album_dialog, null, false);

            Theme theme = Settings.getInstance(context).getThemeInstance(context);

            final AlertDialog dialog = new AlertDialog.Builder(context, theme.getDialogThemeRes())
                    .setTitle(R.string.add_path_to_virtual_album)
                    .setView(dialogLayout)
                    .setNeutralButton(R.string.create_virtual_album, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int which) {
                            dialogInterface.dismiss();
                            AlertDialog dialog = getCreateVirtualAlbumDialog(context, new OnCreateVirtualAlbumCallback() {
                                @Override
                                public void onVirtualAlbumCreated(VirtualAlbum virtualAlbum) {
                                    virtualAlbum.addDirectory(path);
                                    Provider.saveVirtualAlbums(context);
                                }
                            });
                            dialog.show();
                        }
                    })
                    .setNegativeButton(R.string.cancel, null)
                    .create();
            //noinspection ConstantConditions
            dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);

            RecyclerView recyclerView = dialogLayout.findViewById(R.id.recyclerView);
            recyclerView.setLayoutManager(new LinearLayoutManager(context));
            recyclerView.setAdapter(new RecyclerViewAdapter(context,
                    new RecyclerViewAdapter.OnVirtualAlbumSelectedCallback() {
                        @Override
                        public void onVirtualAlbumSelected(VirtualAlbum virtualAlbum) {
                            virtualAlbum.addDirectory(path);
                            Provider.saveVirtualAlbums(context);
                            dialog.dismiss();
                            String message = context
                                    .getString(R.string.added_path_to_virtual_album, virtualAlbum.getName());
                            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
                        }
                    }));

            final View scrollIndicatorTop = dialogLayout.findViewById(R.id.scroll_indicator_top);
            final View scrollIndicatorBottom = dialogLayout.findViewById(R.id.scroll_indicator_bottom);
            recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
                    scrollIndicatorTop.setVisibility(
                            recyclerView.canScrollVertically(-1) ?
                                    View.VISIBLE : View.INVISIBLE);

                    scrollIndicatorBottom.setVisibility(
                            recyclerView.canScrollVertically(1) ?
                                    View.VISIBLE : View.INVISIBLE);
                }
            });

            return dialog;
        }

        private static class RecyclerViewAdapter extends RecyclerView.Adapter {

            private ArrayList<VirtualAlbum> virtualAlbums;

            private OnVirtualAlbumSelectedCallback callback;

            public interface OnVirtualAlbumSelectedCallback {
                void onVirtualAlbumSelected(VirtualAlbum virtualAlbum);
            }

            private static class VirtualAlbumHolder extends RecyclerView.ViewHolder {

                private OnVirtualAlbumSelectedCallback callback;

                TextView textView;

                VirtualAlbumHolder(View itemView, OnVirtualAlbumSelectedCallback callback) {
                    super(itemView);
                    this.callback = callback;

                    Context context = itemView.getContext();
                    Theme theme = Settings.getInstance(context).getThemeInstance(context);
                    int accentColor = theme.getAccentColor(context);

                    textView = itemView.findViewById(R.id.text);
                    textView.setTextColor(accentColor);
                    ImageView folderIndicator = itemView.findViewById(R.id.folder_indicator);
                    folderIndicator.setColorFilter(accentColor, PorterDuff.Mode.SRC_IN);

                    itemView.findViewById(R.id.delete_button).setVisibility(View.GONE);
                }

                void bind(final VirtualAlbum album) {
                    textView.setText(album.getName());
                    itemView.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            callback.onVirtualAlbumSelected(album);
                        }
                    });
                }
            }

            RecyclerViewAdapter(Context context, OnVirtualAlbumSelectedCallback callback) {
                virtualAlbums = Provider.getVirtualAlbums(context);
                this.callback = callback;
            }

            @Override
            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                View v = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.virtual_album_cover, parent, false);
                return new VirtualAlbumHolder(v, callback);
            }

            @Override
            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
                VirtualAlbum virtualAlbum = virtualAlbums.get(position);
                ((VirtualAlbumHolder) holder).bind(virtualAlbum);
            }

            @Override
            public int getItemCount() {
                return virtualAlbums.size();
            }
        }
    }
} public abstract class Theme {

    static final int BASE_DARK = 0;
    static final int BASE_LIGHT = 1;

    public abstract int getBaseTheme();

    public boolean isBaseLight() {
        return getBaseTheme() == BASE_LIGHT;
    }

    /*flags*/
    public abstract boolean darkStatusBarIcons();

    public abstract boolean elevatedToolbar();

    public abstract boolean statusBarOverlay();

    public abstract boolean darkStatusBarIconsInSelectorMode();

    /*colors*/
    public abstract int getBackgroundColorRes();

    public abstract int getToolbarColorRes();

    public abstract int getTextColorPrimaryRes();

    public abstract int getTextColorSecondaryRes();

    public abstract int getAccentColorRes();

    public abstract int getAccentColorLightRes();

    public abstract int getAccentTextColorRes();


    public int getBackgroundColor(Context context) {
        return getColor(context, getBackgroundColorRes());
    }

    public int getToolbarColor(Context context) {
        return getColor(context, getToolbarColorRes());
    }

    public int getTextColorPrimary(Context context) {
        return getColor(context, getTextColorPrimaryRes());
    }

    public int getTextColorSecondary(Context context) {
        return getColor(context, getTextColorSecondaryRes());
    }

    public int getAccentColor(Context context) {
        return getColor(context, getAccentColorRes());
    }

    public int getAccentColorLight(Context context) {
        return getColor(context, getAccentColorLightRes());
    }

    public int getAccentTextColor(Context context) {
        return getColor(context, getAccentTextColorRes());
    }

    private static int getColor(Context context, int res) {
        return ContextCompat.getColor(context, res);
    }


    /*Dialog theme*/
    public abstract int getDialogThemeRes();

    @Override
    public boolean equals(Object obj) {
        Class c = this.getClass();
        return c.isInstance(obj);
    }
} public class SelectorModeManager {

    private static final String SELECTOR_MODE_ACTIVE = "SELECTOR_MODE_ACTIVE";
    private static final String SELECTED_ITEMS_PATHS = "SELECTED_ITEMS_PATHS";

    private boolean selectorModeActive = false;
    private ArrayList<String> selected_items_paths;

    private ArrayList<Callback> callbacks;

    //to handle backPressed in SelectorMode
    private OnBackPressedCallback onBackPressedCallback;

    //SelectorMode Callbacks
    public interface OnBackPressedCallback {
        void cancelSelectorMode();
    }

    public interface Callback {
        void onSelectorModeEnter();

        void onSelectorModeExit();

        void onItemSelected(int selectedItemCount);
    }

    public static class SimpleCallback implements Callback {
        @Override
        public void onSelectorModeEnter() {

        }

        @Override
        public void onSelectorModeExit() {

        }

        @Override
        public void onItemSelected(int selectedItemCount) {

        }
    }

    public void onSelectorModeEnter() {
        if (callbacks != null) {
            for (int i = 0; i < callbacks.size(); i++) {
                callbacks.get(i).onSelectorModeEnter();
            }
        }
    }

    private void onSelectorModeExit() {
        if (callbacks != null) {
            for (int i = 0; i < callbacks.size(); i++) {
                callbacks.get(i).onSelectorModeExit();
            }
        }
    }

    public void onItemSelected(int selectedItemCount) {
        if (callbacks != null) {
            for (int i = 0; i < callbacks.size(); i++) {
                callbacks.get(i).onItemSelected(selectedItemCount);
            }
        }
    }

    public SelectorModeManager(Bundle savedState) {
        if (savedState.containsKey(SELECTOR_MODE_ACTIVE)) {
            setSelectorMode(Boolean.parseBoolean(savedState.getString(SELECTOR_MODE_ACTIVE)));
        }

        if (isSelectorModeActive() && savedState.containsKey(SELECTED_ITEMS_PATHS)) {
            selected_items_paths = savedState.getStringArrayList(SELECTED_ITEMS_PATHS);
        } else {
            selected_items_paths = new ArrayList<>();
        }
    }

    public SelectorModeManager() {
        selected_items_paths = new ArrayList<>();
    }

    public boolean isItemSelected(String path) {
        return selected_items_paths.contains(path);
    }

    public void setSelectorMode(boolean selectorMode) {
        this.selectorModeActive = selectorMode;
        if (selectorMode) {
            onSelectorModeEnter();
        } else {
            onSelectorModeExit();
        }
    }

    public boolean isSelectorModeActive() {
        return selectorModeActive;
    }

    public boolean onItemSelect(String path) {
        boolean selected = addOrRemovePathFromList(selected_items_paths, path);
        onItemSelected(getSelectedItemCount());
        return selected;
    }

    public void selectAll(String[] paths) {
        ArrayList<String> itemsToSelect = new ArrayList<>();
        for (int i = 0; i < paths.length; i++) {
            if (!selected_items_paths.contains(paths[i])) {
                itemsToSelect.add(paths[i]);
            }
        }
        selected_items_paths.addAll(itemsToSelect);
        if (callbacks != null) {
            for (int i = 0; i < callbacks.size(); i++) {
                callbacks.get(i).onItemSelected(selected_items_paths.size());
            }
        }
    }

    public int getSelectedItemCount() {
        return selected_items_paths.size();
    }

    public String[] createStringArray(Activity context) {
        ArrayList<String> selected_items_paths = sortStringArray(context, this.selected_items_paths);
        return createStringArray(selected_items_paths);
    }

    public void clearList() {
        this.selected_items_paths = new ArrayList<>();
    }

    public void saveInstanceState(Bundle outState) {
        boolean active = isSelectorModeActive();
        outState.putString(SELECTOR_MODE_ACTIVE, String.valueOf(active));
        if (active) {
            outState.putStringArrayList(SELECTED_ITEMS_PATHS, selected_items_paths);
        }
    }

    public SelectorModeManager addCallback(Callback callback) {
        if (callbacks == null) {
            callbacks = new ArrayList<>();
        }
        callbacks.add(callback);

        if (isSelectorModeActive()) {
            callback.onSelectorModeEnter();
            callback.onItemSelected(getSelectedItemCount());
        }
        return this;
    }

    public ArrayList<Callback> getCallbacks() {
        return callbacks;
    }

    public boolean callbacksAttached() {
        return callbacks != null && callbacks.size() > 0;
    }

    public void setOnBackPressedCallback(OnBackPressedCallback onBackPressedCallback) {
        this.onBackPressedCallback = onBackPressedCallback;
    }

    public boolean onBackPressedCallbackAlreadySet() {
        return onBackPressedCallback != null;
    }

    public boolean onBackPressed() {
        if (onBackPressedCallback != null && isSelectorModeActive()) {
            onBackPressedCallback.cancelSelectorMode();
            return true;
        } else {
            return false;
        }
    }


    //Util methods
    private static boolean addOrRemovePathFromList(ArrayList<String> arr, String item) {
        //find out if item is in arr
        boolean itemIsInList = false;
        for (int i = 0; i < arr.size(); i++) {
            if (arr.get(i).equals(item)) {
                itemIsInList = true;
                break;
            }
        }

        if (itemIsInList) {
            //remove item
            arr.remove(item);
            return false;
        } else {
            //add item
            arr.add(item);
            return true;
        }
    }

    public static AlbumItem[] createAlbumItemArray(String[] arr) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.addAll(Arrays.asList(arr));
        return createAlbumItemArray(arrayList);
    }

    private static AlbumItem[] createAlbumItemArray(ArrayList<String> arr) {
        AlbumItem[] albumItems = new AlbumItem[arr.size()];
        for (int i = 0; i < arr.size(); i++) {
            albumItems[i] = AlbumItem.getInstance(arr.get(i));
        }
        return albumItems;
    }

    private static String[] createStringArray(ArrayList<String> arr) {
        String[] paths = new String[arr.size()];
        for (int i = 0; i < arr.size(); i++) {
            paths[i] = arr.get(i);
        }
        return paths;
    }

    private static ArrayList<String> sortStringArray(Activity context, ArrayList<String> paths) {
        /*ArrayList<AlbumItem> albumItems = new ArrayList<>();
        for (int i = 0; i < paths.size(); i++) {
            albumItems.add(AlbumItem.getInstance(paths.get(i)));
        }
        int sortBy = Settings.getInstance(context).sortAlbumBy();
        SortUtil.sort(albumItems, sortBy);

        ArrayList<String> sortedPaths = new ArrayList<>();
        for (int i = 0; i < albumItems.size(); i++) {
            sortedPaths.add(albumItems.get(i).getPath());
        }

        return sortedPaths;*/
        return paths;
    }
} public class AlbumAdapter extends AbstractRecyclerViewAdapter<Album> {

    @SuppressWarnings("FieldCanBeLocal")
    private final int VIEW_TYPE_PHOTO = 1;
    private final int VIEW_TYPE_GIF = 2;
    private final int VIEW_TYPE_VIDEO = 3;
    private final int VIEW_TYPE_RAW = 4;

    private DragSelectTouchListener dragSelectTouchListener;

    public AlbumAdapter(SelectorModeManager.Callback callback, final RecyclerView recyclerView,
                        final Album album, boolean pick_photos) {
        super(pick_photos);

        setData(album);
        setSelectorModeManager(new SelectorModeManager());
        if (callback != null) {
            getSelectorManager().addCallback(callback);
        }

        if (pick_photos) {
            getSelectorManager().setSelectorMode(true);
            if (callback != null) {
                callback.onSelectorModeEnter();
            }
        }

        //disable default change animation
        ((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);

        if (callback != null && dragSelectEnabled()) {
            dragSelectTouchListener = new DragSelectTouchListener()
                    .withSelectListener(new DragSelectTouchListener.OnDragSelectListener() {
                        @Override
                        public void onSelectChange(int start, int end, boolean isSelected) {
                            for (int i = start; i <= end; i++) {
                                getSelectorManager().onItemSelect(getData()
                                        .getAlbumItems().get(i).getPath());
                                //update ViewHolder
                                notifyItemChanged(i);
                            }
                        }
                    });
            recyclerView.addOnItemTouchListener(dragSelectTouchListener);
        }
    }

    @Override
    public int getItemViewType(int position) {
        AlbumItem albumItem = getData().getAlbumItems().get(position);
        if (albumItem instanceof RAWImage) {
            return VIEW_TYPE_RAW;
        } else if (albumItem instanceof Gif) {
            return VIEW_TYPE_GIF;
        } else if (albumItem instanceof Photo) {
            return VIEW_TYPE_PHOTO;
        } else if (albumItem instanceof Video) {
            return VIEW_TYPE_VIDEO;
        }
        return -1;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return onCreateViewHolder(parent, viewType, R.layout.albumitem_cover);
    }

    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType, int layoutRes) {
        View v = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false);
        switch (viewType) {
            case VIEW_TYPE_RAW:
                return new RAWImageHolder(v);
            case VIEW_TYPE_GIF:
                return new GifViewHolder(v);
            case VIEW_TYPE_VIDEO:
                return new VideoViewHolder(v);
            case VIEW_TYPE_PHOTO:
                return new PhotoViewHolder(v);
            default:
                break;
        }
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
        final AlbumItem albumItem = getData().getAlbumItems().get(position);

        if (!albumItem.equals(((AlbumItemHolder) holder).getAlbumItem())) {
            ((AlbumItemHolder) holder).setAlbumItem(albumItem);
        }

        boolean selected = getSelectorManager().isItemSelected(albumItem.getPath());

        ((AlbumItemHolder) holder).setSelected(selected);

        holder.itemView.setTag(albumItem.getPath());

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (getSelectorMode()) {
                    onItemSelected((AlbumItemHolder) holder);
                } else {
                    Log.d("AlbumAdapter", "onClick: " + getData().getPath());
                    Context context = holder.itemView.getContext();
                    Intent intent = new Intent(context, ItemActivity.class);
                    intent.putExtra(ItemActivity.ALBUM_ITEM, albumItem);
                    intent.putExtra(ItemActivity.ALBUM_PATH, getData().getPath());
                    intent.putExtra(ItemActivity.ITEM_POSITION, getData().getAlbumItems().indexOf(albumItem));

                    if (Settings.getInstance(context).showAnimations()) {
                        ActivityOptionsCompat options =
                                ActivityOptionsCompat.makeSceneTransitionAnimation(
                                        (Activity) context, holder.itemView.findViewById(R.id.image),
                                        albumItem.getPath());
                        ActivityCompat.startActivityForResult((Activity) context, intent,
                                ItemActivity.VIEW_IMAGE, options.toBundle());
                    } else {
                        ActivityCompat.startActivityForResult((Activity) context, intent,
                                ItemActivity.VIEW_IMAGE, null);
                    }
                }
            }
        });

        if (getSelectorManager().callbacksAttached()) {
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    if (!getSelectorMode()) {
                        setSelectorMode(true);
                        clearSelectedItemsList();
                    }

                    onItemSelected((AlbumItemHolder) holder);

                    if (dragSelectEnabled()) {
                        //notify DragSelectTouchListener
                        boolean selected = getSelectorManager().isItemSelected(albumItem.getPath());
                        if (selected) {
                            int position = getData().getAlbumItems().indexOf(albumItem);
                            dragSelectTouchListener.startDragSelection(position);
                        }
                    }
                    return true;
                }
            });
        }
    }

    public boolean isSelectorModeActive() {
        return getSelectorMode() && !pickPhotos();
    }

    public void restoreSelectedItems() {
        //notify AlbumActivity
        getSelectorManager().onSelectorModeEnter();

        for (int i = 0; i < getData().getAlbumItems().size(); i++) {
            if (getSelectorManager().isItemSelected(getData().getAlbumItems().get(i).getPath())) {
                notifyItemChanged(i);
            }
        }

        getSelectorManager().onItemSelected(getSelectorManager().getSelectedItemCount());
    }

    private void checkForNoSelectedItems() {
        if (getSelectedItemCount() == 0 && !pickPhotos()) {
            cancelSelectorMode(null);
        }
    }

    private int getSelectedItemCount() {
        return getSelectorManager().getSelectedItemCount();
    }

    private void onItemSelected(AlbumItemHolder holder) {
        boolean selected = getSelectorManager().onItemSelect(holder.albumItem.getPath());
        holder.setSelected(selected);
        checkForNoSelectedItems();
    }

    public String[] cancelSelectorMode(Activity context) {
        setSelectorMode(false);
        //update ui
        for (int i = 0; i < getData().getAlbumItems().size(); i++) {
            if (getSelectorManager().isItemSelected(getData().getAlbumItems().get(i).getPath())) {
                notifyItemChanged(i);
            }
        }
        //generate paths array
        String[] paths;
        if (context != null) {
            paths = getSelectorManager().createStringArray(context);
        } else {
            paths = null;
        }
        //clear manager list
        clearSelectedItemsList();
        return paths;
    }

    public boolean onBackPressed() {
        if (getSelectorMode() && !pickPhotos()) {
            cancelSelectorMode(null);
            return true;
        }
        return false;
    }

    private boolean getSelectorMode() {
        return getSelectorManager().isSelectorModeActive();
    }

    private void setSelectorMode(boolean activate) {
        getSelectorManager().setSelectorMode(activate);
    }

    public boolean dragSelectEnabled() {
        return true;
    }

    private void clearSelectedItemsList() {
        getSelectorManager().clearList();
    }

    @Override
    public int getItemCount() {
        return getData() != null ? getData().getAlbumItems().size() : 0;
    }
} public class Album
        implements Parcelable, SortUtil.Sortable {

    private static final int NOT_HIDDEN = 1;
    private static final int HIDDEN = 2;

    private ArrayList<AlbumItem> albumItems;
    private String path;

    private int hidden = -1;
    public boolean excluded;
    public boolean pinned;

    public Album() {
        albumItems = new ArrayList<>();

        excluded = false;
        pinned = false;
    }

    public Album setPath(String path) {
        this.path = path;

        excluded = Provider.isDirExcluded(getPath(),
                Provider.getExcludedPaths());

        pinned = Provider.isAlbumPinned(getPath(),
                Provider.getPinnedPaths());

        return this;
    }

    public boolean isHidden() {
        if (hidden != -1) {
            return hidden == HIDDEN;
        }

        if (getName().startsWith(".")) {
            hidden = HIDDEN;
            return true;
        } else {
            File dir = new File(getPath());
            File[] files = dir.listFiles();
            if (files != null) {
                for (int i = 0; i < files.length; i++) {
                    if (files[i].getName().equals(MediaProvider.FILE_TYPE_NO_MEDIA)) {
                        hidden = HIDDEN;
                        return true;
                    }
                }
            }
        }
        hidden = NOT_HIDDEN;
        return false;
    }

    public String getPath() {
        return path;
    }

    @Override
    public String getName() {
        return new File(getPath()).getName();
    }

    @Override
    public long getDate() {
        long newestItem = -1;
        for (int i = 0; i < albumItems.size(); i++) {
            if (albumItems.get(i).getDate() > newestItem) {
                newestItem = albumItems.get(i).getDate();
            }
        }
        return newestItem;
    }

    @Override
    public boolean pinned() {
        return pinned;
    }

    public ArrayList<AlbumItem> getAlbumItems() {
        return albumItems;
    }

    @SuppressWarnings("unchecked")
    public Album(Parcel parcel) {
        path = parcel.readString();
        hidden = parcel.readInt();
        albumItems = new ArrayList<>();
        albumItems = parcel.createTypedArrayList(AlbumItem.CREATOR);
    }

    @Override
    public String toString() {
        return getName() + ": " + getAlbumItems().toString();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(path);
        parcel.writeInt(hidden);
        AlbumItem[] albumItems = new AlbumItem[this.albumItems.size()];
        for (int k = 0; k < albumItems.length; k++) {
            albumItems[k] = this.albumItems.get(k);
        }
        parcel.writeTypedArray(albumItems, 0);
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public Album createFromParcel(Parcel parcel) {
            return new Album(parcel);
        }

        @Override
        public Album[] newArray(int i) {
            return new Album[i];
        }
    };
} public abstract class AlbumItem
        implements Parcelable, SortUtil.Sortable {

    private static final int PHOTO = 1;
    private static final int GIF = 2;
    private static final int VIDEO = 3;
    private static final int RAW = 4;

    private String name;
    private String path;
    private Uri uri;
    private long dateTaken;
    private int[] imageDimens;
    private List<String> tags;

    public boolean error = false;
    public boolean isSharedElement = false;
    public boolean hasFadedIn = false;

    //factory method
    public static AlbumItem getInstance(String path) {
        AlbumItem albumItem = null;
        if (MediaType.isGif(path)) {
            albumItem = new Gif();
        } else if (MediaType.isRAWImage(path)) {
            albumItem = new RAWImage();
        } else if (MediaType.isImage(path)) {
            albumItem = new Photo();
        } else if (MediaType.isVideo(path)) {
            albumItem = new Video();
        }

        if (albumItem != null) {
            albumItem.setPath(path).setName(new File(path).getName());
        }
        return albumItem;
    }

    public static AlbumItem getInstance(Context context, String path) {
        if (MediaType.isVideo(path) && !Settings.getInstance(context).showVideos()) {
            return null;
        }
        return getInstance(path);
    }

    public static AlbumItem getInstance(final Context context, Uri uri) {
        if (uri == null) {
            return null;
        }

        String mimeType = MediaType.getMimeType(context, uri);
        return getInstance(context, uri, mimeType);
    }

    public static AlbumItem getInstance(final Context context, Uri uri, String mimeType) {
        if (uri == null) {
            return null;
        }

        AlbumItem albumItem = null;
        if (MediaType.checkGifMimeType(mimeType)) {
            albumItem = new Gif();
        } else if (MediaType.checkRAWMimeType(mimeType)) {
            albumItem = new RAWImage();
        } else if (MediaType.checkImageMimeType(mimeType)) {
            albumItem = new Photo();
        } else if (MediaType.checkVideoMimeType(mimeType)) {
            albumItem = new Video();
        }

        if (albumItem != null) {
            albumItem.setPath("N/A");
            albumItem.setUri(uri);

            //retrieve file name
            String filename = InfoUtil.retrieveFileName(context, uri);
            albumItem.setName(filename != null ? filename : "");
        }
        return albumItem;
    }

    public AlbumItem() {
        name = "";
        path = "";
        dateTaken = -1;
    }

    public AlbumItem setName(String name) {
        this.name = name;
        return this;
    }

    private AlbumItem setPath(String path) {
        this.path = path;
        return this;
    }

    @Override
    public String getName() {
        return name;
    }

    public String getPath() {
        return path;
    }

    public void setDate(long dateTaken) {
        this.dateTaken = dateTaken;
    }

    @Override
    public long getDate() {
        if (dateTaken != -1) {
            return dateTaken;
        }

        return new File(getPath()).lastModified();
    }

    @Override
    public boolean pinned() {
        return false;
    }

    @SuppressWarnings("unused")
    public void preloadUri(final Context context) {
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                getUri(context);
            }
        });
    }

    public void setUri(Uri uri) {
        this.uri = uri;
    }

    public Uri getUri(Context context) {
        if (uri == null) {
            setUri(StorageUtil.getContentUri(context, this));
        }
        return uri;
    }

    public int[] getImageDimens(Context context) {
        if (imageDimens == null) {
            imageDimens = retrieveImageDimens(context);
        }
        return new int[]{this.imageDimens[0], this.imageDimens[1]};
    }

    public abstract int[] retrieveImageDimens(Context context);

    public List<String> getTags(Context context) {
        if (tags == null) {
            retrieveTags(context);
        }
        return tags;
    }

    private void retrieveTags(Context context) {
        // TODO: implement
        tags = new LinkedList<>();
    }

    // returns whether the Operation was successful
    public boolean addTag(Context context, String tag) {
        // TODO: implement
        return false;
    }

    // returns whether the Operation was successful
    public boolean removeTag(Context context, String tag) {
        // TODO: implement
        return false;
    }

    AlbumItem(Parcel parcel) {
        this.name = parcel.readString();
        this.path = parcel.readString();
        this.error = Boolean.parseBoolean(parcel.readString());
        this.uri = Uri.parse(parcel.readString());
    }

    @Override
    public String toString() {
        return getName() + ", " + getPath();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        int k;
        if (this instanceof RAWImage) {
            k = RAW;
        } else if (this instanceof Gif) {
            k = GIF;
        } else if (this instanceof Video) {
            k = VIDEO;
        } else {
            k = PHOTO;
        }
        parcel.writeInt(k);
        parcel.writeString(name);
        parcel.writeString(path);
        parcel.writeString(String.valueOf(error));
        parcel.writeString(String.valueOf(uri));
    }

    @SuppressWarnings("WeakerAccess")
    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        @Override
        public AlbumItem createFromParcel(Parcel parcel) {
            switch (parcel.readInt()) {
                case VIDEO:
                    return new Video(parcel);
                case GIF:
                    return new Gif(parcel);
                case RAW:
                    return new RAWImage(parcel);
                default:
                    return new Photo(parcel);
            }
        }

        public AlbumItem[] newArray(int i) {
            return new AlbumItem[i];
        }
    };

    public static AlbumItem getErrorItem() {
        AlbumItem albumItem = new Photo();
        albumItem.setPath("ERROR").setName("ERROR");
        return albumItem;
    }

    public abstract String getType(Context context);

    public Key getGlideSignature() {
        File file = new File(getPath());
        String lastModified = String.valueOf(file.lastModified());
        return new ObjectKey(lastModified);
    }

    public RequestOptions getGlideRequestOptions(Context context) {
        return new RequestOptions()
                .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
                .error(Util.getErrorPlaceholder(context))
                .signature(getGlideSignature());
    }
} public abstract class FileOperation extends IntentService implements Parcelable {

    private static final int NOTIFICATION_ID = 6;

    public static final String RESULT_DONE = "us.koller.cameraroll.data.FileOperations.FileOperation.RESULT_DONE";
    public static final String FAILED = "us.koller.cameraroll.data.FileOperations.FileOperation.FAILED";
    public static final String NEED_REMOVABLE_STORAGE_PERMISSION = "us.koller.cameraroll.data.FileOperations.FileOperation.NEED_REMOVABLE_STORAGE_PERMISSION";
    public static final String TYPE = "TYPE";

    public static final int EMPTY = 0;
    public static final int MOVE = 1;
    public static final int COPY = 2;
    public static final int DELETE = 3;
    public static final int NEW_DIR = 4;
    public static final int RENAME = 5;

    public static final String WORK_INTENT = "WORK_INTENT";
    public static final String FILES = "FILES";
    public static final String TARGET = "TARGET";
    public static final String NEW_FILE_NAME = "NEW_FILE_NAME";
    public static final String REMOVABLE_STORAGE_TREE_URI = "REMOVABLE_STORAGE_TREE_URI";

    private NotificationCompat.Builder notifBuilder;

    private ArrayList<String> pathsToScan;

    public FileOperation() {
        super("");

        pathsToScan = new ArrayList<>();
    }

    @Override
    protected void onHandleIntent(Intent workIntent) {
        notifBuilder = createNotificationBuilder();
        notifBuilder.setProgress(1, 0, false);
        Notification notification = notifBuilder.build();
        startForeground(NOTIFICATION_ID, notification);
        NotificationManager manager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (manager != null) {
            manager.notify(NOTIFICATION_ID, notification);
        }

        ContentObserver.selfChange = true;

        execute(workIntent);

        if (autoSendDoneBroadcast()) {
            if (pathsToScan.size() > 0) {
                onProgress(-1, -1);
                scanPaths(getApplicationContext(), new Util.MediaScannerCallback() {
                    @Override
                    public void onAllPathsScanned() {
                        sendDoneBroadcast();
                        stopForeground(true);

                    }
                });
            } else {
                sendDoneBroadcast();
                stopForeground(true);
            }
        } else {
            ContentObserver.selfChange = false;
            stopForeground(true);
        }
    }

    private NotificationCompat.Builder createNotificationBuilder() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel(getApplicationContext());
        }
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this,
                getString(R.string.file_op_channel_id))
                .setContentTitle(getNotificationTitle());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            builder.setSmallIcon(getNotificationSmallIconRes());
        }
        return builder;
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private static void createNotificationChannel(Context context) {
        NotificationManager mNotificationManager =
                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel mChannel = new NotificationChannel(
                context.getString(R.string.file_op_channel_id),
                context.getString(R.string.file_op_channel_name),
                NotificationManager.IMPORTANCE_LOW);
        mChannel.setDescription(context.getString(R.string.file_op_channel_description));
        if (mNotificationManager != null) {
            mNotificationManager.createNotificationChannel(mChannel);
        }
    }

    private NotificationCompat.Builder getNotificationBuilder() {
        return notifBuilder;
    }

    abstract String getNotificationTitle();

    public abstract int getNotificationSmallIconRes();

    public abstract void execute(Intent workIntent);

    public void addPathToScan(String path) {
        pathsToScan.add(path);
    }

    public void addPathsToScan(List<String> paths) {
        pathsToScan.addAll(paths);
    }

    ArrayList<String> getPathsToScan() {
        return pathsToScan;
    }

    public boolean autoSendDoneBroadcast() {
        return true;
    }

    public void sendDoneBroadcast() {
        ContentObserver.selfChange = false;
        showToast(getString(R.string.done));
        Intent intent = getDoneIntent();
        sendLocalBroadcast(intent);
    }

    public void sendFailedBroadcast(Intent workIntent, String path) {
        Intent intent = new Intent(FAILED);
        intent.putExtra(FILES, path);
        intent.putExtra(WORK_INTENT, workIntent);
        sendLocalBroadcast(intent);
    }

    public void requestPermissionForRemovableStorageBroadcast(Intent workIntent) {
        Intent intent = new Intent(NEED_REMOVABLE_STORAGE_PERMISSION);
        intent.putExtra(WORK_INTENT, workIntent);
        sendLocalBroadcast(intent);
    }

    public Intent getDoneIntent() {
        Intent intent = new Intent(RESULT_DONE);
        intent.putExtra(TYPE, this.getType());
        return intent;
    }

    public void sendLocalBroadcast(Intent intent) {
        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
    }

    public abstract int getType();

    @Override
    public int describeContents() {
        return 0;
    }

    @SuppressWarnings("unused")
    public void onProgress(final int progress, final int totalNumber) {
        NotificationCompat.Builder notifBuilder = getNotificationBuilder();
        if (progress >= 0) {
            notifBuilder.setProgress(totalNumber, progress, false);
        } else {
            notifBuilder.setProgress(0, 0, true);
        }
        //notifBuilder.setProgress(0, 0, true);
        NotificationManager manager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (manager != null) {
            manager.notify(NOTIFICATION_ID, notifBuilder.build());
        }
    }

    public void showToast(final String message) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getBaseContext(), message, Toast.LENGTH_SHORT).show();
            }
        });
    }

    public void runOnUiThread(Runnable r) {
        Handler handler = new Handler(Looper.getMainLooper());
        handler.post(r);
    }

    public static File_POJO[] getFiles(Intent workIntent) {
        Parcelable[] parcelables = workIntent.getParcelableArrayExtra(FILES);
        return File_POJO.generateArray(parcelables);
    }

    public static Intent getDefaultIntent(Context context, int action, File_POJO[] files) {
        String actionString = Util.getActionString(context, EMPTY);
        Class service = null;
        switch (action) {
            case COPY:
                service = Copy.class;
                actionString = Util.getActionString(context, COPY);
                break;
            case MOVE:
                service = Move.class;
                actionString = Util.getActionString(context, MOVE);
                break;
            case DELETE:
                service = Delete.class;
                actionString = Util.getActionString(context, DELETE);
                break;
            case NEW_DIR:
                service = NewDirectory.class;
                actionString = Util.getActionString(context, NEW_DIR);
                break;
            case RENAME:
                service = Rename.class;
                actionString = Util.getActionString(context, RENAME);
                break;
            default:
                break;
        }
        if (service != null) {
            return new Intent(context, service)
                    .setAction(actionString)
                    .putExtra(FileOperation.FILES, files);
        }
        return new Intent();
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(getType());
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        @Override
        public FileOperation createFromParcel(Parcel parcel) {
            switch (parcel.readInt()) {
                case MOVE:
                    return new Move();
                case COPY:
                    return new Copy();
                case DELETE:
                    return new Delete();
                case NEW_DIR:
                    return new NewDirectory();
                case RENAME:
                    return new Rename();
                default:
                    return null;
            }
        }

        public AlbumItem[] newArray(int i) {
            return new AlbumItem[i];
        }
    };


    public Uri getTreeUri(Intent workIntent, String path) {
        Log.d("FileOperation", "getTreeUri");
        Uri treeUri;
        String treeUriExtra = workIntent.getStringExtra(FileOperation.REMOVABLE_STORAGE_TREE_URI);
        if (treeUriExtra != null) {
            treeUri = Uri.parse(treeUriExtra);
        } else {
            Settings s = Settings.getInstance(getApplicationContext());
            treeUri = s.getRemovableStorageTreeUri();
        }

        if (path != null) {
            //check if path is child of the treeUri
            DocumentFile file = StorageUtil.parseDocumentFile(getApplicationContext(), treeUri, new File(path));
            if (file != null) {
                return treeUri;
            } else {
                requestPermissionForRemovableStorageBroadcast(workIntent);
            }
        } else {
            return treeUri;
        }
        return null;
    }

    void scanPaths(final Context context, final Util.MediaScannerCallback callback) {
        String[] paths = new String[pathsToScan.size()];
        pathsToScan.toArray(paths);
        Util.scanPaths(context, paths, callback);
    }

    public static class Util {
        public static IntentFilter getIntentFilter(IntentFilter filter) {
            filter.addAction(FileOperation.RESULT_DONE);
            filter.addAction(FileOperation.FAILED);
            return filter;
        }

        public static String getActionString(Context context, int type) {
            switch (type) {
                case EMPTY:
                    return "empty";
                case MOVE:
                    return context.getString(R.string.move);
                case COPY:
                    return context.getString(R.string.copy);
                case DELETE:
                    return context.getString(R.string.delete);
                case NEW_DIR:
                    return context.getString(R.string.new_folder);
                case RENAME:
                    return context.getString(R.string.rename);
                default:
                    break;
            }
            return "";
        }

        public static int getActionInt(Context context, String action) {
            if (action.equals(context.getString(R.string.move))) {
                return MOVE;
            } else if (action.equals(context.getString(R.string.copy))) {
                return COPY;
            } else if (action.equals(context.getString(R.string.delete))) {
                return DELETE;
            }
            return EMPTY;
        }

        static boolean isOnRemovableStorage(String path) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                File file = new File(path);
                try {
                    if (file.exists() && Environment.isExternalStorageRemovable(file)) {
                        return true;
                    }
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                }
            }
            return false;
        }

        public static ArrayList<String> getAllChildPaths(ArrayList<String> paths, String path) {
            File file = new File(path);
            if (file.exists()) {
                if (file.isDirectory()) {
                    File[] children = file.listFiles();
                    for (int i = 0; i < children.length; i++) {
                        getAllChildPaths(paths, children[i].getPath());
                    }
                } else {
                    paths.add(path);
                }
            }
            return paths;
        }

        public interface MediaScannerCallback {
            void onAllPathsScanned();
        }

        public static void scanPaths(final Context context, final String[] paths, final MediaScannerCallback callback) {
            scanPaths(context, paths, callback, false);
        }

        public static void scanPathsWithNotification(final Context context, final String[] paths) {
            scanPaths(context, paths, null, true);
        }

        @SuppressLint("ShowToast")
        private static void scanPaths(final Context context, final String[] paths, final MediaScannerCallback callback, final boolean withNotification) {
            Log.i("FileOperation", "scanPaths(), paths: " + Arrays.toString(paths));
            if (paths == null) {
                if (callback != null) {
                    callback.onAllPathsScanned();
                }
                return;
            }

            //create notification
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                createNotificationChannel(context);
            }
            final NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context,
                    context.getString(R.string.file_op_channel_id))
                    .setContentTitle("Scanning...");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                notifBuilder.setSmallIcon(R.drawable.ic_autorenew_white);
            }
            notifBuilder.setProgress(paths.length, 0, false);
            final NotificationManager manager =
                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

            AsyncTask.execute(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < paths.length; i++) {
                        String path = paths[i];
                        if (MediaType.isMedia(path)) {
                            Uri contentUri = MediaStore.Files.getContentUri("external");
                            ContentResolver resolver = context.getContentResolver();
                            if (new File(path).exists()) {
                                AlbumItem albumItem = AlbumItem.getInstance(path);
                                ContentValues values = new ContentValues();
                                if (albumItem instanceof Video) {
                                    values.put(MediaStore.Video.Media.DATA, path);
                                    values.put(MediaStore.Video.Media.MIME_TYPE, MediaType.getMimeType(path));
                                } else {
                                    values.put(MediaStore.Images.Media.DATA, path);
                                    values.put(MediaStore.Images.Media.MIME_TYPE, MediaType.getMimeType(path));
                                    try {
                                        ExifInterface exif = new ExifInterface(path);
                                        Locale locale = us.koller.cameraroll.util.Util.getLocale(context);
                                        String dateString = String.valueOf(ExifUtil.getCastValue(exif, ExifInterface.TAG_DATETIME));
                                        try {
                                            Date date = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", locale).parse(dateString);
                                            long dateTaken = date.getTime();
                                            values.put(MediaStore.Images.Media.DATE_TAKEN, dateTaken);
                                        } catch (ParseException ignored) {
                                        }
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                                resolver.insert(contentUri, values);
                            } else {
                                resolver.delete(contentUri,
                                        MediaStore.MediaColumns.DATA + "='" + path + "'",
                                        null);
                            }
                        }

                        if (withNotification) {
                            notifBuilder.setProgress(paths.length, i, false);
                            if (manager != null) {
                                manager.notify(NOTIFICATION_ID, notifBuilder.build());
                            }
                        }

                    }

                    if (manager != null) {
                        manager.cancel(NOTIFICATION_ID);
                    }

                    if (callback != null) {
                        new Handler(Looper.getMainLooper()).post(new Runnable() {
                            @Override
                            public void run() {
                                callback.onAllPathsScanned();
                            }
                        });
                    }
                }
            });
        }

        public static String getParentPath(String path) {
            return new File(path).getParent();
        }
    }
} public class Rename extends FileOperation {

    public static final String NEW_FILE_PATH = "NEW_FILE_PATH";

    private String newFilePath;

    @Override
    String getNotificationTitle() {
        return getString(R.string.rename);
    }

    @Override
    public int getNotificationSmallIconRes() {
        return R.drawable.ic_text_format_white;
    }

    @Override
    public void execute(Intent workIntent) {
        final File_POJO[] files = getFiles(workIntent);
        final String newFileName = workIntent.getStringExtra(FileOperation.NEW_FILE_NAME);
        if (files.length > 0 && newFileName != null) {
            final File_POJO file = files[0];
            boolean result;
            if (FileOperation.Util.isOnRemovableStorage(file.getPath())) {
                //file is on removable storage
                Uri treeUri = getTreeUri(workIntent, file.getPath());
                if (treeUri == null) {
                    return;
                }
                result = renameFileRemovableStorage(getApplicationContext(), treeUri, file.getPath(), newFileName);
            } else {
                result = renameFile(file.getPath(), newFileName);
            }

            if (!result) {
                sendFailedBroadcast(workIntent, file.getPath());
            } else {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplicationContext(), getString(R.string.successfully_renamed_file),
                                Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }
    }

    @Override
    public Intent getDoneIntent() {
        Intent intent = super.getDoneIntent();
        intent.putExtra(NEW_FILE_PATH, newFilePath);
        return intent;
    }

    @Override
    public int getType() {
        return FileOperation.RENAME;
    }

    private static String getFileExtension(String filename) {
        int index = filename.lastIndexOf(".");
        if (index != -1) {
            return filename.substring(index);
        }
        return "";
    }

    public static String getNewFilePath(String path, String newFileName) {
        File file = new File(path);
        String fileExtension = getFileExtension(file.getName());
        String destination = file.getPath().replace(file.getName(), "");
        File newFile = new File(destination, newFileName + fileExtension);
        return newFile.getPath();
    }

    private boolean renameFile(String path, String newFileName) {
        //keep old paths to remove them from MediaStore afterwards
        ArrayList<String> oldPaths = FileOperation.Util.getAllChildPaths(new ArrayList<String>(), path);

        File file = new File(path);
        newFilePath = getNewFilePath(path, newFileName);
        File newFile = new File(newFilePath);

        //renaming file
        boolean success = file.renameTo(newFile);

        //re-scan all paths
        ArrayList<String> newPaths = FileOperation.Util.getAllChildPaths(new ArrayList<String>(), newFile.getPath());
        addPathsToScan(oldPaths);
        addPathsToScan(newPaths);

        return success;
    }

    private boolean renameFileRemovableStorage(Context context, Uri treeUri, String path, String newFileName) {
        //keep old paths to remove them from MediaStore afterwards
        ArrayList<String> oldPaths = FileOperation.Util.getAllChildPaths(new ArrayList<String>(), path);

        newFilePath = getNewFilePath(path, newFileName);
        boolean success = false;
        DocumentFile file = StorageUtil.parseDocumentFile(context, treeUri, new File(path));
        if (file != null) {
            success = file.renameTo(new File(newFilePath).getName());
        }

        //re-scan all paths
        ArrayList<String> newPaths = FileOperation.Util.getAllChildPaths(new ArrayList<String>(), newFilePath);
        addPathsToScan(oldPaths);
        addPathsToScan(newPaths);
        return success;
    }

    public static class Util {

        public static AlertDialog getRenameDialog(final BaseActivity activity,
                                                  final File_POJO file,
                                                  final BroadcastReceiver broadcastReceiver) {

            Theme theme = Settings.getInstance(activity).getThemeInstance(activity);
            ContextThemeWrapper wrapper = new ContextThemeWrapper(activity, theme.getDialogThemeRes());

            @SuppressLint("InflateParams")
            View dialogLayout = LayoutInflater.from(wrapper)
                    .inflate(R.layout.input_dialog_layout, null, false);

            final EditText editText = dialogLayout.findViewById(R.id.edit_text);
            String name = file.getName();
            int index = name.lastIndexOf(".");
            //String fileExtension = name.substring(index, name.length());
            name = name.substring(0, index != -1 ? index : name.length());
            editText.setText(name);
            editText.setSelection(name.length());

            AlertDialog dialog = new AlertDialog.Builder(wrapper)
                    .setTitle(R.string.rename)
                    .setView(dialogLayout)
                    .setPositiveButton(R.string.rename, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            final String newFileName = editText.getText().toString();

                            if (broadcastReceiver != null) {
                                activity.registerLocalBroadcastReceiver(broadcastReceiver);
                            }

                            final File_POJO[] files = new File_POJO[]{file};
                            Intent intent = FileOperation.getDefaultIntent(activity, FileOperation.RENAME, files)
                                            .putExtra(FileOperation.NEW_FILE_NAME, newFileName);
                            activity.startService(intent);
                        }
                    })
                    .setNegativeButton(R.string.cancel, null)
                    .create();
            //noinspection ConstantConditions
            dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
            return dialog;
        }
    }
} public class File_POJO
        implements Parcelable, SortUtil.Sortable {

    private String path;
    private String name;

    private ArrayList<File_POJO> children;
    public boolean isMedia;
    public boolean excluded;

    public File_POJO(String path, boolean isMedia) {
        this.path = path;
        this.isMedia = isMedia;

        children = new ArrayList<>();

        excluded = Provider.isDirExcluded(getPath(),
                Provider.getExcludedPaths());
    }

    public void addChild(File_POJO file) {
        children.add(file);
    }

    public File_POJO setName(String name) {
        this.name = name;
        return this;
    }

    @Override
    public String getName() {
        if (name != null) {
            return name;
        }
        String[] s = getPath().split("/");
        return s[s.length - 1];
    }

    @Override
    public long getDate() {
        //not needed
        return new File(getPath()).lastModified();
    }

    @Override
    public String toString() {
        return getPath();
    }

    public String getPath() {
        return path;
    }

    @Override
    public boolean pinned() {
        return false;
    }

    public ArrayList<File_POJO> getChildren() {
        return children;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(path);
        parcel.writeString(name);
        parcel.writeString(String.valueOf(isMedia));
        File_POJO[] children = new File_POJO[this.children.size()];
        for (int k = 0; k < children.length; k++) {
            children[k] = this.children.get(k);
        }
        parcel.writeTypedArray(children, 0);
        parcel.writeString(String.valueOf(excluded));
    }

    @SuppressWarnings("unchecked")
    public File_POJO(Parcel parcel) {
        path = parcel.readString();
        name = parcel.readString();
        isMedia = Boolean.valueOf(parcel.readString());
        children = parcel.createTypedArrayList(CREATOR);
        excluded = Boolean.valueOf(parcel.readString());
    }

    @SuppressWarnings("WeakerAccess")
    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public File_POJO createFromParcel(Parcel parcel) {
            return new File_POJO(parcel);
        }

        @Override
        public File_POJO[] newArray(int i) {
            return new File_POJO[i];
        }
    };

    public static File_POJO[] generateArray(Parcelable[] parcelables) {
        File_POJO[] files = new File_POJO[parcelables.length];
        for (int i = 0; i < parcelables.length; i++) {
            files[i] = (File_POJO) parcelables[i];
        }
        return files;
    }
} public class MediaProvider extends Provider {

    private static ArrayList<Album> albums;

    public static final String SINGLE_FOLDER_PATH = "CAMERA_ROLL_SINGLE_FOLDER_PATH";
    private static final int MODE_STORAGE = 1;
    private static final int MODE_MEDIASTORE = 2;

    public static final String FILE_TYPE_NO_MEDIA = ".nomedia";
    public static final int PERMISSION_REQUEST_CODE = 16;

    public static boolean dataChanged = false;

    public abstract static class OnMediaLoadedCallback implements Provider.Callback {
        public abstract void onMediaLoaded(ArrayList<Album> albums);
    }

    public interface OnAlbumLoadedCallback {
        void onAlbumLoaded(Album album);
    }

    public MediaProvider(Context context) {
        super(context);
    }

    public static boolean checkPermission(Activity context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            int read = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE);
            int write = ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE);
            if (read != PackageManager.PERMISSION_GRANTED || write != PackageManager.PERMISSION_GRANTED) {
                String[] requestedPermissions = new String[]{
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE};
                ActivityCompat.requestPermissions(context, requestedPermissions, PERMISSION_REQUEST_CODE);
                return false;
            }
        }
        return true;
    }

    public void loadAlbums(final Activity context,
                           final boolean hiddenFolders,
                           OnMediaLoadedCallback callback) {

        if (!MediaProvider.checkPermission(context)) {
            callback.needPermission();
            return;
        }

        int mode = getMode(context);

        switch (mode) {
            case MODE_STORAGE:
                retriever = new StorageRetriever();
                break;
            case MODE_MEDIASTORE:
                retriever = new MediaStoreRetriever();
                break;
            default:
                break;
        }

        if (retriever != null) {
            setCallback(callback);

            retriever.loadAlbums(context, hiddenFolders,
                    new OnMediaLoadedCallback() {
                        @Override
                        public void onMediaLoaded(ArrayList<Album> albums) {
                            //remove excluded albums
                            for (int i = albums.size() - 1; i >= 0; i--) {
                                if (albums.get(i) == null || albums.get(i).excluded) {
                                    albums.remove(i);
                                }
                            }

                            SortUtil.sortAlbums(context, albums);

                            setAlbums(albums);
                            OnMediaLoadedCallback callback = getCallback();
                            if (callback != null) {
                                callback.onMediaLoaded(albums);
                            }
                        }

                        @Override
                        public void timeout() {
                            OnMediaLoadedCallback callback = getCallback();
                            if (callback != null) {
                                callback.timeout();
                            }
                        }

                        @Override
                        public void needPermission() {
                            OnMediaLoadedCallback callback = getCallback();
                            if (callback != null) {
                                callback.needPermission();
                            }
                        }
                    });
        } else {
            if (callback != null) {
                callback.onMediaLoaded(null);
            }
        }
    }

    private static void setAlbums(ArrayList<Album> albums) {
        MediaProvider.albums = albums;
    }

    public static ArrayList<Album> getAlbums() {
        return albums;
    }

    public static ArrayList<Album> getAlbumsWithVirtualDirectories(Activity context) {
        Settings s = Settings.getInstance(context);
        boolean virtualDirs = s.getVirtualDirectories();
        if (!virtualDirs) {
            return getAlbums();
        }

        ArrayList<VirtualAlbum> virtualAlbums = getVirtualAlbums(context);
        ArrayList<Album> albums = getAlbums();
        if (albums == null || virtualAlbums == null) {
            return albums;
        }
        //noinspection unchecked
        albums = (ArrayList<Album>) getAlbums().clone();
        ArrayList<Album> albumsWithVirtualDirs = new ArrayList<>();

        for (int i = virtualAlbums.size() - 1; i >= 0; i--) {
            VirtualAlbum virtualAlbum = virtualAlbums.get(i);
            if (virtualAlbum.getDirectories().size() > 0) {
                virtualAlbum.create(context, albums);
                if (virtualAlbum.getAlbumItems().size() > 0) {
                    albumsWithVirtualDirs.add(virtualAlbum);
                }
            }
        }

        albumsWithVirtualDirs.addAll(albums);
        for (int i = albumsWithVirtualDirs.size() - 1; i >= 0; i--) {
            Album album = albumsWithVirtualDirs.get(i);
            if (!(album instanceof VirtualAlbum)) {
                for (int k = 0; k < virtualAlbums.size(); k++) {
                    if (virtualAlbums.get(k).contains(album.getPath())) {
                        albumsWithVirtualDirs.remove(i);
                        break;
                    }
                }
            }
        }
        SortUtil.sortAlbums(context, albumsWithVirtualDirs);
        return albumsWithVirtualDirs;
    }

    public static void loadAlbum(final Activity context, final String path,
                                 final OnAlbumLoadedCallback callback) {
        if (path == null) {
            context.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    callback.onAlbumLoaded(null);
                }
            });
            return;
        }

        if (albums == null) {
            Settings s = Settings.getInstance(context);
            boolean hiddenFolders = s.getHiddenFolders();
            new MediaProvider(context).loadAlbums(context, hiddenFolders, new OnMediaLoadedCallback() {
                @Override
                public void onMediaLoaded(ArrayList<Album> albums) {
                    loadAlbum(context, path, callback);
                }

                @Override
                public void timeout() {
                    callback.onAlbumLoaded(getErrorAlbum());
                }

                @Override
                public void needPermission() {
                    callback.onAlbumLoaded(getErrorAlbum());
                }
            });
        } else {
            if (path.startsWith(VirtualAlbum.VIRTUAL_ALBUMS_DIR)) {
                ArrayList<VirtualAlbum> virtualDirectories = getVirtualAlbums(context);
                for (int i = 0; i < virtualDirectories.size(); i++) {
                    if (virtualDirectories.get(i).getPath().equals(path)) {
                        final VirtualAlbum album = virtualDirectories.get(i);
                        album.create(context, albums);
                        context.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                callback.onAlbumLoaded(album);
                            }
                        });
                        return;
                    }
                }
            } else if (path.equals(SINGLE_FOLDER_PATH)) {
                final Album album = new Album().setPath(MediaProvider.SINGLE_FOLDER_PATH);
                for (int i = 0; i < albums.size(); i++) {
                    album.getAlbumItems().addAll(albums.get(i).getAlbumItems());
                }
                int sortBy = Settings.getInstance(context).sortAlbumsBy();
                SortUtil.sort(album.getAlbumItems(), sortBy);
                context.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        callback.onAlbumLoaded(album);
                    }
                });
            } else {
                for (int i = 0; i < albums.size(); i++) {
                    if (albums.get(i).getPath().equals(path)) {
                        final Album album = albums.get(i);
                        context.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                callback.onAlbumLoaded(album);
                            }
                        });
                        return;
                    }
                }
            }
        }
    }

    public static Album getErrorAlbum() {
        //Error album
        Album album = new Album().setPath("ERROR");
        album.getAlbumItems().add(AlbumItem.getErrorItem());
        return album;
    }

    private static int getMode(Context context) {
        return Settings.getInstance(context).useStorageRetriever() ?
                MODE_STORAGE : MODE_MEDIASTORE;
    }
} public abstract class Provider {

    //prevent StorageRetriever from querying Android-Folder
    private static final String[] permanentlyExcludedPaths = {}; // "/storage/emulated/0/Android"

    // by default excluded folders:
    // not expecting relevant media in alarms, music or ringtone folder
    private static final String[] defaultExcludedPaths = {
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS).getPath(),
            Environment.getExternalStorageDirectory().getPath() + "/Android",
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath(),
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES).getPath()};

    // by default pinned folders:
    @SuppressWarnings("MismatchedReadAndWriteOfArray")
    private static final String[] defaultPinnedPaths = {};

    // default virtual directories:
    // not expecting relevant media in alarms, music or ringtone folder
    @SuppressWarnings("MismatchedReadAndWriteOfArray")
    private static final VirtualAlbum[] defaultVirtualAlbums = {};

    Retriever retriever;

    private Callback callback;

    //handle pinned albums
    private static final String PINNED_PATHS_NAME = "pinned_paths.txt";
    private static ArrayList<String> pinnedPaths;

    //handle excluded paths
    private static final String EXCLUDED_PATHS_NAME = "excluded_paths.txt";
    private static ArrayList<String> excludedPaths;

    //virtual directories
    private static final String VIRTUAL_DIRECTORIES_NAME = "virtual_directories.txt";
    private static ArrayList<VirtualAlbum> virtualAlbums;

    public interface Callback {
        void timeout();

        void needPermission();
    }

    Provider(Context context) {
        if (excludedPaths == null) {
            loadExcludedPaths(context);
        }

        if (pinnedPaths == null) {
            loadPinnedPaths(context);
        }
    }

    void setCallback(Callback callback) {
        this.callback = callback;
    }

    @SuppressWarnings("unchecked")
    public <T extends Callback> T getCallback() {
        if (callback != null) {
            return (T) callback;
        }
        return null;
    }

    public void onDestroy() {
        setCallback(null);

        if (retriever != null) {
            retriever.onDestroy();
        }
    }

    public static ArrayList<VirtualAlbum> getVirtualAlbums(Context context) {
        if (virtualAlbums == null) {
            virtualAlbums = loadVirtualAlbums(context);
        }
        return virtualAlbums;
    }

    private static ArrayList<VirtualAlbum> loadVirtualAlbums(Context context) {
        virtualAlbums = new ArrayList<>();

        try {
            ArrayList<String> temp = loadPathsArrayList(context, VIRTUAL_DIRECTORIES_NAME);
            for (int i = 0; i < temp.size(); i++) {
                virtualAlbums.add(new VirtualAlbum(temp.get(i)));
            }
        } catch (IOException e) {
            // no file found
            virtualAlbums.addAll(Arrays.asList(defaultVirtualAlbums));
        }

        return virtualAlbums;
    }

    public static void addVirtualAlbum(Context context, VirtualAlbum virtualAlbum) {
        if (virtualAlbums == null) {
            virtualAlbums = loadVirtualAlbums(context);
        }
        virtualAlbums.add(virtualAlbum);
    }

    public static void removeVirtualAlbum(Context context, VirtualAlbum virtualAlbum) {
        if (virtualAlbums == null) {
            virtualAlbums = loadVirtualAlbums(context);
        }
        virtualAlbums.remove(virtualAlbum);
    }

    public static void saveVirtualAlbums(Context context) {
        try {
            ArrayList<String> temp = new ArrayList<>();
            for (int i = 0; i < virtualAlbums.size(); i++) {
                temp.add(virtualAlbums.get(i).toString());
            }
            savePathsArrayList(context, temp, VIRTUAL_DIRECTORIES_NAME);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static ArrayList<String> getPinnedPaths() {
        return pinnedPaths;
    }

    public static boolean isAlbumPinned(String albumPath, ArrayList<String> pinnedPaths) {
        if (pinnedPaths == null) {
            return false;
        }
        if (pinnedPaths.contains(albumPath)) {
            return true;
        } else {
            for (int i = 0; i < pinnedPaths.size(); i++) {
                if (albumPath.equals(pinnedPaths.get(i))) {
                    return true;
                }
            }
        }
        return false;
    }

    public static void pinPath(Context context, String path) {
        if (pinnedPaths == null) {
            pinnedPaths = loadPinnedPaths(context);
        }

        if (!pinnedPaths.contains(path)) {
            pinnedPaths.add(path);
        }
    }

    public static void unpinPath(Context context, String path) {
        if (pinnedPaths == null) {
            pinnedPaths = loadPinnedPaths(context);
        }

        pinnedPaths.remove(path);
    }

    public static ArrayList<String> loadPinnedPaths(Context context) {
        pinnedPaths = new ArrayList<>();

        try {
            pinnedPaths = loadPathsArrayList(context, PINNED_PATHS_NAME);
        } catch (IOException e) {
            // no file found
            pinnedPaths.addAll(Arrays.asList(defaultPinnedPaths));
        }

        return excludedPaths;
    }

    public static void savePinnedPaths(Context context) {
        try {
            savePathsArrayList(context, pinnedPaths, PINNED_PATHS_NAME);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static boolean isPathPermanentlyExcluded(String path) {
        boolean permanentlyExcluded = false;
        for (int i = 0; i < Provider.permanentlyExcludedPaths.length; i++) {
            if (path.contains(Provider.permanentlyExcludedPaths[i])) {
                permanentlyExcluded = true;
                break;
            }
        }
        return permanentlyExcluded;
    }

    private static boolean searchDir(String path) {
        if (path == null) {
            return false;
        }
        boolean search = true;
        for (int i = 0; i < Provider.permanentlyExcludedPaths.length; i++) {
            if (path.contains(Provider.permanentlyExcludedPaths[i])) {
                search = false;
                break;
            }
        }

        //Provider are now taking care of excluded folders themselves
        if (search && excludedPaths != null) {
            for (int i = 0; i < Provider.excludedPaths.size(); i++) {
                if (path.contains(Provider.excludedPaths.get(i))) {
                    search = false;
                    break;
                }
            }
        }

        return search;
    }

    public static boolean isDirExcluded(String path, ArrayList<String> excludedPaths) {
        if (isPathPermanentlyExcluded(path)) {
            return true;
        }

        if (excludedPaths == null) {
            return false;
        }

        boolean excluded = false;
        for (int i = 0; i < excludedPaths.size(); i++) {
            if (path.contains(excludedPaths.get(i))) {
                excluded = true;
                break;
            }
        }
        return excluded;
    }

    public static boolean isDirExcludedBecauseParentDirIsExcluded
            (String path, ArrayList<String> excludedPaths) {
        if (!isDirExcluded(path, excludedPaths)) {
            return false;
        }

        boolean excludedBecauseParent = true;
        for (int i = 0; i < excludedPaths.size(); i++) {
            if (path.equals(excludedPaths.get(i))) {
                excludedBecauseParent = false;
                break;
            }
        }
        return excludedBecauseParent;
    }

    public static ArrayList<String> getExcludedPaths() {
        return excludedPaths;
    }

    public static void addExcludedPath(Context context, String path) {
        if (excludedPaths == null) {
            excludedPaths = loadExcludedPaths(context);
        }
        if (!excludedPaths.contains(path)) {
            excludedPaths.add(path);
        }
    }

    public static void removeExcludedPath(Context context, String path) {
        if (excludedPaths == null) {
            excludedPaths = loadExcludedPaths(context);
        }
        excludedPaths.remove(path);
    }

    public static ArrayList<String> loadExcludedPaths(Context context) {
        excludedPaths = new ArrayList<>();

        try {
            excludedPaths = loadPathsArrayList(context, EXCLUDED_PATHS_NAME);
        } catch (IOException e) {
            // no file found
            excludedPaths.addAll(Arrays.asList(defaultExcludedPaths));
        }

        return excludedPaths;
    }

    public static void saveExcludedPaths(Context context) {
        try {
            savePathsArrayList(context, excludedPaths, EXCLUDED_PATHS_NAME);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static ArrayList<String> loadPathsArrayList(Context context, String filename) throws IOException {
        ArrayList<String> paths = new ArrayList<>();

        //read file
        ArrayList<String> lines = readFromFile(context, filename);
        paths.addAll(lines);

        return paths;
    }

    private static void savePathsArrayList(Context context, ArrayList<String> paths, String filename) throws IOException {
        if (paths == null) {
            return;
        }

        //write to file
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < paths.size(); i++) {
            sb.append(paths.get(i)).append('\n');
        }

        writeToFile(context, filename, sb.toString());
    }

    private static ArrayList<String> readFromFile(Context context, String filename) throws IOException {
        ArrayList<String> lines = new ArrayList<>();

        //read file
        FileInputStream fis = context.openFileInput(filename);
        BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
        String line;
        while ((line = reader.readLine()) != null) {
            lines.add(line);
        }
        fis.close();

        return lines;
    }

    private static void writeToFile(Context context, String filename, String data) throws IOException {
        //write to file
        FileOutputStream fos
                = context.openFileOutput(filename, Context.MODE_PRIVATE);
        fos.write(data.getBytes());
        fos.close();
    }

    public static File[] getDirectoriesToSearch(Context context) {
        FileFilter filter = new FileFilter() {
            @Override
            public boolean accept(File file) {
                return file != null && Provider.searchDir(file.getPath());
            }
        };

        //external Directory
        File dir = Environment.getExternalStorageDirectory();
        File[] dirs = dir.listFiles(filter);
        if (dirs == null) {
            dirs = new File[]{};
        }

        //handle removable storage (e.g. SDCards)
        ArrayList<File> temp = new ArrayList<>();
        temp.addAll(Arrays.asList(dirs));
        File[] removableStorageRoots = StorageUtil.getRemovableStorageRoots(context);
        for (int i = 0; i < removableStorageRoots.length; i++) {
            File root = removableStorageRoots[i];
            File[] files = root.listFiles(filter);
            if (files != null) {
                Collections.addAll(temp, files);
            }
        }

        dirs = new File[temp.size()];
        for (int i = 0; i < dirs.length; i++) {
            dirs[i] = temp.get(i);
        }
        return dirs;
    }
} public class Settings {

    public static final int DEFAULT_COLUMN_COUNT = 4;
    private static final String PREF_KEY_HIDDEN_FOLDERS = "HIDDEN_FOLDERS";

    private String theme;
    private boolean storageRetriever;
    private int style;
    private int columnCount;
    private int sort_albums_by;
    private int sort_album_by;
    private boolean hiddenFolders;
    private boolean showVideos;
    private boolean use8BitColor;
    private boolean cameraShortcut;
    private Uri removableStorageTreeUri;
    private boolean virtualDirectories;
    @SuppressWarnings("FieldCanBeLocal")
    private boolean fadeImages = false;
    private boolean showAnimations;
    private boolean maxBrightness;
    private float prevBrightness;

    private static Settings instance;

    public static Settings getInstance(Context context) {
        if (instance == null) {
            instance = new Settings(context);
        }
        return instance;
    }

    private Settings(Context context) {
        SharedPreferences sharedPreferences
                = PreferenceManager.getDefaultSharedPreferences(context);

        theme = sharedPreferences.getString(
                context.getString(R.string.pref_key_theme),
                context.getString(R.string.DARK_THEME_VALUE));

        storageRetriever = sharedPreferences.getBoolean(
                context.getString(R.string.pref_key_media_retriever),
                false);

        style = sharedPreferences.getInt(
                context.getString(R.string.pref_key_style),
                context.getResources().getInteger(R.integer.STYLE_PARALLAX_VALUE));

        columnCount = sharedPreferences.getInt(
                context.getString(R.string.pref_key_column_count),
                DEFAULT_COLUMN_COUNT);

        sort_albums_by = sharedPreferences.getInt(
                context.getString(R.string.pref_key_sort_albums),
                SortUtil.BY_NAME);

        sort_album_by = sharedPreferences.getInt(
                context.getString(R.string.pref_key_sort_album),
                SortUtil.BY_DATE);

        showVideos = sharedPreferences.getBoolean(
                context.getString(R.string.pref_key_show_videos),
                true);

        hiddenFolders = sharedPreferences.getBoolean
                (PREF_KEY_HIDDEN_FOLDERS, false);

        use8BitColor = sharedPreferences.getBoolean(
                context.getString(R.string.pref_key_8_bit_color),
                false);

        cameraShortcut = sharedPreferences.getBoolean(
                context.getString(R.string.pref_key_camera_shortcut),
                false);

        removableStorageTreeUri = Uri.parse(sharedPreferences.getString(
                context.getString(R.string.pref_key_removable_storage_treeUri),
                ""));

        virtualDirectories = sharedPreferences.getBoolean(
                context.getString(R.string.pref_key_virtual_directories),
                true);

        showAnimations = sharedPreferences.getBoolean(
                context.getString(R.string.pref_key_animations),
                true);

        maxBrightness = sharedPreferences.getBoolean(
                context.getString(R.string.pref_key_max_brightness),
                false);
    }

    /*Getter & Setter*/
    public String getTheme() {
        return theme;
    }

    public Theme getThemeInstance(Context context) {
        String theme = getTheme();
        Resources res = context.getResources();
        if (theme.equals(res.getString(R.string.LIGHT_THEME_VALUE))) {
            return new LightTheme();
        } else if (theme.equals(res.getString(R.string.BLACK_THEME_VALUE))) {
            return new BlackTheme();
        } else {
            return new DarkTheme();
        }
    }

    public void setTheme(String theme) {
        this.theme = theme;
    }

    public boolean useStorageRetriever() {
        return storageRetriever;
    }

    public void useStorageRetriever(boolean storageRetriever) {
        this.storageRetriever = storageRetriever;
    }

    public int getStyle(Context context, boolean pickPhotos) {
        if (pickPhotos && style == NestedRecyclerView.getValue(context)) {
            return Cards2.getValue(context);
        }
        return style;
    }

    public void setStyle(int style) {
        this.style = style;
    }

    public Style getStyleInstance(Context context, boolean pickPhotos) {
        int style = getStyle(context, pickPhotos);
        return getStyleInstance(context, style);
    }

    public Style getStyleInstance(Context context, int style) {
        if (style == Parallax.getValue(context)) {
            return new Parallax();
        } else if (style == Cards.getValue(context)) {
            return new Cards();
        } else if (style == Cards2.getValue(context)) {
            return new Cards2();
        } else if (style == NestedRecyclerView.getValue(context)) {
            return new NestedRecyclerView();
        } else if (style == List.getValue(context)) {
            return new List();
        }
        return null;
    }

    public int getColumnCount(Context context) {
        if (columnCount == 0) {
            columnCount = DEFAULT_COLUMN_COUNT;
        }

        boolean landscape = context.getResources().getBoolean(R.bool.landscape);
        if (landscape) {
            return columnCount + 1;
        }
        return columnCount;
    }

    public int getRealColumnCount() {
        if (columnCount == 0) {
            columnCount = DEFAULT_COLUMN_COUNT;
        }
        return columnCount;
    }

    public void setColumnCount(int columnCount) {
        this.columnCount = columnCount;
    }

    public int sortAlbumsBy() {
        return sort_albums_by;
    }

    public void sortAlbumsBy(Context context, int sort_albums_by) {
        this.sort_albums_by = sort_albums_by;

        String key = context.getString(R.string.pref_key_sort_albums);
        saveInt(context, key, sort_albums_by);
    }

    public int sortAlbumBy() {
        return sort_album_by;
    }

    public void sortAlbumBy(Context context, int sort_album_by) {
        this.sort_album_by = sort_album_by;

        String key = context.getString(R.string.pref_key_sort_album);
        saveInt(context, key, sort_album_by);
    }

    public boolean getHiddenFolders() {
        return hiddenFolders;
    }

    public boolean setHiddenFolders(Context context, boolean hiddenFolders) {
        this.hiddenFolders = hiddenFolders;
        saveBoolean(context, PREF_KEY_HIDDEN_FOLDERS, hiddenFolders);
        return hiddenFolders;
    }

    public boolean use8BitColor() {
        return use8BitColor;
    }

    public void use8BitColor(boolean use8BitColor) {
        this.use8BitColor = use8BitColor;
    }

    public boolean getCameraShortcut() {
        return cameraShortcut;
    }

    public void setCameraShortcut(boolean cameraShortcut) {
        this.cameraShortcut = cameraShortcut;
    }

    public boolean showVideos() {
        return showVideos;
    }

    public void showVideos(boolean hideVideos) {
        this.showVideos = hideVideos;
    }

    public Uri getRemovableStorageTreeUri() {
        Log.d("Settings", "getRemovableStorageTreeUri: " + removableStorageTreeUri);
        return removableStorageTreeUri;
    }

    public boolean getVirtualDirectories() {
        return virtualDirectories;
    }

    @SuppressWarnings("unused")
    public void setVirtualDirectories(Context context, boolean virtualDirectories) {
        this.virtualDirectories = virtualDirectories;
        saveBoolean(context, context.getString(R.string.pref_key_virtual_directories),
                virtualDirectories);
    }

    public boolean fadeImages() {
        return fadeImages;
    }

    public boolean noFolderMode() {
        return false;
    }

    public boolean showAnimations() {
        return showAnimations;
    }

    public void showAnimations(boolean showAnimations) {
        this.showAnimations = showAnimations;
    }

    public boolean isMaxBrightness() {
        return maxBrightness;
    }

    public void setMaxBrightness(boolean maxBrightness) {
        this.maxBrightness = maxBrightness;
    }

    public void setPrevBrightness(float prevBrightness) {
        this.prevBrightness = prevBrightness;
    }

    public float getPrevBrightness() {
        return prevBrightness;
    }

    public void setRemovableStorageTreeUri(Context context, Uri removableStorageTreeUri) {
        this.removableStorageTreeUri = removableStorageTreeUri;
        saveString(context,
                context.getString(R.string.pref_key_removable_storage_treeUri),
                removableStorageTreeUri.toString());
    }

    public static void saveInt(Context context, String key, int value) {
        SharedPreferences sharedPreferences
                = PreferenceManager.getDefaultSharedPreferences(context);
        sharedPreferences
                .edit()
                .putInt(key, value)
                .apply();
    }

    private static void saveBoolean(Context context, String key, boolean value) {
        SharedPreferences sharedPreferences
                = PreferenceManager.getDefaultSharedPreferences(context);
        sharedPreferences
                .edit()
                .putBoolean(key, value)
                .apply();
    }

    private static void saveString(Context context, String key, String value) {
        SharedPreferences sharedPreferences
                = PreferenceManager.getDefaultSharedPreferences(context);
        sharedPreferences
                .edit()
                .putString(key, value)
                .apply();
    }

    public static class Utils {
        public static String getThemeName(Context context, String themeValue) {
            int valuesRes = R.array.theme_values;
            int namesRes = R.array.theme_names;
            return getValueName(context, themeValue, valuesRes, namesRes);
        }

        public static String getStyleName(Context context, int styleValue) {
            int valuesRes = R.array.style_values;
            int namesRes = R.array.style_names;
            return getValueName(context, styleValue, valuesRes, namesRes);
        }


        private static String getValueName(Context context, String value,
                                           int valuesRes, int namesRes) {
            String[] values = context.getResources().getStringArray(valuesRes);
            String[] names = context.getResources().getStringArray(namesRes);

            int index = -1;
            for (int i = 0; i < values.length; i++) {
                if (values[i].equals(value)) {
                    index = i;
                    break;
                }
            }

            if (index >= 0) {
                return names[index];
            }
            return "Error";
        }

        private static String getValueName(Context context, int value,
                                           int valuesRes, int namesRes) {
            int[] values = context.getResources().getIntArray(valuesRes);
            String[] names = context.getResources().getStringArray(namesRes);

            int index = -1;
            for (int i = 0; i < values.length; i++) {
                if (values[i] == value) {
                    index = i;
                    break;
                }
            }

            if (index >= 0) {
                return names[index];
            }
            return "Error";
        }
    }
} public class FastScrollerRecyclerView extends RecyclerView {

    private RecyclerFastScroller fastScroller;
    private boolean fastScrolling = false;

    //to achieve equalSpacing between cards
    private int outerGridSpacing = 0;

    public FastScrollerRecyclerView(Context context) {
        super(context);
    }

    public FastScrollerRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public FastScrollerRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        ViewParent parent = getParent();
        if (parent instanceof View) {
            View parentView = (View) parent;
            fastScroller = parentView.findViewById(R.id.fastScroller);
            if (fastScroller != null) {
                fastScroller.attachRecyclerView(this);
            }
        }
    }

    @Override
    public void setPadding(int left, int top, int right, int bottom) {
        super.setPadding(left, top, right, bottom);

        //handle fastScroller padding
        if (fastScroller != null) {
            MarginLayoutParams params = (MarginLayoutParams) fastScroller.getLayoutParams();
            params.leftMargin = getPaddingLeft() - getOuterGridSpacing();
            params.topMargin = getPaddingTop() - getOuterGridSpacing();
            params.rightMargin = getPaddingRight() - getOuterGridSpacing();
            params.bottomMargin = getPaddingBottom() - getOuterGridSpacing();
            fastScroller.setLayoutParams(params);

            //pass padding top to Handle as translationY
            View mHandle = fastScroller.getChildAt(1);
            if (mHandle != null) {
                mHandle.setTranslationY(fastScroller.getPaddingTop());
            }

            fastScroller.setOnHandleTouchListener(new OnTouchListener() {
                @Override
                @SuppressLint("ClickableViewAccessibility")
                public boolean onTouch(View view, MotionEvent motionEvent) {
                    switch (motionEvent.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            fastScrolling = true;
                            break;
                        case MotionEvent.ACTION_UP:
                            fastScrolling = false;
                            break;
                        default:
                            break;
                    }
                    return false;
                }
            });

            fastScroller.requestLayout();
        }
    }

    public void addOuterGridSpacing(int outerGridSpacing) {
        this.outerGridSpacing += outerGridSpacing;

        setPadding(getPaddingStart() + outerGridSpacing,
                getPaddingTop() + outerGridSpacing,
                getPaddingEnd() + outerGridSpacing,
                getPaddingBottom() + outerGridSpacing);
    }

    public int getOuterGridSpacing() {
        return outerGridSpacing;
    }

    /*So that fastScroller doesn't trigger SwipeBack*/
    @Override
    public boolean canScrollVertically(int direction) {
        return fastScrolling || super.canScrollVertically(direction);
    }

    @Override
    public int computeVerticalScrollRange() {
        return super.computeVerticalScrollRange() - getPaddingBottom();
    }
} public class GridMarginDecoration extends RecyclerView.ItemDecoration {

    private int space;

    public GridMarginDecoration(int space) {
        this.space = space;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view,
                               RecyclerView parent, RecyclerView.State state) {
        outRect.left = space / 2;
        outRect.top = space / 2;
        outRect.right = space / 2;
        outRect.bottom = space / 2;
    }
} public class SwipeBackCoordinatorLayout extends CoordinatorLayout {
    // widget
    public OnSwipeListener listener;

    // data
    private int swipeDistance = 0;
    private static float SWIPE_TRIGGER = 100;
    private static final float SWIPE_RADIO = 2.5F;

    private int swipeDir = NULL_DIR;
    public static final int NULL_DIR = 0;
    public static final int UP_DIR = 1;
    public static final int DOWN_DIR = -1;

    public SwipeBackCoordinatorLayout(Context context) {
        super(context);
        this.initialize();
    }

    public SwipeBackCoordinatorLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.initialize();
    }

    public SwipeBackCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.initialize();
    }

    private void initialize() {
        SWIPE_TRIGGER = (float) (getResources().getDisplayMetrics().heightPixels / 5.0);
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
        if (type == ViewCompat.TYPE_TOUCH) {
            return super.onStartNestedScroll(child, target, axes, type)
                    || ((axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0);
        }
        return super.onStartNestedScroll(child, target, axes, type);
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) {
        int dyConsumed = 0;
        if (swipeDistance != 0) {
            dyConsumed = onPreScroll(dy);
        }

        int[] newConsumed = new int[]{0, 0};
        super.onNestedPreScroll(target, dx, dy - dyConsumed, newConsumed, type);

        consumed[0] = newConsumed[0];
        consumed[1] = newConsumed[1] + dyConsumed;
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        int newDyConsumed = dyConsumed;
        int newDyUnconsumed = dyUnconsumed;
        if (swipeDistance == 0) {
            int dir = dyUnconsumed < 0 ? DOWN_DIR : UP_DIR;
            if (listener.canSwipeBack(dir)) {
                onScroll(dyUnconsumed);
                newDyConsumed = dyConsumed + dyUnconsumed;
                newDyUnconsumed = 0;
            }
        }

        super.onNestedScroll(target, dxConsumed, newDyConsumed, dxUnconsumed, newDyUnconsumed, type);
    }

    @Override
    public void onStopNestedScroll(View child, int type) {
        super.onStopNestedScroll(child, type);
        if (Math.abs(swipeDistance) >= SWIPE_TRIGGER) {
            swipeBack();
        } else {
            reset();
        }
    }

    private int onPreScroll(int dy) {
        int consumed;
        if (swipeDistance * (swipeDistance - dy) < 0) {
            swipeDir = NULL_DIR;
            consumed = swipeDistance;
            swipeDistance = 0;
        } else {
            consumed = dy;
            swipeDistance -= dy;
        }

        setSwipeTranslation();

        return consumed;
    }

    private void onScroll(int dy) {
        swipeDistance = -dy;
        swipeDir = swipeDistance > 0 ? DOWN_DIR : UP_DIR;

        setSwipeTranslation();
    }


    private void swipeBack() {
        if (listener != null) {
            listener.onSwipeFinish(swipeDir);
        }
    }

    private void reset() {
        swipeDir = NULL_DIR;
        if (swipeDistance != 0) {
            ResetAnimation a = new ResetAnimation(swipeDistance);
            a.setAnimationListener(resetAnimListener);
            a.setDuration(300);
            startAnimation(a);
        }
    }

    private void setSwipeTranslation() {
        float translationY = (float) (1.0 * swipeDistance / SWIPE_RADIO);
        setTranslationY(translationY);
        if (listener != null) {
            listener.onSwipeProcess(Math.min(1, Math.abs(translationY)));
        }
    }

    public static int getBackgroundColor(float percent) {
        return Color.argb((int) (255 * 0.5 * (2 - percent)), 0, 0, 0);
    }

    public static boolean canSwipeBackForThisView(View v, int dir) {
        return !v.canScrollVertically(dir);
    }

    private class ResetAnimation extends Animation {
        // data
        private int fromDistance;

        ResetAnimation(int from) {
            this.fromDistance = from;
        }

        @Override
        public void applyTransformation(float interpolatedTime, Transformation t) {
            swipeDistance = (int) (fromDistance * (1 - interpolatedTime));
            setSwipeTranslation();
        }
    }

    private Animation.AnimationListener resetAnimListener
            = new Animation.AnimationListener() {

        @Override
        public void onAnimationStart(Animation animation) {
            setEnabled(false);
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            setEnabled(true);
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
            // do nothing.
        }
    };

    public interface OnSwipeListener {
        boolean canSwipeBack(int dir);

        void onSwipeProcess(float percent);

        void onSwipeFinish(int dir);
    }

    public void setOnSwipeListener(OnSwipeListener l) {
        this.listener = l;
    }
} public class SortUtil {

    public static final int BY_DATE = 1;
    public static final int BY_NAME = 2;
    public static final int BY_SIZE = 3;

    //interface, implemented by Album & AlbumItem, to sort them
    public interface Sortable {
        String getName();

        long getDate();

        String getPath();

        boolean pinned();
    }

    public static void sortAlbums(Activity context, ArrayList<Album> albums) {
        Settings settings = Settings.getInstance(context);

        int sortAlbumBy = settings.sortAlbumBy();
        for (int i = 0; i < albums.size(); i++) {
            sort(albums.get(i).getAlbumItems(), sortAlbumBy);
        }

        int sortAlbumsBy = settings.sortAlbumsBy();
        switch (sortAlbumsBy) {
            case BY_NAME:
            case BY_DATE:
                sort(albums, sortAlbumsBy);
                return;
            case BY_SIZE:
                // Sorting
                Collections.sort(albums, new Comparator<Album>() {
                    @Override
                    public int compare(Album a1, Album a2) {
                        if (a1 != null && a2 != null) {
                            if (a1.pinned() ^ a2.pinned()) {
                                return a2.pinned() ? 1 : -1;
                            }
                            Integer a1_size = a1.getAlbumItems().size();
                            Integer a2_size = a2.getAlbumItems().size();
                            return a2_size.compareTo(a1_size);
                        }
                        return 0;
                    }
                });
                break;
            default:
                break;
        }
    }

    public static void sort(ArrayList<? extends Sortable> sortables, int by) {
        switch (by) {
            case BY_NAME:
                sortByName(sortables);
                break;
            case BY_DATE:
                sortByDate(sortables);
                break;
            default:
                break;
        }
    }

    public static void sortByName(ArrayList<? extends Sortable> sortables) {
        // Sorting
        /*Collections.sort(sortables, new Comparator<Sortable>() {
            @Override
            public int compare(Sortable s1, Sortable s2) {
                return compareNames(s1, s2);
            }
        });*/
        Collections.sort(sortables, new AlphanumNameComparator());
    }

    public static void sortByDate(ArrayList<? extends Sortable> sortables) {
        // Sorting
        Collections.sort(sortables, new Comparator<Sortable>() {
            @Override
            public int compare(Sortable s1, Sortable s2) {
                return compareDate(s1, s2);
            }
        });
    }

    private static int compareNames(Sortable s1, Sortable s2) {
        if (s1 != null && s2 != null) {
            if (s1.pinned() ^ s2.pinned()) {
                return s2.pinned() ? 1 : -1;
            }
            return s1.getName().compareTo(s2.getName());
        }
        return 0;
    }

    private static int compareDate(Sortable s1, Sortable s2) {
        if (s1 != null && s2 != null) {
            if (s1.pinned() ^ s2.pinned()) {
                return s2.pinned() ? 1 : -1;
            }
            Long l1 = s1.getDate();
            Long l2 = s2.getDate();
            if (l1.equals(l2)) {
                //if date is equal --> sort by Name
                return compareNames(s1, s2);
            }
            return l2.compareTo(l1);
        }
        return 0;
    }
} public class StorageUtil {

    public static Uri getContentUri(Context context, String path) {
        return getContentUri(context, AlbumItem.getInstance(path));
    }

    public static Uri getContentUri(Context context, AlbumItem albumItem) {
        Uri uri;
        if (albumItem instanceof Video) {
            uri = getContentUriForVideoFromMediaStore(context, albumItem.getPath());
        } else {
            uri = getContentUriForImageFromMediaStore(context, albumItem.getPath());
        }
        return uri;
    }

    private static Uri getContentUriForImageFromMediaStore(Context context, String path) {
        ContentResolver resolver = context.getContentResolver();
        //Uri photoUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        // to handle hidden images
        Uri photoUri = MediaStore.Files.getContentUri("external");
        Cursor cursor = resolver.query(photoUri,
                new String[]{BaseColumns._ID},
                MediaStore.MediaColumns.DATA + " = ?",
                new String[]{path}, null);
        if (cursor == null) {
            return Uri.parse(path);
        }
        cursor.moveToFirst();
        if (cursor.isAfterLast()) {
            cursor.close();
            // insert system media db
            ContentValues values = new ContentValues();
            values.put(MediaStore.Images.Media.DATA, path);
            values.put(MediaStore.Images.Media.MIME_TYPE, MediaType.getMimeType(path));
            return context.getContentResolver().insert(photoUri, values);
        } else {
            long id = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
            Uri uri = ContentUris.withAppendedId(photoUri, id);
            cursor.close();
            return uri;
        }
    }

    private static Uri getContentUriForVideoFromMediaStore(Context context, String path) {
        ContentResolver resolver = context.getContentResolver();
        //Uri videoUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        // to handle hidden videos
        Uri videoUri = MediaStore.Files.getContentUri("external");
        Cursor cursor = resolver.query(videoUri,
                new String[]{BaseColumns._ID},
                MediaStore.MediaColumns.DATA + " = ?",
                new String[]{path}, null);

        if (cursor == null) {
            return Uri.parse(path);
        }
        cursor.moveToFirst();
        if (cursor.isAfterLast()) {
            cursor.close();
            // insert system media db
            ContentValues values = new ContentValues();
            values.put(MediaStore.Video.Media.DATA, path);
            values.put(MediaStore.Video.Media.MIME_TYPE, MediaType.getMimeType(path));
            return context.getContentResolver().insert(videoUri, values);
        } else {
            int imageId = cursor.getInt(cursor.getColumnIndex(BaseColumns._ID));
            Uri uri = ContentUris.withAppendedId(videoUri, imageId);
            cursor.close();
            return uri;
        }
    }

    public static StorageRoot[] loadRoots(Activity context) {
        ArrayList<StorageRoot> temp = new ArrayList<>();

        StorageRoot externalStorage = new StorageRoot(
                Environment.getExternalStorageDirectory().getPath());
        externalStorage.setName(context.getString(R.string.storage));
        temp.add(externalStorage);

        File[] removableStorageRoots = getRemovableStorageRoots(context);
        for (int i = 0; i < removableStorageRoots.length; i++) {
            temp.add(new StorageRoot(removableStorageRoots[i].getPath()));
        }

        StorageRoot[] roots = new StorageRoot[temp.size()];
        return temp.toArray(roots);
    }

    public static File[] getRemovableStorageRoots(Context context) {
        File[] roots = context.getExternalFilesDirs("external");
        ArrayList<File> rootsArrayList = new ArrayList<>();

        for (int i = 0; i < roots.length; i++) {
            if (roots[i] != null) {
                String path = roots[i].getPath();
                int index = path.lastIndexOf("/Android/data/");
                if (index > 0) {
                    path = path.substring(0, index);
                    if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
                        rootsArrayList.add(new File(path));
                    }
                }
            }
        }

        roots = new File[rootsArrayList.size()];
        rootsArrayList.toArray(roots);
        return roots;
    }

    private static String getSdCardRootPath(Context context, String path) {
        File[] roots = getRemovableStorageRoots(context);
        for (int i = 0; i < roots.length; i++) {
            if (path.startsWith(roots[i].getPath())) {
                return roots[i].getPath();
            }
        }
        return null;
    }

    public static DocumentFile parseDocumentFile(Context context, Uri treeUri, File file) {
        DocumentFile treeRoot;
        try {
            treeRoot = DocumentFile.fromTreeUri(context, treeUri);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            return null;
        }

        String path;
        try {
            path = file.getCanonicalPath();
            String sdCardPath = getSdCardRootPath(context, path);
            if (sdCardPath != null) {
                if (sdCardPath.equals(path)) {
                    return treeRoot;
                }
                path = path.substring(sdCardPath.length() + 1);
            } else {
                return null;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        Log.d("StorageUtil", "path: " + path);

        if (treeRoot != null) {
            treeRoot = DocumentFile.fromTreeUri(context, treeUri);
            String[] pathParts = path.split("/");
            DocumentFile documentFile = treeRoot;
            for (int i = 0; i < pathParts.length; i++) {
                if (documentFile != null) {
                    documentFile = documentFile.findFile(pathParts[i]);
                } else {
                    return null;
                }
            }
            return documentFile;
        }
        return null;
    }

    public static DocumentFile createDocumentFile(Context context, Uri treeUri, String path, String mimeType) {
        int index = path.lastIndexOf("/");
        String dirPath = path.substring(0, index);
        DocumentFile file = parseDocumentFile(context, treeUri, new File(dirPath));
        if (file != null) {
            String name = path.substring(index + 1);
            file = file.createFile(mimeType, name);
        }
        return file;
    }

    public static DocumentFile createDocumentDir(Context context, Uri treeUri, String path) {
        int index = path.lastIndexOf("/");
        String dirPath = path.substring(0, index);
        DocumentFile file = parseDocumentFile(context, treeUri, new File(dirPath));
        if (file != null) {
            String name = path.substring(index + 1);
            file = file.createDirectory(name);
        }
        return file;
    }
} public class ColorFade {

    private static AnimatorSet toolbarTitleAnimSet;

    public interface ToolbarTitleFadeCallback {
        void setTitle(Toolbar toolbar);
    }

    public static void fadeBackgroundColor(final View v, final int startColor, final int endColor) {
        ValueAnimator animator = getDefaultValueAnimator();
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                int color = getAnimatedColor(startColor, endColor,
                        valueAnimator.getAnimatedFraction());
                v.setBackgroundColor(color);
            }
        });
        animator.start();
    }

    private static ValueAnimator getDefaultValueAnimator() {
        return getDefaultValueAnimator(0, 100);
    }

    private static ValueAnimator getDefaultValueAnimator(int startValue, int endValue) {
        ValueAnimator animator = ValueAnimator.ofInt(startValue, endValue);
        animator.setDuration(500);
        animator.setInterpolator(new AccelerateDecelerateInterpolator());
        return animator;
    }

    private static int getAnimatedColor(int startColor, int endColor, float animatedValue) {
        int alpha = getAnimatedValue(Color.alpha(startColor), Color.alpha(endColor), animatedValue);
        int red = getAnimatedValue(Color.red(startColor), Color.red(endColor), animatedValue);
        int green = getAnimatedValue(Color.green(startColor), Color.green(endColor), animatedValue);
        int blue = getAnimatedValue(Color.blue(startColor), Color.blue(endColor), animatedValue);
        return Color.argb(alpha, red, green, blue);
    }

    private static int getAnimatedValue(int start, int end, float animatedValue) {
        return (int) (start + (end - start) * animatedValue);
    }

    // imageView saturation fade
    public static void fadeSaturation(final ImageView imageView) {
        if (!Settings.getInstance(imageView.getContext()).fadeImages()) {
            return;
        }

        // code from: https://github.com/nickbutcher/plaid/blob/master/app/src/main/java/io/plaidapp/ui/FeedAdapter.java
        imageView.setHasTransientState(true);
        final AnimUtils.ObservableColorMatrix matrix = new AnimUtils.ObservableColorMatrix();
        final ObjectAnimator saturation = ObjectAnimator.ofFloat(
                matrix, AnimUtils.ObservableColorMatrix.SATURATION, 0f, 1f);
        saturation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener
                () {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                // just animating the color matrix does not invalidate the
                // drawable so need this update listener.  Also have to create a
                // new CMCF as the matrix is immutable :(
                imageView.setColorFilter(new ColorMatrixColorFilter(matrix));
            }
        });
        saturation.setDuration(2000L);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            saturation.setInterpolator(AnimUtils.getFastOutSlowInInterpolator(imageView.getContext()));
        }
        saturation.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                imageView.clearColorFilter();
                imageView.setHasTransientState(false);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                super.onAnimationCancel(animation);
                imageView.clearColorFilter();
                imageView.setHasTransientState(false);
            }

            @Override
            public void onAnimationPause(Animator animation) {
                super.onAnimationPause(animation);
                imageView.clearColorFilter();
                imageView.setHasTransientState(false);
            }
        });
        saturation.start();
    }

    //fade Toolbar title text change
    public static void fadeToolbarTitleColor(final Toolbar toolbar, final int color,
                                             final ToolbarTitleFadeCallback callback) {

        if (toolbarTitleAnimSet != null) {
            toolbarTitleAnimSet.cancel();
        }

        //final int transparent = ContextCompat.getColor(toolbar.getContext(), android.R.color.transparent);
        final int transparent = Color.argb(0, Color.red(color), Color.green(color), Color.blue(color));

        TextView titleView = null;
        for (int i = 0; i < toolbar.getChildCount(); i++) {
            View v = toolbar.getChildAt(i);
            if (v instanceof TextView) {
                titleView = (TextView) v;
                break;
            }
        }
        final TextView finalTextView = titleView;

        ValueAnimator fadeOut = null;
        if (finalTextView != null) {
            fadeOut = getDefaultValueAnimator();
            fadeOut.setDuration(250);
            fadeOut.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    finalTextView.setAlpha(1 - valueAnimator.getAnimatedFraction());
                }
            });
            fadeOut.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    if (callback != null) {
                        callback.setTitle(toolbar);
                    }
                }
            });
        } else {
            toolbar.setTitleTextColor(transparent);
            callback.setTitle(toolbar);
        }

        ValueAnimator fadeIn = getDefaultValueAnimator();
        fadeIn.setDuration(250);
        if (finalTextView != null) {
            fadeIn.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    super.onAnimationStart(animation);
                    finalTextView.setAlpha(1.0f);
                }
            });
        }
        fadeIn.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                toolbar.setTitleTextColor(getAnimatedColor(transparent, color,
                        valueAnimator.getAnimatedFraction()));
            }
        });

        toolbarTitleAnimSet = new AnimatorSet();
        if (fadeOut != null) {
            toolbarTitleAnimSet.playSequentially(fadeOut, fadeIn);
        } else {
            toolbarTitleAnimSet.playSequentially(fadeIn);
        }
        toolbarTitleAnimSet.addListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        toolbarTitleAnimSet = null;
                    }

                    @Override
                    public void onAnimationCancel(Animator animation) {
                        super.onAnimationCancel(animation);
                        toolbar.setTitleTextColor(color);
                    }
                });
        toolbarTitleAnimSet.start();
    }

    public static void fadeDrawableColor(final Drawable d, final int startColor, final int endColor) {
        DrawableCompat.wrap(d);
        ValueAnimator animator = getDefaultValueAnimator();
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                int animatedColor = getAnimatedColor(startColor, endColor,
                        valueAnimator.getAnimatedFraction());
                DrawableCompat.setTint(d, animatedColor);

            }
        });
        AnimatorSet set = new AnimatorSet();
        set.play(animator);
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                DrawableCompat.unwrap(d);
            }
        });
        set.start();
    }

    public static void fadeDrawableAlpha(final Drawable d, int endAlpha) {
        ValueAnimator animator = getDefaultValueAnimator(d.getAlpha(), endAlpha);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                d.setAlpha((Integer) valueAnimator.getAnimatedValue());
            }
        });
        animator.start();
    }
} public class MediaType {

    private static String[] imageExtensions = {"jpg", "png", "jpe", "jpeg", "bmp"};
    private static String[] imageMimeTypes = {"image/*", "image/jpeg", "image/jpg", "image/png", "image/bmp"};

    private static String[] videoExtensions = {"mp4", "mkv", "webm", "avi"};
    private static String[] videoMimeTypes = {"video/*", "video/mp4", "video/x-matroska", "video/webm", "video/avi"};

    private static String[] gifExtensions = {"gif"};
    private static String[] gifMimeTypes = {"image/gif"};

    private static String[] rawExtensions = {"dng", "cr2", "arw"};
    private static String[] rawMimeTypes = {"image/x-adobe-dng", "image/x-canon-cr2", "image/arw", "image/x-sony-arw"};

    private static String[] exifExtensions = {"jpg", "jpe", "jpeg", "dng", "cr2"};
    private static String[] exifMimeTypes = {"image/jpeg", "image/jpg", "image/x-adobe-dng", "image/x-canon-cr2"};

    private static String[] exifWritingExtensions = {"jpg", "jpe", "jpeg"};
    private static String[] exifWritingMimeTypes = {"image/jpeg", "image/jpg"};

    private static String[] wallpaperMimeTypes = {"image/jpeg", "image/png"};

    public static boolean isMedia(String path) {
        return checkImageExtension(path) ||
                checkRAWExtension(path) ||
                checkGifExtension(path) ||
                checkVideoExtension(path);
    }

    public static String getMimeType(String path) {
        if (path == null) {
            return null;
        }
        String fileExtension = MimeTypeMap.getFileExtensionFromUrl(path);
        return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);
    }

    public static String getMimeType(Context context, Uri uri) {
        return context.getContentResolver().getType(uri);
    }

    //trying to check via mimeType
    public static boolean isImage(String path) {
        return path != null && checkImageExtension(path);
    }

    public static boolean isVideo(String path) {
        return path != null && checkVideoExtension(path);
    }

    public static boolean isGif(String path) {
        return path != null && checkGifExtension(path);
    }

    public static boolean isRAWImage(String path) {
        return path != null && checkRAWExtension(path);
    }

    /*check mimeTypes*/
    public static boolean doesSupportExifMimeType(String mimeType) {
        return checkExtension(mimeType, exifMimeTypes);
    }

    public static boolean doesSupportWritingExifMimeType(String mimeType) {
        return checkExtension(mimeType, exifWritingMimeTypes);
    }

    public static boolean checkImageMimeType(String mimeType) {
        return checkExtension(mimeType, imageMimeTypes);
    }

    public static boolean checkVideoMimeType(String mimeType) {
        return checkExtension(mimeType, videoMimeTypes);
    }

    public static boolean checkGifMimeType(String mimeType) {
        return checkExtension(mimeType, gifMimeTypes);
    }

    public static boolean checkRAWMimeType(String mimeType) {
        return checkExtension(mimeType, rawMimeTypes);
    }

    /*check fileExtensions*/
    @SuppressWarnings("unused")
    public static boolean doesSupportExifFileExtension(String path) {
        return checkExtension(path, exifExtensions);
    }

    @SuppressWarnings("unused")
    public static boolean doesSupportWritingExifFileExtension(String path) {
        return checkExtension(path, exifWritingExtensions);
    }

    private static boolean checkImageExtension(String path) {
        return checkExtension(path, imageExtensions);
    }

    private static boolean checkVideoExtension(String path) {
        return checkExtension(path, videoExtensions);
    }

    private static boolean checkGifExtension(String path) {
        return checkExtension(path, gifExtensions);
    }

    private static boolean checkRAWExtension(String path) {
        return checkExtension(path, rawExtensions);
    }

    @SuppressWarnings("DefaultLocale")
    private static boolean checkExtension(String path, String[] extensions) {
        if (path == null) {
            return false;
        }
        for (int i = 0; i < extensions.length; i++) {
            if (path.toLowerCase().endsWith(extensions[i])) {
                return true;
            }
        }
        return false;
    }

    public static boolean suitableAsWallpaper(Context context, Uri uri) {
        if (uri != null) {
            String mimeType = getMimeType(context, uri);
            return mimeType != null && checkExtension(mimeType, wallpaperMimeTypes);
        }
        return false;
    }
} public class Util {

    public static final String SNACKBAR = "SNACKBAR";

    public static int[] getImageDimensions(Context context, Uri uri) {
        int[] dimensions = new int[]{0, 0};

        try {
            InputStream is = context.getContentResolver().openInputStream(uri);

            //try exif
            String mimeType = MediaType.getMimeType(context, uri);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                    && MediaType.doesSupportExifMimeType(mimeType)
                    && is != null) {
                ExifInterface exif = new ExifInterface(is);
                if (exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH) != null
                        && exif.getAttribute(ExifInterface.TAG_IMAGE_LENGTH) != null) {
                    int width = (int) ExifUtil.getCastValue(exif, ExifInterface.TAG_IMAGE_WIDTH);
                    int height = (int) ExifUtil.getCastValue(exif, ExifInterface.TAG_IMAGE_LENGTH);
                    if (width != 0 && height != 0) {
                        return new int[]{width, height};
                    }
                }
            }

            //exif didn't work
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(is, new Rect(0, 0, 0, 0), options);
            dimensions[0] = options.outWidth;
            dimensions[1] = options.outHeight;

            if (is != null) {
                is.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return dimensions;
    }

    public static int[] getVideoDimensions(String path) throws FileNotFoundException {
        File file = new File(path);
        if (!file.exists()) {
            throw new FileNotFoundException();
        }

        MediaMetadataRetriever retriever = new MediaMetadataRetriever();

        try {
            retriever.setDataSource(path);
            Bitmap bitmap = retriever.getFrameAtTime();

            int[] dimensions = new int[2];

            if (bitmap != null) {
                dimensions[0] = bitmap.getWidth() > 0 ? bitmap.getWidth() : 1;
                dimensions[1] = bitmap.getHeight() > 0 ? bitmap.getHeight() : 1;
            }
            return dimensions;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return new int[]{1, 1};
    }

    @SuppressWarnings("inlineValue")
    public static TextView setToolbarTypeface(Toolbar toolbar) {
        for (int i = 0; i < toolbar.getChildCount(); i++) {
            View view = toolbar.getChildAt(i);
            if (view instanceof TextView) {
                TextView textView = (TextView) view;
                if (textView.getText().equals(toolbar.getTitle())) {
                    Typeface typeface = ResourcesCompat
                            .getFont(toolbar.getContext(), R.font.roboto_mono_medium);
                    textView.setTypeface(typeface);
                    return textView;
                }
            }
        }
        return null;
    }

    public static void setDarkStatusBarIcons(final View v) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            v.post(new Runnable() {
                @Override
                @RequiresApi(api = Build.VERSION_CODES.M)
                public void run() {
                    v.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
                }
            });
        }
    }

    public static void setLightStatusBarIcons(final View v) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            v.post(new Runnable() {
                @Override
                public void run() {
                    v.setSystemUiVisibility(0);
                }
            });
        }
    }

    @SuppressWarnings("unused")
    public static boolean areStatusBarIconsDark(final View v) {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && v.getSystemUiVisibility() != 0;
    }

    public static void showSnackbar(Snackbar snackbar) {
        snackbar.getView().setTag(SNACKBAR);
        TextView textView = snackbar.getView()
                .findViewById(android.support.design.R.id.snackbar_text);
        textView.setTypeface(ResourcesCompat
                .getFont(textView.getContext(), R.font.roboto_mono_medium));
        snackbar.show();
    }

    public static Snackbar getPermissionDeniedSnackbar(final View rootView) {
        Snackbar snackbar = Snackbar.make(rootView,
                R.string.read_permission_denied,
                Snackbar.LENGTH_INDEFINITE);
        snackbar.getView().setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                Toast.makeText(rootView.getContext(), R.string.read_permission_denied, Toast.LENGTH_SHORT).show();
                return false;
            }
        });
        return snackbar;
    }

    public static void colorToolbarOverflowMenuIcon(Toolbar toolbar, int color) {
        //set Toolbar overflow icon color
        Drawable drawable = toolbar.getOverflowIcon();
        if (drawable != null) {
            drawable = DrawableCompat.wrap(drawable);
            DrawableCompat.setTint(drawable.mutate(), color);
            toolbar.setOverflowIcon(drawable);
        }
    }

    public static Drawable getAlbumItemSelectorOverlay(Context context) {
        Drawable selectorOverlay = AppCompatResources.getDrawable(context,
                R.drawable.album_item_selected_indicator);

        if (selectorOverlay == null) {
            return null;
        }
        return tintDrawableWithAccentColor(context, selectorOverlay);
    }

    public static Drawable getErrorPlaceholder(Context context) {
        Drawable errorPlaceholder = AppCompatResources.getDrawable(context,
                R.drawable.error_placeholder);

        if (errorPlaceholder == null) {
            return null;
        }
        return tintDrawableWithAccentColor(context, errorPlaceholder);
    }

    private static Drawable tintDrawableWithAccentColor(Context context, Drawable d) {
        Settings s = Settings.getInstance(context);
        Theme theme = s.getThemeInstance(context);

        int tintColor = theme.getAccentColorLight(context);
        d = DrawableCompat.wrap(d);
        DrawableCompat.setTint(d, tintColor);
        return d;
    }

    //int[left, top, right, bottom]
    public static int[] getScreenSize(Activity context) {
        Rect displayRect = new Rect();
        context.getWindow().getDecorView().getWindowVisibleDisplayFrame(displayRect);
        return new int[]{
                displayRect.left, displayRect.top,
                displayRect.right, displayRect.bottom};
    }

    public static float getAnimatorSpeed(Context context) {
        PowerManager powerManager = (PowerManager)
                context.getSystemService(Context.POWER_SERVICE);
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
                && powerManager.isPowerSaveMode()) {
            // Animations are disabled in power save mode, so just show a toast instead.
            return 0.0f;
        }
        return android.provider.Settings.Global.getFloat(context.getContentResolver(),
                android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f);
    }

    public static Locale getLocale(Context context) {
        Locale locale;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            locale = context.getResources().getConfiguration().getLocales().get(0);
        } else {
            locale = context.getResources().getConfiguration().locale;
        }
        return locale;
    }

    public static int dpToPx(Context context, int dp) {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
    }
}
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.SharedElementCallback;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.transition.Fade;
import android.transition.Slide;
import android.transition.TransitionSet;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.animation.AccelerateDecelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import us.koller.cameraroll.R;
import us.koller.cameraroll.data.fileOperations.Move;
import us.koller.cameraroll.data.models.VirtualAlbum;
import us.koller.cameraroll.themes.Theme;
import us.koller.cameraroll.adapter.SelectorModeManager;
import us.koller.cameraroll.adapter.album.AlbumAdapter;
import us.koller.cameraroll.data.models.Album;
import us.koller.cameraroll.data.models.AlbumItem;
import us.koller.cameraroll.data.fileOperations.FileOperation;
import us.koller.cameraroll.data.fileOperations.Rename;
import us.koller.cameraroll.data.models.File_POJO;
import us.koller.cameraroll.data.provider.MediaProvider;
import us.koller.cameraroll.data.provider.Provider;
import us.koller.cameraroll.data.Settings;
import us.koller.cameraroll.ui.widget.FastScrollerRecyclerView;
import us.koller.cameraroll.ui.widget.GridMarginDecoration;
import us.koller.cameraroll.ui.widget.SwipeBackCoordinatorLayout;
import us.koller.cameraroll.util.SortUtil;
import us.koller.cameraroll.util.StorageUtil;
import us.koller.cameraroll.util.animators.ColorFade;
import us.koller.cameraroll.util.MediaType;
import us.koller.cameraroll.util.Util;
                                    int[] screenSize = Util.getScreenSize(AlbumActivity.this);

                                    int[] windowInsets = new int[]{
                                            Math.abs(screenSize[0] - rootView.getLeft()),
                                            Math.abs(screenSize[1] - rootView.getTop()),
                                            Math.abs(screenSize[2] - rootView.getRight()),
                                            Math.abs(screenSize[3] - rootView.getBottom())};

                                    toolbar.setPadding(toolbar.getPaddingStart(),
                                            toolbar.getPaddingTop() + windowInsets[1],
                                            toolbar.getPaddingEnd(),
                                            toolbar.getPaddingBottom());

                                    ViewGroup.MarginLayoutParams toolbarParams
                                            = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams();
                                    toolbarParams.leftMargin += windowInsets[0];
                                    toolbarParams.rightMargin += windowInsets[2];
                                    toolbar.setLayoutParams(toolbarParams);

                                    recyclerView.setPadding(recyclerView.getPaddingStart() + windowInsets[0],
                                            recyclerView.getPaddingTop() + windowInsets[1],
                                            recyclerView.getPaddingEnd() + windowInsets[2],
                                            recyclerView.getPaddingBottom() + windowInsets[3]);
                                    recyclerView.scrollToPosition(0);

                                    fab.setTranslationX(-windowInsets[2]);
                                    fab.setTranslationY(-windowInsets[3]);

                                    rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                                }
                            });
        }

        //onNewIntent(getIntent());

        //needed for transparent statusBar
        setSystemUiFlags();

        //load album
        String path;
        if (savedInstanceState != null && savedInstanceState.containsKey(ALBUM_PATH)) {
            path = savedInstanceState.getString(ALBUM_PATH);
        } else {
            path = getIntent().getStringExtra(ALBUM_PATH);
        }
        MediaProvider.loadAlbum(this, path,
                new MediaProvider.OnAlbumLoadedCallback() {
                    @Override
                    public void onAlbumLoaded(Album album) {
                        AlbumActivity.this.album = album;
                        AlbumActivity.this.onAlbumLoaded(savedInstanceState);
                    }
                });

    }

    private void onAlbumLoaded(Bundle savedInstanceState) {
        int sort_by = Settings.getInstance(this).sortAlbumBy();
        SortUtil.sort(album.getAlbumItems(), sort_by);

        ActionBar actionBar = getSupportActionBar();
        if (!pick_photos && actionBar != null) {
            actionBar.setTitle(album.getName());
        }

        recyclerViewAdapter.setData(album);

        //restore Selector mode, when needed
        if (savedInstanceState != null) {
            final SelectorModeManager manager = new SelectorModeManager(savedInstanceState);
            manager.addCallback(this);
            recyclerViewAdapter.setSelectorModeManager(manager);
            final View rootView = findViewById(R.id.root_view);
            rootView.getViewTreeObserver().addOnGlobalLayoutListener(
                    new ViewTreeObserver.OnGlobalLayoutListener() {
                        @Override
                        public void onGlobalLayout() {
                            rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                            if (manager.isSelectorModeActive()) {
                                recyclerViewAdapter.restoreSelectedItems();
                            }
                        }
                    });

        }

        if (!pick_photos && menu != null) {
            setupMenu();
        }
    }

    private void setupMenu() {
        if (album instanceof VirtualAlbum) {
            menu.findItem(R.id.exclude).setVisible(false);
            menu.findItem(R.id.rename).setVisible(false);
        } else {
            //setup exclude checkbox
            boolean enabled = !Provider
                    .isDirExcludedBecauseParentDirIsExcluded(album.getPath(),
                            Provider.getExcludedPaths());
            menu.findItem(R.id.exclude).setEnabled(enabled);
            menu.findItem(R.id.exclude).setChecked(album.excluded || !enabled);
        }

        menu.findItem(R.id.pin).setChecked(album.pinned);

        if (recyclerViewAdapter.isSelectorModeActive()) {
            handleMenuVisibilityForSelectorMode(true);
        }
    }

    @Override
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void onActivityReenter(int requestCode, Intent data) {
        super.onActivityReenter(requestCode, data);
        Log.d("AlbumActivity", "onActivityReenter: " + this);
        if (data != null) {
            sharedElementReturnPosition = data.getIntExtra(EXTRA_CURRENT_ALBUM_POSITION, -1);
            if (sharedElementReturnPosition > -1 && album != null
                    && sharedElementReturnPosition < album.getAlbumItems().size()) {
                AlbumItem albumItem = album.getAlbumItems().get(sharedElementReturnPosition);