/*
* Copyright (C) 2014 Saravan Pantham
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jams.music.player.FoldersFragment;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.TranslateAnimation;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.jams.music.player.AsyncTasks.AsyncCopyMoveTask;
import com.jams.music.player.AsyncTasks.AsyncDeleteTask;
import com.jams.music.player.Helpers.TypefaceHelper;
import com.jams.music.player.Helpers.UIElementsHelper;
import com.jams.music.player.MainActivity.MainActivity;
import com.jams.music.player.R;
import com.jams.music.player.Utils.Common;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.comparator.NameFileComparator;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
/**
* FilesFoldersFragment. Contained within MainActivity.
*
* @author Saravan Pantham
*/
public class FilesFoldersFragment extends Fragment {
//Context.
private Context mContext;
private FilesFoldersFragment mFilesFoldersFragment;
private Common mApp;
//UI Elements.
private ListView listView;
//Folder parameter ArrayLists.
private String rootDir;
public static String currentDir;
private List<String> fileFolderNameList = null;
private List<String> fileFolderPathList = null;
private List<String> fileFolderSizeList = null;
private List<Integer> fileFolderTypeList = null;
//File size/unit dividers
private final long kiloBytes = 1024;
private final long megaBytes = kiloBytes * kiloBytes;
private final long gigaBytes = megaBytes * kiloBytes;
private final long teraBytes = gigaBytes * kiloBytes;
//List of subdirectories within a directory (Used by "Play Folder Recursively").
private ArrayList<String> subdirectoriesList = new ArrayList<String>();
//Flag that determines whether hidden files are displayed or not.
private boolean SHOW_HIDDEN_FILES = false;
//Temp file for copy/move operations.
public File copyMoveSourceFile;
//Flag that indicates if copyMoveSourceFile should be moved or copied.
public boolean shouldMoveCopiedFile = false;
public boolean mIsPasteShown = false;
//HashMap to store the each folder's previous scroll/position state.
private HashMap<String, Parcelable> mFolderStateMap;
//Handler.
private Handler mHandler = new Handler();
public static final int FOLDER = 0;
public static final int FILE = 1;
public static final int AUDIO_FILE = 3;
public static final int PICTURE_FILE = 4;
public static final int VIDEO_FILE = 5;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_folders, container, false);
mContext = getActivity().getApplicationContext();
mFilesFoldersFragment = this;
mApp = (Common) mContext;
mFolderStateMap = new HashMap<String, Parcelable>();
//Set the hidden files flag.
SHOW_HIDDEN_FILES = mApp.getSharedPreferences().getBoolean("SHOW_HIDDEN_FILES", false);
listView = (ListView) rootView.findViewById(R.id.folders_list_view);
listView.setFastScrollEnabled(true);
listView.setVisibility(View.INVISIBLE);
//Set the background color based on the theme.
rootView.setBackgroundColor(UIElementsHelper.getBackgroundColor(mContext));
//Apply the ListView params.
//Apply the ListViews' dividers.
if (mApp.getCurrentTheme()==Common.DARK_THEME) {
listView.setDivider(mContext.getResources().getDrawable(R.drawable.icon_list_divider));
} else {
listView.setDivider(mContext.getResources().getDrawable(R.drawable.icon_list_divider_light));
}
listView.setDividerHeight(1);
//KitKat translucent navigation/status bar.
if (Build.VERSION.SDK_INT==Build.VERSION_CODES.KITKAT) {
int topPadding = Common.getStatusBarHeight(mContext);
//Calculate navigation bar height.
int navigationBarHeight = 0;
int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
navigationBarHeight = getResources().getDimensionPixelSize(resourceId);
}
if (rootView!=null) {
rootView.setPadding(0, topPadding, 0, 0);
}
listView.setClipToPadding(false);
listView.setPadding(0, 0, 0, navigationBarHeight);
}
rootDir = mApp.getSharedPreferences().getString("DEFAULT_FOLDER", Environment.getExternalStorageDirectory().getPath());
currentDir = rootDir;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
slideUpListView();
}
}, 250);
return rootView;
}
/**
* Slides in the ListView.
*/
private void slideUpListView() {
getDir(rootDir, null);
TranslateAnimation animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
Animation.RELATIVE_TO_SELF, 0.0f,
Animation.RELATIVE_TO_SELF, 2.0f,
Animation.RELATIVE_TO_SELF, 0.0f);
animation.setDuration(600);
animation.setInterpolator(new AccelerateDecelerateInterpolator());
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationEnd(Animation arg0) {
}
@Override
public void onAnimationRepeat(Animation arg0) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationStart(Animation arg0) {
listView.setVisibility(View.VISIBLE);
}
});
listView.startAnimation(animation);
}
/**
* Retrieves the folder hierarchy for the specified folder.
*
* @param dirPath The path of the new folder.
* @param restoreState The state of the ListView that should be restored. Pass
* null if the ListView's position should not be restored.
*/
private void getDir(String dirPath, Parcelable restoreState) {
((MainActivity) getActivity()).showFolderFragmentActionItems(dirPath,
getActivity().getMenuInflater(),
((MainActivity) getActivity()).getMenu(),
mIsPasteShown);
fileFolderNameList = new ArrayList<String>();
fileFolderPathList = new ArrayList<String>();
fileFolderSizeList = new ArrayList<String>();
fileFolderTypeList = new ArrayList<Integer>();
File f = new File(dirPath);
File[] files = f.listFiles();
if (files!=null) {
//Sort the files by name.
Arrays.sort(files, NameFileComparator.NAME_INSENSITIVE_COMPARATOR);
for(int i=0; i < files.length; i++) {
File file = files[i];
if(file.isHidden()==SHOW_HIDDEN_FILES && file.canRead()) {
if (file.isDirectory()) {
/*
* Starting with Android 4.2, /storage/emulated/legacy/...
* is a symlink that points to the actual directory where
* the user's files are stored. We need to detect the
* actual directory's file path here.
*/
String filePath;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
filePath = getRealFilePath(file.getAbsolutePath());
else
filePath = file.getAbsolutePath();
fileFolderPathList.add(filePath);
fileFolderNameList.add(file.getName());
File[] listOfFiles = file.listFiles();
if (listOfFiles!=null) {
fileFolderTypeList.add(FOLDER);
if (listOfFiles.length==1) {
fileFolderSizeList.add("" + listOfFiles.length + " item");
} else {
fileFolderSizeList.add("" + listOfFiles.length + " items");
}
} else {
fileFolderTypeList.add(FOLDER);
fileFolderSizeList.add("Unknown items");
}
} else {
try {
String path = file.getCanonicalPath();
fileFolderPathList.add(path);
} catch (IOException e) {
continue;
}
fileFolderNameList.add(file.getName());
String fileName = "";
try {
fileName = file.getCanonicalPath();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Add the file element to fileFolderTypeList based on the file type.
if (getFileExtension(fileName).equalsIgnoreCase("mp3") ||
getFileExtension(fileName).equalsIgnoreCase("3gp") ||
getFileExtension(fileName).equalsIgnoreCase("mp4") ||
getFileExtension(fileName).equalsIgnoreCase("m4a") ||
getFileExtension(fileName).equalsIgnoreCase("aac") ||
getFileExtension(fileName).equalsIgnoreCase("ts") ||
getFileExtension(fileName).equalsIgnoreCase("flac") ||
getFileExtension(fileName).equalsIgnoreCase("mid") ||
getFileExtension(fileName).equalsIgnoreCase("xmf") ||
getFileExtension(fileName).equalsIgnoreCase("mxmf") ||
getFileExtension(fileName).equalsIgnoreCase("midi") ||
getFileExtension(fileName).equalsIgnoreCase("rtttl") ||
getFileExtension(fileName).equalsIgnoreCase("rtx") ||
getFileExtension(fileName).equalsIgnoreCase("ota") ||
getFileExtension(fileName).equalsIgnoreCase("imy") ||
getFileExtension(fileName).equalsIgnoreCase("ogg") ||
getFileExtension(fileName).equalsIgnoreCase("mkv") ||
getFileExtension(fileName).equalsIgnoreCase("wav")) {
//The file is an audio file.
fileFolderTypeList.add(AUDIO_FILE);
fileFolderSizeList.add("" + getFormattedFileSize(file.length()));
} else if (getFileExtension(fileName).equalsIgnoreCase("jpg") ||
getFileExtension(fileName).equalsIgnoreCase("gif") ||
getFileExtension(fileName).equalsIgnoreCase("png") ||
getFileExtension(fileName).equalsIgnoreCase("bmp") ||
getFileExtension(fileName).equalsIgnoreCase("webp")) {
//The file is a picture file.
fileFolderTypeList.add(PICTURE_FILE);
fileFolderSizeList.add("" + getFormattedFileSize(file.length()));
} else if (getFileExtension(fileName).equalsIgnoreCase("3gp") ||
getFileExtension(fileName).equalsIgnoreCase("mp4") ||
getFileExtension(fileName).equalsIgnoreCase("3gp") ||
getFileExtension(fileName).equalsIgnoreCase("ts") ||
getFileExtension(fileName).equalsIgnoreCase("webm") ||
getFileExtension(fileName).equalsIgnoreCase("mkv")) {
//The file is a video file.
fileFolderTypeList.add(VIDEO_FILE);
fileFolderSizeList.add("" + getFormattedFileSize(file.length()));
} else {
//We don't have an icon for this file type so give it the generic file flag.
fileFolderTypeList.add(FILE);
fileFolderSizeList.add("" + getFormattedFileSize(file.length()));
}
}
}
}
}
FoldersListViewAdapter foldersListViewAdapter = new FoldersListViewAdapter(getActivity(),
this,
fileFolderNameList,
fileFolderTypeList,
fileFolderSizeList,
fileFolderPathList);
listView.setAdapter(foldersListViewAdapter);
foldersListViewAdapter.notifyDataSetChanged();
//Restore the ListView's previous state.
if (restoreState!=null) {
listView.onRestoreInstanceState(restoreState);
} else if (mFolderStateMap.containsKey(dirPath)) {
listView.onRestoreInstanceState(mFolderStateMap.get(dirPath));
}
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View view, int index, long arg3) {
//Store the current folder's state in the HashMap.
if (mFolderStateMap.size()==3) {
mFolderStateMap.clear();
}
mFolderStateMap.put(currentDir, listView.onSaveInstanceState());
String newPath = fileFolderPathList.get(index);
if ((Integer) view.getTag(R.string.folder_list_item_type)==FOLDER)
currentDir = newPath;
//Check if the selected item is a folder or a file.
if (fileFolderTypeList.get(index)==FOLDER) {
getDir(newPath, null);
} else {
int fileIndex = 0;
for (int i=0; i < index; i++) {
if (fileFolderTypeList.get(i)==AUDIO_FILE)
fileIndex++;
}
play(fileFolderTypeList.get(index), fileIndex, currentDir);
}
}
});
}
/**
* Refreshes the ListView with the current dataset.
*/
public void refreshListView() {
//Update the ListView.
getDir(currentDir, listView.onSaveInstanceState());
}
/**
* Resolves the /storage/emulated/legacy paths to
* their true folder path representations. Required
* for Nexii and other devices with no SD card.
*/
@SuppressLint("SdCardPath")
private String getRealFilePath(String filePath) {
if (filePath.equals("/storage/emulated/0") ||
filePath.equals("/storage/emulated/0/") ||
filePath.equals("/storage/emulated/legacy") ||
filePath.equals("/storage/emulated/legacy/") ||
filePath.equals("/storage/sdcard0") ||
filePath.equals("/storage/sdcard0/") ||
filePath.equals("/sdcard") ||
filePath.equals("/sdcard/") ||
filePath.equals("/mnt/sdcard") ||
filePath.equals("/mnt/sdcard/")) {
return Environment.getExternalStorageDirectory().toString();
}
return filePath;
}
/**
* Calculates the parent dir of the current dir and calls getDir().
* Returns true if the parent dir is the rootDir
*/
public boolean getParentDir() {
if (currentDir.equals("/"))
return true;
//Get the current folder's parent folder.
File currentFolder = new File(currentDir);
String parentFolder = "";
try {
parentFolder = currentFolder.getParentFile().getCanonicalPath();
} catch (Exception e) {
e.printStackTrace();
}
FilesFoldersFragment.currentDir = parentFolder;
getDir(parentFolder, null);
return false;
}
/**
* Takes in a file size value and formats it.
*/
public String getFormattedFileSize(final long value) {
final long[] dividers = new long[] { teraBytes, gigaBytes, megaBytes, kiloBytes, 1 };
final String[] units = new String[] { "TB", "GB", "MB", "KB", "bytes" };
if(value < 1) {
return "";
}
String result = null;
for(int i = 0; i < dividers.length; i++) {
final long divider = dividers[i];
if(value >= divider) {
result = format(value, divider, units[i]);
break;
}
}
return result;
}
public String format(final long value, final long divider, final String unit) {
final double result = divider > 1 ? (double) value / (double) divider : (double) value;
return new DecimalFormat("#,##0.#").format(result) + " " + unit;
}
public String getFileExtension(String fileName) {
String fileNameArray[] = fileName.split("\\.");
String extension = fileNameArray[fileNameArray.length-1];
return extension;
}
/**
* This method goes through a folder recursively and saves all its
* subdirectories to an ArrayList (subdirectoriesList).
*/
public void iterateThruFolder(String path) {
File root = new File(path);
File[] list = root.listFiles();
if (list==null) {
return;
}
for (File f : list) {
if (f.isDirectory()) {
iterateThruFolder(f.getAbsolutePath());
if (!subdirectoriesList.contains(f.getPath())) {
subdirectoriesList.add(f.getPath());
}
}
}
}
/**
* Plays the specified file/folder.
*
* @param itemType Specifies whether the input path is a file path
* or a folder path.
* @param index The index of the first song to play. Pass 0 if itemType
* is FOLDER.
* @param folderPath The path of the folder that should be played.
*/
public void play(int itemType, int index, String folderPath) {
//Build the query's selection clause.
String querySelection = MediaStore.Audio.Media.DATA + " LIKE "
+ "'" + folderPath.replace("'", "''") + "/%'";
//Exclude all subfolders from this playback sequence if we're playing a file.
if (itemType==AUDIO_FILE) {
for (int i = 0; i < fileFolderPathList.size(); i++) {
if (fileFolderTypeList.get(i) == FOLDER)
querySelection += " AND " + MediaStore.Audio.Media.DATA + " NOT LIKE "
+ "'" + fileFolderPathList.get(i).replace("'", "''") + "/%'";
}
mApp.getPlaybackKickstarter().initPlayback(mContext,
querySelection,
Common.PLAY_ALL_IN_FOLDER,
index,
true, false);
} else if (itemType==FOLDER) {
mApp.getPlaybackKickstarter().initPlayback(mContext,
querySelection,
Common.PLAY_ALL_IN_FOLDER,
index,
true, false);
} else {
Toast.makeText(mContext, R.string.cant_play_this_file, Toast.LENGTH_SHORT).show();
}
}
/**
* Displays a "Rename" dialog and renames the specified file/folder.
*
* @param path The path of the folder/file that needs to be renamed.
*/
public void rename(String path) {
final File renameFile = new File(path);
final AlertDialog renameAlertDialog = new AlertDialog.Builder(getActivity()).create();
final EditText fileEditText = new EditText(getActivity());
fileEditText.setHint(R.string.file_name);
fileEditText.setSingleLine(true);
fileEditText.setText(renameFile.getName());
renameAlertDialog.setView(fileEditText);
renameAlertDialog.setTitle(R.string.rename);
renameAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
mContext.getResources().getString(R.string.cancel),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
renameAlertDialog.dismiss();
}
});
renameAlertDialog.setButton(DialogInterface.BUTTON_POSITIVE,
mContext.getResources().getString(R.string.rename),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//Check if the new file name is empty.
if (fileEditText.getText().toString().isEmpty()) {
Toast.makeText(getActivity(), R.string.enter_a_name_for_folder, Toast.LENGTH_LONG).show();
} else {
File newNameFile = null;
try {
newNameFile = new File(renameFile.getParentFile().getCanonicalPath() + "/" + fileEditText.getText().toString());
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getActivity(), R.string.folder_could_not_be_renamed, Toast.LENGTH_LONG).show();
return;
}
try {
if (renameFile.isDirectory())
FileUtils.moveDirectory(renameFile, newNameFile);
else
FileUtils.moveFile(renameFile, newNameFile);
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getActivity(), R.string.folder_could_not_be_renamed, Toast.LENGTH_LONG).show();
return;
}
Toast.makeText(getActivity(), R.string.folder_renamed, Toast.LENGTH_SHORT).show();
renameAlertDialog.dismiss();
refreshListView();
}
}
});
renameAlertDialog.show();
}
/**
* Stores the specified file/folder's path in a temp variable and displays
* the "Paste" option in the ActionBar.
*
* @param path The path of the file/folder to copy/move.
* @param shouldMove Pass true if the file/folder should be moved instead of copied.
*/
public void copyMove(String path, boolean shouldMove) {
shouldMoveCopiedFile = shouldMove;
copyMoveSourceFile = new File(path);
if (!copyMoveSourceFile.exists()) {
Toast.makeText(mContext, R.string.cant_copy_this_file_folder, Toast.LENGTH_SHORT).show();
return;
}
//Show the paste option in the ActionBar.
mIsPasteShown = true;
((MainActivity) getActivity()).showFolderFragmentActionItems(currentDir,
getActivity().getMenuInflater(),
((MainActivity) getActivity()).getMenu(),
true);
}
/**
* Pastes the specified file into the current directory.
*
* @param file The file to paste into the current directory.
*/
public void pasteIntoCurrentDir(File file) {
mIsPasteShown = false;
AsyncCopyMoveTask task = new AsyncCopyMoveTask(mContext, file, new File(currentDir + "/" + file.getName()),
this, shouldMoveCopiedFile);
task.execute();
}
/**
* Deletes the specified file.
*
* @param file The file to delete.
*/
public void deleteFile(File file) {
int fileType;
if (file.isDirectory())
fileType = FOLDER;
else
fileType = FILE;
AsyncDeleteTask task = new AsyncDeleteTask(getActivity(), this, file, fileType);
task.execute();
}
@Override
public void onDestroyView() {
super.onDestroyView();
mContext = null;
listView = null;
}
/*
* Getter methods.
*/
public String getCurrentDir() {
return currentDir;
}
/*
* Setter methods.
*/
public void setCurrentDir(String currentDir) {
this.currentDir = currentDir;
}
}