com.orpheusdroid.screenrecorder.VideosListFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.orpheusdroid.screenrecorder.VideosListFragment.java

Source

/*
 * Copyright (c) 2016-2017. Vijai Chandra Prasad R.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses
 */

package com.orpheusdroid.screenrecorder;

import android.Manifest;
import android.app.Fragment;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.orpheusdroid.screenrecorder.adapter.Video;
import com.orpheusdroid.screenrecorder.adapter.VideoRecyclerAdapter;

import java.io.File;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * <p>
 *     This fragment lists the videos recorded
 * </p>
 *
 * @author Vijai Chandra Prasad .R
 * @see VideoRecyclerAdapter
 * @see PermissionResultListener
 * @see GetVideosAsync
 */
public class VideosListFragment extends Fragment
        implements PermissionResultListener, SwipeRefreshLayout.OnRefreshListener {
    /**
     * RecyclerView
     */
    private RecyclerView videoRV;

    /**
     * TextView to display empty videos or permission denied message
     */
    private TextView message;

    /**
     * SharedPreference object
     */
    private SharedPreferences prefs;

    /**
     * SwipeRefreshLayout object to refresh list
     */
    private SwipeRefreshLayout swipeRefreshLayout;

    /**
     * ArrayList to store all videos from the save location
     */
    private ArrayList<Video> videosList = new ArrayList<>();

    private boolean loadInOnCreate = false;

    public VideosListFragment() {

    }

    /**
     * Method to check if the file's meme type is video
     *
     * @param path String - path to the file
     * @return boolean
     */
    private static boolean isVideoFile(String path) {
        String mimeType = URLConnection.guessContentTypeFromName(path);
        return mimeType != null && mimeType.startsWith("video");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_videos, container, false);
        message = view.findViewById(R.id.message_tv);
        videoRV = view.findViewById(R.id.videos_rv);

        swipeRefreshLayout = view.findViewById(R.id.swipeRefresh);
        swipeRefreshLayout.setOnRefreshListener(this);
        swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary, android.R.color.holo_green_dark,
                android.R.color.holo_orange_dark, android.R.color.holo_blue_dark);

        prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());

        if (loadInOnCreate)
            checkPermission();

        return view;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    //Load videos from the directory only when the fragment is visible to the screen
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser && getActivity() != null) {
            Log.d(Const.TAG, "Videos fragment is visible load the videos");
            checkPermission();
        } else if (isVisibleToUser && getActivity() == null)
            loadInOnCreate = true;
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        MenuItem refresh = menu.add("Refresh");
        refresh.setIcon(R.drawable.ic_refresh_white_24dp);
        refresh.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS);
        refresh.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                // Prevent repeated refresh requests
                if (swipeRefreshLayout.isRefreshing())
                    return false;
                videosList.clear();
                checkPermission();
                Log.d(Const.TAG, "Refreshing");
                return false;
            }
        });
    }

    /**
     * Check if we have permission to read the external storage and load the videos into ArrayList<Video>
     */
    private void checkPermission() {
        if (ContextCompat.checkSelfPermission(getActivity(),
                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            if (getActivity() instanceof MainActivity) {
                ((MainActivity) getActivity()).setPermissionResultListener(this);
                ((MainActivity) getActivity()).requestPermissionStorage();
            }
        } else {
            //We have required permission now and lets populate the video from the selected
            // directory if the arraylist holding videos is empty
            if (videosList.isEmpty()) {
                File directory = new File(prefs.getString(getString(R.string.savelocation_key),
                        Environment.getExternalStorageDirectory() + File.separator + "screenrecorder"));
                //Remove directory pointers and other files from the list
                if (!directory.exists()) {
                    MainActivity.createDir();
                    Log.d(Const.TAG, "Directory missing! Creating dir");
                }
                ArrayList<File> filesList = new ArrayList<File>();
                if (directory.isDirectory() && directory.exists()) {
                    filesList.addAll(Arrays.asList(getVideos(directory.listFiles())));
                }
                //Read the videos and extract details from it in async.
                // This is essential if the directory contains huge number of videos

                new GetVideosAsync().execute(filesList.toArray(new File[filesList.size()]));
            }
        }
    }

    /**
     * Filter all video files from array of files
     *
     * @param files File[] containing files from a directory
     * @return File[] containing only video files
     */
    private File[] getVideos(File[] files) {
        List<File> newFiles = new ArrayList<>();
        for (File file : files) {
            if (!file.isDirectory() && isVideoFile(file.getPath()))
                newFiles.add(file);
        }
        return newFiles.toArray(new File[newFiles.size()]);
    }

    /**
     * Init recyclerview once the videos list is ready
     *
     * @param videos ArrayList<Video>
     */
    private void setRecyclerView(ArrayList<Video> videos) {
        videoRV.setHasFixedSize(true);
        final GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), 2);
        videoRV.setLayoutManager(layoutManager);
        final VideoRecyclerAdapter adapter = new VideoRecyclerAdapter(getActivity(), videos, this);
        videoRV.setAdapter(adapter);
        //Set the span to 1 (width to match the screen) if the view type is section
        layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return adapter.isSection(position) ? layoutManager.getSpanCount() : 1;
            }
        });
    }

    //Permission result callback method
    @Override
    public void onPermissionResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
        case Const.EXTDIR_REQUEST_CODE:
            if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                Log.d(Const.TAG, "Storage permission granted.");
                //Performing storage task immediately after granting permission sometimes causes
                //permission not taking effect.
                checkPermission();
            } else {
                Log.d(Const.TAG, "Storage permission denied.");
                videoRV.setVisibility(View.GONE);
                message.setText(R.string.video_list_permission_denied_message);
            }
            break;
        }
    }

    /**
     * Clear the videos ArrayList once the save directory is changed which forces reloading of videos from new directory
     */
    public void removeVideosList() {
        videosList.clear();
        Log.d(Const.TAG, "Reached video fragment");
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d(Const.TAG, "Refresh data after edit!");
        removeVideosList();
        checkPermission();
    }

    @Override
    public void onRefresh() {
        videosList.clear();
        checkPermission();
        Log.d(Const.TAG, "Refreshing");
    }

    /**
     * Class to retrieve videos from the directory asynchronously
     *
     * @author Vijai Chandra Prasad .R
     */
    class GetVideosAsync extends AsyncTask<File[], Integer, ArrayList<Video>> {
        //ProgressDialog progress;
        File[] files;
        ContentResolver resolver;

        GetVideosAsync() {
            resolver = getActivity().getApplicationContext().getContentResolver();
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //Set refreshing to true
            swipeRefreshLayout.setRefreshing(true);
        }

        @Override
        protected void onPostExecute(ArrayList<Video> videos) {
            //If the directory has no videos, remove recyclerview from rootview and show empty message.
            // Else set recyclerview and remove message textview
            if (videos.isEmpty()) {
                videoRV.setVisibility(View.GONE);
                message.setVisibility(View.VISIBLE);
            } else {
                //Sort the videos in a descending order
                Collections.sort(videos, Collections.<Video>reverseOrder());
                setRecyclerView(addSections(videos));
                videoRV.setVisibility(View.VISIBLE);
                message.setVisibility(View.GONE);
            }
            //Finish refreshing
            swipeRefreshLayout.setRefreshing(false);
        }

        //Add sections depending on the date the video is recorded to array list
        private ArrayList<Video> addSections(ArrayList<Video> videos) {
            ArrayList<Video> videosWithSections = new ArrayList<>();
            Date currentSection = new Date();
            Log.d(Const.TAG, "Original Length: " + videos.size());
            for (int i = 0; i < videos.size(); i++) {
                Video video = videos.get(i);
                //Add the first section arbitrarily
                if (i == 0) {
                    videosWithSections.add(new Video(true, video.getLastModified()));
                    videosWithSections.add(video);
                    currentSection = video.getLastModified();
                    continue;
                }
                if (addNewSection(currentSection, video.getLastModified())) {
                    videosWithSections.add(new Video(true, video.getLastModified()));
                    currentSection = video.getLastModified();
                }
                videosWithSections.add(video);
            }
            Log.d(Const.TAG, "Length with sections: " + videosWithSections.size());
            return videosWithSections;
        }

        /**
         * Method to add date sections to videos list
         * <p>
         * <p></p>Check if a new Section is to be added by comparing the difference of the section date
         * and the video's last modified date</p>
         *
         * @param current Date of current video
         * @param next    Date of next video
         * @return boolean if a new section must be added
         */
        private boolean addNewSection(Date current, Date next) {
            Calendar currentSectionDate = toCalendar(current.getTime());
            Calendar nextVideoDate = toCalendar(next.getTime());

            // Get the represented date in milliseconds
            long milis1 = currentSectionDate.getTimeInMillis();
            long milis2 = nextVideoDate.getTimeInMillis();

            // Calculate difference in milliseconds
            int dayDiff = (int) Math.abs((milis2 - milis1) / (24 * 60 * 60 * 1000));
            Log.d(Const.TAG, "Date diff is: " + (dayDiff));
            return dayDiff > 0;
        }

        /**
         * Method to return a Calander object from the timestamp
         *
         * @param timestamp long timestamp
         * @return Calendar
         */
        private Calendar toCalendar(long timestamp) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(timestamp);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);
            return calendar;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);

            Log.d(Const.TAG, "Progress is :" + values[0]);
        }

        @Override
        protected ArrayList<Video> doInBackground(File[]... arg) {
            //Get video file name, Uri and video thumbnail from mediastore
            files = arg[0];
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
                if (!file.isDirectory() && isVideoFile(file.getPath())) {
                    videosList.add(new Video(file.getName(), file, getBitmap(file), new Date(file.lastModified())));
                    //Update progress dialog
                    publishProgress(i);
                }
            }
            return videosList;
        }

        /**
         * Method to get thumbnail from mediastore for video file
         *
         * @param file File object of the video
         * @return Bitmap
         */
        Bitmap getBitmap(File file) {
            String[] projection = { MediaStore.Images.Media._ID, MediaStore.Images.Media.BUCKET_ID,
                    MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.DATA };
            Cursor cursor = resolver.query(MediaStore.Video.Media.getContentUri("external"), projection,
                    MediaStore.Images.Media.DATA + "=? ", new String[] { file.getPath() }, null);

            if (cursor != null && cursor.moveToNext()) {
                int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
                int id = cursor.getInt(idColumn);
                Bitmap thumbNail = MediaStore.Video.Thumbnails.getThumbnail(resolver, id,
                        MediaStore.Video.Thumbnails.MINI_KIND, null);
                Log.d(Const.TAG, "Retrieved thumbnail for file: " + file.getName());
                cursor.close();
                return thumbNail;
            }
            return null;
        }
    }
}