dentex.youtube.downloader.DashboardActivity.java Source code

Java tutorial

Introduction

Here is the source code for dentex.youtube.downloader.DashboardActivity.java

Source

/***
Copyright (c) 2012-2013 Samuele Rini
    
   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
       
   ***
       
   https://github.com/dentex/ytdownloader/
https://sourceforge.net/projects/ytdownloader/
       
   ***
       
   Different Licenses and Credits where noted in code comments.
*/

package dentex.youtube.downloader;

import group.pals.android.lib.ui.filechooser.FileChooserActivity;
import group.pals.android.lib.ui.filechooser.io.localfile.LocalFile;
import group.pals.android.lib.ui.filechooser.services.IFileProvider;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.cmc.music.common.ID3WriteException;
import org.cmc.music.metadata.MusicMetadata;
import org.cmc.music.metadata.MusicMetadataSet;
import org.cmc.music.myid3.MyID3;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.os.Parcelable;
import android.provider.MediaStore.Video.Thumbnails;
import android.support.v4.app.NotificationCompat;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.bugsense.trace.BugSenseHandler;
import com.matsuhiro.android.download.DownloadTask;
import com.matsuhiro.android.download.DownloadTaskListener;
import com.matsuhiro.android.download.InvalidYoutubeLinkException;
import com.matsuhiro.android.download.Maps;

import dentex.youtube.downloader.ffmpeg.FfmpegController;
import dentex.youtube.downloader.ffmpeg.ShellUtils.ShellCallback;
import dentex.youtube.downloader.utils.Json;
import dentex.youtube.downloader.utils.PopUps;
import dentex.youtube.downloader.utils.Utils;

public class DashboardActivity extends Activity {

    private final static String DEBUG_TAG = "DashboardActivity";
    public static boolean isDashboardRunning;
    private ContextThemeWrapper boxThemeContextWrapper = new ContextThemeWrapper(this, R.style.BoxTheme);
    private NotificationCompat.Builder aBuilder;
    private NotificationManager aNotificationManager;
    private int totSeconds;
    private int currentTime;
    protected File audioFile;
    protected String basename;
    private String aSuffix = ".audio";
    private String vfilename;
    private boolean removeVideo;
    private boolean removeAudio;
    private ListView lv;
    private Editable searchText;

    static List<String> idEntries = new ArrayList<String>();
    static List<String> typeEntries = new ArrayList<String>();
    static List<String> linkEntries = new ArrayList<String>();
    static List<Integer> posEntries = new ArrayList<Integer>();
    static List<String> statusEntries = new ArrayList<String>();
    static List<String> pathEntries = new ArrayList<String>();
    static List<String> filenameEntries = new ArrayList<String>();
    static List<String> basenameEntries = new ArrayList<String>();
    static List<String> audioExtEntries = new ArrayList<String>();
    static List<String> sizeEntries = new ArrayList<String>();
    static List<String> partSizeEntries = new ArrayList<String>();
    static List<Integer> progressEntries = new ArrayList<Integer>();
    static List<Long> speedEntries = new ArrayList<Long>();

    private static int entries;

    private static List<DashboardListItem> itemsList = new ArrayList<DashboardListItem>();
    private static DashboardAdapter da;
    private boolean isSearchBarVisible;
    private DashboardListItem currentItem = null;
    private TextView userFilename;
    private boolean extrTypeIsMp3Conv;
    int posX;
    private String type;
    private boolean isFfmpegRunning = false;
    private boolean isAnyAsyncInProgress = false;

    private String tagArtist;
    private String tagAlbum;
    private String tagTitle;
    private String tagYear;
    private String tagGenre;

    private String ogg = "OGG Vorbis";
    private String aac = "AAC (Advanced Audio Codec)";
    private String mp3 = "MP3 (low quality)";
    //private String aac_mp3 = "AAC / MP3";

    private boolean newClick;
    public static long countdown;

    public static Activity sDashboard;

    //long BeforeTime = System.currentTimeMillis();
    //long TotalRxBeforeTest = TrafficStats.getUidRxBytes(YTD.uid);
    //long TotalTxBeforeTest = TrafficStats.getUidTxBytes(YTD.uid);

    private Timer autoUpdate;
    public static boolean isLandscape;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        BugSenseHandler.leaveBreadcrumb("DashboardActivity_onCreate");
        //Utils.logger("v", "TotalRxBeforeTest: " + TotalRxBeforeTest, DEBUG_TAG);

        // Theme init
        Utils.themeInit(this);

        setContentView(R.layout.activity_dashboard);

        // Language init
        Utils.langInit(this);

        // Detect screen orientation
        int or = this.getResources().getConfiguration().orientation;
        isLandscape = (or == 2) ? true : false;

        sDashboard = DashboardActivity.this;

        if (da != null) {
            clearAdapterAndLists();
        }

        countdown = 10;

        parseJson(this);
        updateProgressBars();
        buildList();

        lv = (ListView) findViewById(R.id.dashboard_list);

        da = new DashboardAdapter(itemsList, this);

        if (da.isEmpty()) {
            showEmptyListInfo(this);
        } else {
            lv.setAdapter(da);
        }

        /*Log.i(DEBUG_TAG, "ADML Maps:" +
         "\ndtMap:                 " + Maps.dtMap +
         "\nmDownloadPercentMap:   " + Maps.mDownloadPercentMap +
         "\nmDownloadSizeMap:      " + Maps.mDownloadSizeMap + 
         "\nmTotalSizeMap:         " + Maps.mTotalSizeMap);*/

        lv.setTextFilterEnabled(true);

        lv.setClickable(true);

        lv.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                if (!isAnyAsyncInProgress) {
                    currentItem = da.getItem(position); // in order to refer to the filtered item
                    newClick = true;

                    final boolean ffmpegEnabled = YTD.settings.getBoolean("enable_advanced_features", false);

                    AlertDialog.Builder builder = new AlertDialog.Builder(boxThemeContextWrapper);
                    builder.setTitle(currentItem.getFilename());

                    if (currentItem.getStatus().equals(getString(R.string.json_status_completed))
                            || currentItem.getStatus().equals(getString(R.string.json_status_imported))) {

                        final boolean audioIsSupported = !currentItem.getAudioExt().equals("unsupported");
                        final File in = new File(currentItem.getPath(), currentItem.getFilename());

                        if (currentItem.getType().equals(YTD.JSON_DATA_TYPE_V)) {

                            // handle click on a **VIDEO** file entry
                            builder.setItems(R.array.dashboard_click_entries,
                                    new DialogInterface.OnClickListener() {
                                        public void onClick(DialogInterface dialog, int which) {

                                            switch (which) {
                                            case 0: // open
                                                BugSenseHandler.leaveBreadcrumb("video_open");
                                                Intent openIntent = new Intent(Intent.ACTION_VIEW);
                                                openIntent.setDataAndType(Uri.fromFile(in), "video/*");
                                                startActivity(Intent.createChooser(openIntent,
                                                        getString(R.string.open_chooser_title)));
                                                break;
                                            case 1: // extract audio only
                                                if (!isFfmpegRunning) {
                                                    BugSenseHandler.leaveBreadcrumb("video_ffmpeg_extract");
                                                    if (audioIsSupported) {
                                                        if (ffmpegEnabled) {
                                                            AlertDialog.Builder builder0 = new AlertDialog.Builder(
                                                                    boxThemeContextWrapper);
                                                            LayoutInflater inflater0 = getLayoutInflater();
                                                            final View view0 = inflater0
                                                                    .inflate(R.layout.dialog_audio_extr_only, null);

                                                            String type = null;
                                                            if (currentItem.getAudioExt().equals(".aac"))
                                                                type = aac;
                                                            if (currentItem.getAudioExt().equals(".ogg"))
                                                                type = ogg;
                                                            if (currentItem.getAudioExt().equals(".mp3"))
                                                                type = mp3;
                                                            //if (currentItem.getAudioExt().equals(".auto")) type = aac_mp3;

                                                            TextView info = (TextView) view0
                                                                    .findViewById(R.id.audio_extr_info);
                                                            info.setText(getString(R.string.audio_extr_info)
                                                                    + "\n\n" + type);

                                                            builder0.setView(view0).setPositiveButton("OK",
                                                                    new DialogInterface.OnClickListener() {
                                                                        @Override
                                                                        public void onClick(DialogInterface dialog,
                                                                                int id) {

                                                                            CheckBox cb0 = (CheckBox) view0
                                                                                    .findViewById(R.id.rem_video_0);
                                                                            removeVideo = cb0.isChecked();

                                                                            Utils.logger("v",
                                                                                    "Launching FFmpeg on: " + in
                                                                                            + "\n-> mode: extraction only"
                                                                                            + "\n-> remove video: "
                                                                                            + removeVideo,
                                                                                    DEBUG_TAG);

                                                                            ffmpegJob(in, null, null);
                                                                        }
                                                                    }).setNegativeButton(R.string.dialogs_negative,
                                                                            new DialogInterface.OnClickListener() {
                                                                                public void onClick(
                                                                                        DialogInterface dialog,
                                                                                        int id) {
                                                                                    // cancel
                                                                                }
                                                                            });

                                                            secureShowDialog(builder0);
                                                        } else {
                                                            notifyFfmpegNotInstalled();
                                                        }
                                                    } else {
                                                        notifyOpsNotSupported();
                                                    }
                                                } else {
                                                    notifyFfmpegIsAlreadyRunning();
                                                }

                                                break;
                                            case 2: // extract audio and convert to mp3
                                                if (!isFfmpegRunning) {
                                                    BugSenseHandler.leaveBreadcrumb("video_ffmpeg_mp3");
                                                    if (audioIsSupported) {
                                                        if (ffmpegEnabled) {
                                                            AlertDialog.Builder builder1 = new AlertDialog.Builder(
                                                                    boxThemeContextWrapper);
                                                            LayoutInflater inflater1 = getLayoutInflater();

                                                            final View view1 = inflater1.inflate(
                                                                    R.layout.dialog_audio_extr_mp3_conv, null);

                                                            builder1.setView(view1).setPositiveButton("OK",
                                                                    new DialogInterface.OnClickListener() {
                                                                        @Override
                                                                        public void onClick(DialogInterface dialog,
                                                                                int id) {

                                                                            final Spinner sp = (Spinner) view1
                                                                                    .findViewById(R.id.mp3_spinner);
                                                                            String[] bitrateData = retrieveBitrateValueFromSpinner(
                                                                                    sp);

                                                                            CheckBox cb1 = (CheckBox) view1
                                                                                    .findViewById(R.id.rem_video_1);
                                                                            removeVideo = cb1.isChecked();

                                                                            Utils.logger("v",
                                                                                    "Launching FFmpeg on: " + in
                                                                                            + "\n-> mode: conversion to mp3 from video file"
                                                                                            + "\n-> remove video: "
                                                                                            + removeVideo,
                                                                                    DEBUG_TAG);

                                                                            ffmpegJob(in, bitrateData[0],
                                                                                    bitrateData[1]);
                                                                        }
                                                                    }).setNegativeButton(R.string.dialogs_negative,
                                                                            new DialogInterface.OnClickListener() {
                                                                                public void onClick(
                                                                                        DialogInterface dialog,
                                                                                        int id) {
                                                                                    //
                                                                                }
                                                                            });

                                                            secureShowDialog(builder1);
                                                        } else {
                                                            notifyFfmpegNotInstalled();
                                                        }
                                                    } else {
                                                        notifyOpsNotSupported();
                                                    }
                                                } else {
                                                    notifyFfmpegIsAlreadyRunning();
                                                }
                                            }
                                        }
                                    });

                            secureShowDialog(builder);

                        } else if (currentItem.getType().equals(YTD.JSON_DATA_TYPE_A_E)
                                || currentItem.getType().equals(YTD.JSON_DATA_TYPE_A_M)) {

                            // handle click on a **AUDIO** file entry
                            builder.setItems(R.array.dashboard_click_entries_audio,
                                    new DialogInterface.OnClickListener() {
                                        public void onClick(DialogInterface dialog, int which) {

                                            switch (which) {
                                            case 0: // open
                                                BugSenseHandler.leaveBreadcrumb("audio_open");
                                                Intent openIntent = new Intent(Intent.ACTION_VIEW);
                                                openIntent.setDataAndType(Uri.fromFile(in), "audio/*");
                                                startActivity(Intent.createChooser(openIntent,
                                                        getString(R.string.open_chooser_title)));
                                                break;
                                            case 1: // convert to mp3
                                                if (!isFfmpegRunning) {
                                                    if (ffmpegEnabled) {
                                                        BugSenseHandler.leaveBreadcrumb("audio_ffmpeg_mp3");
                                                        AlertDialog.Builder builder0 = new AlertDialog.Builder(
                                                                boxThemeContextWrapper);
                                                        LayoutInflater inflater0 = getLayoutInflater();
                                                        final View view2 = inflater0
                                                                .inflate(R.layout.dialog_audio_mp3_conv, null);

                                                        builder0.setView(view2).setPositiveButton("OK",
                                                                new DialogInterface.OnClickListener() {
                                                                    @Override
                                                                    public void onClick(DialogInterface dialog,
                                                                            int id) {

                                                                        final Spinner sp = (Spinner) view2
                                                                                .findViewById(R.id.mp3_spinner_a);
                                                                        String[] bitrateData = retrieveBitrateValueFromSpinner(
                                                                                sp);

                                                                        CheckBox cb2 = (CheckBox) view2
                                                                                .findViewById(
                                                                                        R.id.rem_original_audio);
                                                                        removeAudio = cb2.isChecked();

                                                                        Utils.logger("v", "Launching FFmpeg on: "
                                                                                + in
                                                                                + "\n-> mode: conversion to mp3 from audio file"
                                                                                + "\n-> remove audio: "
                                                                                + removeAudio, DEBUG_TAG);

                                                                        ffmpegJob(in, bitrateData[0],
                                                                                bitrateData[1]);
                                                                    }
                                                                }).setNegativeButton(R.string.dialogs_negative,
                                                                        new DialogInterface.OnClickListener() {
                                                                            public void onClick(
                                                                                    DialogInterface dialog,
                                                                                    int id) {
                                                                                //
                                                                            }
                                                                        });

                                                        secureShowDialog(builder0);
                                                    } else {
                                                        notifyFfmpegNotInstalled();
                                                    }
                                                } else {
                                                    notifyFfmpegIsAlreadyRunning();
                                                }
                                            }
                                        }
                                    });

                            secureShowDialog(builder);
                        }
                    } /*else if (currentItem.getStatus().equals(getString(R.string.json_status_imported))) {
                       Utils.logger("v", "IMPORTED video clicked", DEBUG_TAG);
                           
                       // handle click on an  **IMPORTED VIDEO** entry
                       builder.setItems(R.array.dashboard_click_entries_imported, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                          
                       final File in = new File (currentItem.getPath(), currentItem.getFilename());
                           
                       switch (which) {
                       case 0: // open
                          Intent openIntent = new Intent(Intent.ACTION_VIEW);
                          openIntent.setDataAndType(Uri.fromFile(in), "video/*");
                          startActivity(Intent.createChooser(openIntent, getString(R.string.open_chooser_title)));
                          break;
                       }
                        }
                       });
                          
                       secureShowDialog(builder);
                      }*/
                }
            }
        });

        lv.setLongClickable(true);
        lv.setOnItemLongClickListener(new OnItemLongClickListener() {

            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {

                if (!isAnyAsyncInProgress) {
                    currentItem = da.getItem(position); // in order to refer to the filtered item

                    int COPY = 0;
                    int MOVE = 1;
                    int RENAME = 2;
                    int REDOWNLOAD = 3;
                    int REMOVE = 4;
                    int DELETE = 5;
                    int PAUSERESUME = 6;

                    int[] disabledItems = null;

                    if (currentItem.getStatus().equals(getString(R.string.json_status_in_progress))
                            || currentItem.getStatus().equals(getString(R.string.json_status_paused))) {
                        // show: DELETE and  PAUSERESUME
                        disabledItems = new int[] { COPY, MOVE, RENAME, REDOWNLOAD, REMOVE };
                    } else if (currentItem.getStatus().equals(getString(R.string.json_status_failed))) {
                        // check if the item has a real YouTube ID, otherwise it's an imported video.
                        if (currentItem.getYtId().length() == 11) {
                            // show: REMOVE and REDOWNLOAD
                            disabledItems = new int[] { COPY, MOVE, RENAME, DELETE, PAUSERESUME };
                        } else {
                            // show: REMOVE only
                            disabledItems = new int[] { COPY, MOVE, RENAME, REDOWNLOAD, DELETE, PAUSERESUME };
                        }

                    } else if (currentItem.getStatus().equals(getString(R.string.json_status_imported)) ||
                    //case for audio entries _completed but from _imported
                    (currentItem.getStatus().equals(getString(R.string.json_status_completed))
                            && !(currentItem.getYtId().length() == 11))) {
                        // show: COPY, MOVE, RENAME, REMOVE and DELETE
                        disabledItems = new int[] { REDOWNLOAD, PAUSERESUME };
                    } else if (currentItem.getStatus().equals(getString(R.string.json_status_completed))) {
                        // show: all items except PAUSERESUME
                        disabledItems = new int[] { PAUSERESUME };
                    }

                    AlertDialog.Builder builder = new AlertDialog.Builder(boxThemeContextWrapper);
                    builder.setTitle(currentItem.getFilename());

                    final ArrayAdapter<CharSequence> cla = DashboardLongClickAdapter.createFromResource(
                            boxThemeContextWrapper, R.array.dashboard_long_click_entries,
                            android.R.layout.simple_list_item_1, disabledItems);

                    builder.setAdapter(cla, new DialogInterface.OnClickListener() {

                        public void onClick(DialogInterface dialog, int which) {
                            switch (which) {
                            case 0:
                                BugSenseHandler.leaveBreadcrumb("copy");
                                copy(currentItem);
                                break;
                            case 1:
                                BugSenseHandler.leaveBreadcrumb("move");
                                move(currentItem);
                                break;
                            case 2:
                                BugSenseHandler.leaveBreadcrumb("rename");
                                rename(currentItem);
                                break;
                            case 3:
                                if (currentItem.getStatus().equals(getString(R.string.json_status_failed))) {
                                    BugSenseHandler.leaveBreadcrumb("reDownload_RESTART");
                                    reDownload(currentItem, "RESTART");
                                } else {
                                    BugSenseHandler.leaveBreadcrumb("reDownload");
                                    reDownload(currentItem, "-");
                                }
                                break;
                            case 4:
                                BugSenseHandler.leaveBreadcrumb("removeFromDashboard");
                                removeFromDashboard(currentItem);
                                break;
                            case 5:
                                BugSenseHandler.leaveBreadcrumb("delete");
                                delete(currentItem);
                                break;
                            case 6:
                                BugSenseHandler.leaveBreadcrumb("pauseresume");
                                pauseresume(currentItem);
                            }

                        }
                    });

                    secureShowDialog(builder);
                }

                return true;
            }
        });
    }

    private void notifyFfmpegIsAlreadyRunning() {
        Utils.logger("d", "notifyFfmpegIsAlreadyRunning()", DEBUG_TAG);
        Toast.makeText(sDashboard, "FFmpeg is already running", Toast.LENGTH_LONG).show();
    }

    private void notifyOpsNotSupported() {
        PopUps.showPopUp(getString(R.string.information), getString(R.string.unsupported_operation), "alert",
                sDashboard);
        Utils.logger("d", "notifyOpsNotSupported()", DEBUG_TAG);
    }

    private void toastOpsNotExecuted() {
        Toast.makeText(sDashboard,
                getString(R.string.long_press_warning_title) + "\n- "
                        + getString(R.string.notification_downloading_pt1) + " ("
                        + getString(R.string.json_status_paused) + "/" + getString(R.string.json_status_in_progress)
                        + " )" + "\n- " + getString(R.string.empty_dashboard),
                Toast.LENGTH_LONG).show();
        Utils.logger("d", "toastOpsNotExecuted()", DEBUG_TAG);
    }

    private void copy(DashboardListItem currentItem) {
        Intent intent = new Intent(DashboardActivity.this, FileChooserActivity.class);
        if (intent != null) {
            intent.putExtra(FileChooserActivity._Rootpath,
                    (Parcelable) new LocalFile(Environment.getExternalStorageDirectory()));
            intent.putExtra(FileChooserActivity._FilterMode, IFileProvider.FilterMode.DirectoriesOnly);
            intent.putExtra("path", currentItem.getPath());
            intent.putExtra("name", currentItem.getFilename());
            startActivityForResult(intent, 1);
        }
    }

    private void move(DashboardListItem currentItem) {
        Intent intent = new Intent(DashboardActivity.this, FileChooserActivity.class);
        if (intent != null) {
            intent.putExtra(FileChooserActivity._Rootpath,
                    (Parcelable) new LocalFile(Environment.getExternalStorageDirectory()));
            intent.putExtra(FileChooserActivity._FilterMode, IFileProvider.FilterMode.DirectoriesOnly);
            intent.putExtra("path", currentItem.getPath());
            intent.putExtra("name", currentItem.getFilename());
            startActivityForResult(intent, 2);
        }
    }

    private void rename(final DashboardListItem currentItem) {
        AlertDialog.Builder adb = new AlertDialog.Builder(boxThemeContextWrapper);
        LayoutInflater adbInflater = LayoutInflater.from(DashboardActivity.this);
        View inputFilename = adbInflater.inflate(R.layout.dialog_input_filename, null);
        userFilename = (TextView) inputFilename.findViewById(R.id.input_filename);
        userFilename.setText(currentItem.getFilename());
        adb.setView(inputFilename);
        adb.setTitle(getString(R.string.rename_dialog_title));
        //adb.setMessage(getString(R.string.rename_dialog_msg));

        adb.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                String input = userFilename.getText().toString();
                File in = new File(currentItem.getPath(), currentItem.getFilename());
                File renamed = new File(currentItem.getPath(), input);
                if (in.renameTo(renamed)) {
                    // set new name to the list item
                    currentItem.setFilename(input);

                    // update the JSON file entry
                    Json.addEntryToJsonFile(DashboardActivity.this, currentItem.getId(), currentItem.getType(),
                            currentItem.getYtId(), currentItem.getPos(), currentItem.getStatus(),
                            currentItem.getPath(), input, Utils.getFileNameWithoutExt(input),
                            currentItem.getAudioExt(), currentItem.getSize(), false);

                    // remove references for the old file
                    String mediaUriString = Utils.getContentUriFromFilePath(in.getAbsolutePath(),
                            getContentResolver());
                    //Utils.logger("d", "mediaString: " + mediaUriString, DEBUG_TAG);
                    removeFromMediaStore(in, mediaUriString);

                    // scan the new file
                    Utils.scanMedia(DashboardActivity.this, new String[] { renamed.getAbsolutePath() },
                            new String[] { "video/*" });

                    // refresh the dashboard
                    refreshlist(DashboardActivity.this);

                    Utils.logger("d", "'" + in.getName() + "' renamed to '" + input + "'", DEBUG_TAG);
                } else {
                    Log.e(DEBUG_TAG, "'" + in.getName() + "' NOT renamed");
                }

                // hide keyboard
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(userFilename.getWindowToken(), 0);
            }
        });

        adb.setNegativeButton(getString(R.string.dialogs_negative), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                // cancel
            }
        });

        secureShowDialog(adb);
    }

    public void removeFromDashboard(final DashboardListItem currentItem) {
        AlertDialog.Builder rem = new AlertDialog.Builder(boxThemeContextWrapper);
        //rem.setTitle(getString(R.string.attention));
        rem.setTitle(currentItem.getFilename());
        rem.setMessage(getString(R.string.remove_video_confirm));
        rem.setIcon(android.R.drawable.ic_dialog_alert);
        rem.setPositiveButton("OK", new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                Json.removeEntryFromJsonFile(DashboardActivity.this, currentItem.getId());
                refreshlist(DashboardActivity.this);

                YTD.videoinfo.edit().remove(currentItem.getId() + "_link").apply();
                //YTD.videoinfo.edit().remove(currentItem.getId() + "_position").apply();
            }
        });

        rem.setNegativeButton(R.string.dialogs_negative, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                // cancel
            }
        });

        secureShowDialog(rem);
    }

    public void delete(final DashboardListItem currentItem) {
        AlertDialog.Builder del = new AlertDialog.Builder(boxThemeContextWrapper);
        //del.setTitle(getString(R.string.attention));
        del.setTitle(currentItem.getFilename());
        del.setMessage(getString(R.string.delete_video_confirm));
        del.setIcon(android.R.drawable.ic_dialog_alert);
        del.setPositiveButton("OK", new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                final File fileToDel = new File(currentItem.getPath(), currentItem.getFilename());
                new AsyncDelete().execute(fileToDel);
            }
        });

        del.setNegativeButton(R.string.dialogs_negative, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                // cancel
            }
        });

        secureShowDialog(del);
    }

    private void pauseresume(final DashboardListItem currentItem) {

        final String itemID = currentItem.getId();
        long itemIDlong = Long.parseLong(itemID);

        Utils.logger("d", "pauseresume on id " + itemID, DEBUG_TAG);

        if (currentItem.getStatus().equals(getString(R.string.json_status_in_progress))) {
            BugSenseHandler.leaveBreadcrumb("...pausing");

            try {
                if (Maps.dtMap.containsKey(itemIDlong)) {
                    DownloadTask dt = Maps.dtMap.get(itemIDlong);
                    dt.cancel();
                } else {
                    if (Maps.dtMap.size() > 0) {
                        // cancel (pause) every dt found
                        Utils.logger("w", "pauseresume: id not found into 'dtMap'; canceling all tasks", DEBUG_TAG);
                        for (Iterator<DownloadTask> iterator = Maps.dtMap.values().iterator(); iterator
                                .hasNext();) {
                            DownloadTask dt = (DownloadTask) iterator.next();
                            dt.cancel();
                        }
                    }
                }
            } catch (NullPointerException e) {
                Log.e(DEBUG_TAG, "dt.cancel() @ pauseresume: " + e.getMessage());
                BugSenseHandler.sendExceptionMessage(DEBUG_TAG + "-> dt.cancel() @ pauseresume: ", e.getMessage(),
                        e);
            }

            YTD.removeIdUpdateNotification(itemIDlong);

            // update the JSON file entry
            Json.addEntryToJsonFile(DashboardActivity.this, itemID, currentItem.getType(), currentItem.getYtId(),
                    currentItem.getPos(), YTD.JSON_DATA_STATUS_PAUSED, currentItem.getPath(),
                    currentItem.getFilename(), currentItem.getBasename(), currentItem.getAudioExt(),
                    currentItem.getSize(), false);
        }

        if (currentItem.getStatus().equals(getString(R.string.json_status_paused))) {
            BugSenseHandler.leaveBreadcrumb("...resuming");
            String link = YTD.videoinfo.getString(String.valueOf(itemID) + "_link", null);

            if (link != null) {
                DownloadTaskListener dtl = new DownloadTaskListener() {

                    @Override
                    public void preDownload(DownloadTask task) {
                        long ID = task.getDownloadId();
                        Utils.logger("d", "__preDownload on ID: " + ID, DEBUG_TAG);

                        Maps.mNetworkSpeedMap.put(ID, (long) 0);

                        Json.addEntryToJsonFile(sDashboard, String.valueOf(ID), currentItem.getType(),
                                currentItem.getYtId(), currentItem.getPos(), YTD.JSON_DATA_STATUS_IN_PROGRESS,
                                currentItem.getPath(), currentItem.getFilename(), currentItem.getBasename(),
                                currentItem.getAudioExt(), currentItem.getSize(), false);

                        YTD.sequence.add(ID);

                        YTD.NotificationHelper(sDashboard);
                    }

                    @Override
                    public void updateProcess(DownloadTask task) {
                        /*YTD.downloadPercentMap = task.getDownloadPercentMap();
                        YTD.downloadTotalSizeMap = task.getTotalSizeMap();
                        YTD.downloadPartialSizeMap = task.getDownloadSizeMap();*/
                    }

                    @Override
                    public void finishDownload(DownloadTask task) {
                        long ID = task.getDownloadId();
                        Utils.logger("d", "__finishDownload on ID: " + ID, DEBUG_TAG);

                        Utils.scanMedia(getApplicationContext(),
                                new String[] { currentItem.getPath() + File.separator + currentItem.getFilename() },
                                new String[] { "video/*" });

                        long downloadTotalSize = Maps.mTotalSizeMap.get(ID);
                        String size = String.valueOf(Utils.MakeSizeHumanReadable(downloadTotalSize, false));

                        Json.addEntryToJsonFile(sDashboard, String.valueOf(ID), currentItem.getType(),
                                currentItem.getYtId(), currentItem.getPos(), YTD.JSON_DATA_STATUS_COMPLETED,
                                currentItem.getPath(), currentItem.getFilename(), currentItem.getBasename(),
                                currentItem.getAudioExt(), size, false);

                        if (DashboardActivity.isDashboardRunning)
                            DashboardActivity.refreshlist(sDashboard);

                        YTD.removeIdUpdateNotification(ID);

                        YTD.videoinfo.edit().remove(ID + "_link").apply();
                        //YTD.videoinfo.edit().remove(ID + "_position").apply();

                        Maps.removeFromAllMaps(ID);
                    }

                    @Override
                    public void errorDownload(DownloadTask task, Throwable error) {
                        String nameOfVideo = task.getDownloadedFileName();
                        long ID = task.getDownloadId();

                        Utils.logger("w", "__errorDownload on ID: " + ID, DEBUG_TAG);

                        if (error != null && error instanceof InvalidYoutubeLinkException) {
                            Toast.makeText(sDashboard, nameOfVideo + ": " + getString(R.string.downloading) + "\n"
                                    + getString(R.string.wait), Toast.LENGTH_LONG).show();

                            Json.addEntryToJsonFile(sDashboard, String.valueOf(ID), YTD.JSON_DATA_TYPE_V,
                                    currentItem.getYtId(), currentItem.getPos(), YTD.JSON_DATA_STATUS_PAUSED,
                                    currentItem.getPath(), nameOfVideo, currentItem.getBasename(),
                                    currentItem.getAudioExt(), currentItem.getSize(), false);

                            reDownload(currentItem, "AUTO");
                        } else {
                            Toast.makeText(sDashboard, nameOfVideo + ": " + getString(R.string.download_failed),
                                    Toast.LENGTH_LONG).show();

                            Json.addEntryToJsonFile(sDashboard, String.valueOf(ID), YTD.JSON_DATA_TYPE_V,
                                    currentItem.getYtId(), currentItem.getPos(), YTD.JSON_DATA_STATUS_PAUSED,
                                    currentItem.getPath(), nameOfVideo, currentItem.getBasename(),
                                    currentItem.getAudioExt(), currentItem.getSize(), false);

                            if (DashboardActivity.isDashboardRunning)
                                DashboardActivity.refreshlist(sDashboard);

                            YTD.removeIdUpdateNotification(ID);
                        }
                    }
                };

                //TODO
                try {
                    DownloadTask dt = new DownloadTask(this, itemIDlong, link, currentItem.getFilename(),
                            currentItem.getPath(), dtl, true);
                    Maps.dtMap.put(itemIDlong, dt);
                    dt.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
                } catch (MalformedURLException e) {
                    Log.e(DEBUG_TAG, "unable to start Download Manager -> " + e.getMessage());
                }
            } else {
                //notifyOpsNotSupported();
                reDownload(currentItem, "AUTO");
            }
        }
        refreshlist(sDashboard);
    }

    private void reDownload(DashboardListItem currentItem, String category) {
        String ytLink = "http://www.youtube.com/watch?v=" + currentItem.getYtId();
        Intent rdIntent = new Intent(this, ShareActivity.class);
        rdIntent.setData(Uri.parse(ytLink));
        rdIntent.addCategory(category);
        rdIntent.putExtra("id", currentItem.getId());
        rdIntent.putExtra("position", currentItem.getPos());
        rdIntent.putExtra("filename", currentItem.getFilename());
        rdIntent.setAction(Intent.ACTION_VIEW);
        //rdIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(rdIntent);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_dashboard, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);

        String previousJson = Json.readJsonDashboardFile(sDashboard);
        boolean smtInProgressOrPaused = (previousJson.contains(YTD.JSON_DATA_STATUS_IN_PROGRESS)
                || previousJson.contains(YTD.JSON_DATA_STATUS_PAUSED));

        switch (item.getItemId()) {
        case R.id.menu_search:
            BugSenseHandler.leaveBreadcrumb("ShareActivity_menu_search");
            if (!isSearchBarVisible) {
                spawnSearchBar();
            } else {
                hideSearchBar();
            }
            return true;
        case R.id.menu_backup:
            BugSenseHandler.leaveBreadcrumb("ShareActivity_menu_backup");
            if (YTD.JSON_FILE.exists() && !previousJson.equals("{}\n") && !smtInProgressOrPaused) {
                boolean backupCheckboxEnabled = YTD.settings.getBoolean("dashboard_backup_info", true);
                if (backupCheckboxEnabled == true) {

                    AlertDialog.Builder adb = new AlertDialog.Builder(boxThemeContextWrapper);

                    LayoutInflater adbInflater = LayoutInflater.from(DashboardActivity.this);
                    View showAgainView = adbInflater.inflate(R.layout.dialog_show_again_checkbox, null);
                    final CheckBox showAgain = (CheckBox) showAgainView.findViewById(R.id.showAgain2);
                    showAgain.setChecked(true);
                    adb.setView(showAgainView);

                    adb.setTitle(getString(R.string.information));
                    adb.setMessage(getString(R.string.menu_backup_info));
                    adb.setIcon(android.R.drawable.ic_dialog_info);

                    adb.setPositiveButton("OK", new DialogInterface.OnClickListener() {

                        public void onClick(DialogInterface dialog, int which) {
                            if (!showAgain.isChecked()) {
                                YTD.settings.edit().putBoolean("dashboard_backup_info", false).apply();
                                Utils.logger("d", "dashboard backup info checkbox disabled", DEBUG_TAG);
                            }
                            launchFcForBackup();
                        }
                    });

                    adb.setNegativeButton(R.string.dialogs_negative, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            // cancel
                        }
                    });

                    secureShowDialog(adb);
                } else {
                    launchFcForBackup();
                }
            } else {
                toastOpsNotExecuted();
            }
            return true;
        case R.id.menu_restore:
            BugSenseHandler.leaveBreadcrumb("ShareActivity_menu_restore");
            if (!smtInProgressOrPaused) {
                boolean restoreCheckboxEnabled = YTD.settings.getBoolean("dashboard_restore_info", true);
                if (restoreCheckboxEnabled == true) {

                    AlertDialog.Builder adb = new AlertDialog.Builder(boxThemeContextWrapper);

                    LayoutInflater adbInflater = LayoutInflater.from(DashboardActivity.this);
                    View showAgainView = adbInflater.inflate(R.layout.dialog_show_again_checkbox, null);
                    final CheckBox showAgain = (CheckBox) showAgainView.findViewById(R.id.showAgain2);
                    showAgain.setChecked(true);
                    adb.setView(showAgainView);

                    adb.setTitle(getString(R.string.information));
                    adb.setMessage(getString(R.string.menu_restore_info) + ".\n"
                            + getString(R.string.menu_restore_info_msg));
                    adb.setIcon(android.R.drawable.ic_dialog_info);

                    adb.setPositiveButton("OK", new DialogInterface.OnClickListener() {

                        public void onClick(DialogInterface dialog, int which) {
                            if (!showAgain.isChecked()) {
                                YTD.settings.edit().putBoolean("dashboard_restore_info", false).apply();
                                Utils.logger("d", "dashboard restore info checkbox disabled", DEBUG_TAG);
                            }
                            launchFcForRestore();
                        }
                    });

                    adb.setNegativeButton(R.string.dialogs_negative, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            // cancel
                        }
                    });

                    secureShowDialog(adb);
                } else {
                    launchFcForRestore();
                }
            } else {
                toastOpsNotExecuted();
            }
            return true;
        case R.id.menu_import:
            BugSenseHandler.leaveBreadcrumb("ShareActivity_menu_import");
            boolean importCheckboxEnabled1 = YTD.settings.getBoolean("dashboard_import_info", true);
            if (importCheckboxEnabled1 == true) {

                AlertDialog.Builder adb = new AlertDialog.Builder(boxThemeContextWrapper);

                LayoutInflater adbInflater = LayoutInflater.from(DashboardActivity.this);
                View showAgainView = adbInflater.inflate(R.layout.dialog_show_again_checkbox, null);
                final CheckBox showAgain = (CheckBox) showAgainView.findViewById(R.id.showAgain2);
                showAgain.setChecked(true);
                adb.setView(showAgainView);

                adb.setTitle(getString(R.string.information));
                adb.setMessage(getString(R.string.menu_import_info));
                adb.setIcon(android.R.drawable.ic_dialog_info);

                adb.setPositiveButton("OK", new DialogInterface.OnClickListener() {

                    public void onClick(DialogInterface dialog, int which) {
                        if (!showAgain.isChecked()) {
                            YTD.settings.edit().putBoolean("dashboard_import_info", false).apply();
                            Utils.logger("d", "dashboard import info checkbox disabled", DEBUG_TAG);
                        }
                        launchFcForImport();
                    }
                });

                adb.setNegativeButton(R.string.dialogs_negative, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        // cancel
                    }
                });

                secureShowDialog(adb);
            } else {
                launchFcForImport();
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    private void launchFcForBackup() {
        Intent intent3 = new Intent(DashboardActivity.this, FileChooserActivity.class);
        if (intent3 != null) {
            intent3.putExtra(FileChooserActivity._Rootpath,
                    (Parcelable) new LocalFile(Environment.getExternalStorageDirectory()));
            intent3.putExtra(FileChooserActivity._FilterMode, IFileProvider.FilterMode.DirectoriesOnly);
            startActivityForResult(intent3, 3);
        }
    }

    private void launchFcForRestore() {
        Intent intent4 = new Intent(DashboardActivity.this, FileChooserActivity.class);
        if (intent4 != null) {
            intent4.putExtra(FileChooserActivity._Rootpath,
                    (Parcelable) new LocalFile(Environment.getExternalStorageDirectory()));
            intent4.putExtra(FileChooserActivity._FilterMode, IFileProvider.FilterMode.FilesOnly);
            startActivityForResult(intent4, 4);
        }
    }

    private void launchFcForImport() {
        Intent intent5 = new Intent(DashboardActivity.this, FileChooserActivity.class);
        if (intent5 != null) {
            intent5.putExtra(FileChooserActivity._Rootpath,
                    (Parcelable) new LocalFile(Environment.getExternalStorageDirectory()));
            intent5.putExtra(FileChooserActivity._FilterMode, IFileProvider.FilterMode.FilesOnly);
            startActivityForResult(intent5, 5);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        Utils.logger("v", "_onResume", DEBUG_TAG);
        isDashboardRunning = true;

        /*
         * Timer() adapted from Stack Overflow:
         * http://stackoverflow.com/questions/3701106/periodically-refresh-reload-activity
         * 
         * Q: http://stackoverflow.com/users/446413/raffe
         * A: http://stackoverflow.com/users/244296/cristian
         */
        autoUpdate = new Timer();
        autoUpdate.schedule(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    public void run() {

                        int inProgressIndex = 0;

                        for (int i = 0; i < statusEntries.size(); i++) {
                            if (statusEntries.get(i).equals(YTD.JSON_DATA_STATUS_IN_PROGRESS)) {
                                inProgressIndex++;
                            }
                        }

                        if (inProgressIndex > 0) {
                            //Utils.logger("v", "refreshing...", DEBUG_TAG);
                            refreshlist(sDashboard);
                        }
                    }
                });
            }
        }, 500, 500);
    }

    @Override
    public void onPause() {
        super.onPause();
        Utils.logger("v", "_onPause", DEBUG_TAG);
        isDashboardRunning = false;

        autoUpdate.cancel();
    }

    public static void showEmptyListInfo(Activity activity) {
        TextView info = (TextView) activity.findViewById(R.id.dashboard_activity_info);
        info.setVisibility(View.VISIBLE);
        //Utils.logger("v", "__dashboard is empty__", DEBUG_TAG);
    }

    private class AsyncDelete extends AsyncTask<File, Void, Boolean> {

        File fileToDelete;

        protected void onPreExecute() {
            isAnyAsyncInProgress = true;
        }

        protected Boolean doInBackground(File... fileToDel) {
            fileToDelete = fileToDel[0];
            return doDelete(currentItem, fileToDel[0], true);
        }

        @Override
        protected void onPostExecute(Boolean success) {
            if (success) {
                notifyDeletionOk(currentItem, fileToDelete);
            } else {
                notifyDeletionUnsuccessful(currentItem, fileToDelete);
            }
            isAnyAsyncInProgress = false;
        }
    }

    private boolean doDelete(final DashboardListItem currentItem, File fileToDel, boolean removeFromJsonAlso) {
        Utils.logger("v", "----------> BEGIN delete", DEBUG_TAG);
        boolean isResultOk = false;
        long id = Long.parseLong(currentItem.getId());

        if (currentItem.getStatus().equals(getString(R.string.json_status_in_progress))) {
            // stop download, remove temp file and update notification
            try {
                if (Maps.dtMap.containsKey(id)) {
                    DownloadTask dt = Maps.dtMap.get(id);
                    dt.cancel();
                } else {
                    if (Maps.dtMap.size() > 0) {
                        // cancel (pause) every dt found
                        Utils.logger("w", "doDelete: id not found into 'dtMap'; canceling all tasks", DEBUG_TAG);
                        for (Iterator<DownloadTask> iterator = Maps.dtMap.values().iterator(); iterator
                                .hasNext();) {
                            DownloadTask dt = (DownloadTask) iterator.next();
                            dt.cancel();
                        }
                    }
                }

                isResultOk = removeTemp(fileToDel, id);
            } catch (NullPointerException e) {
                Log.e(DEBUG_TAG, "dt.cancel(): " + e.getMessage());
                BugSenseHandler.sendExceptionMessage(DEBUG_TAG + "-> dt.cancel() @ doDelete: ", e.getMessage(), e);
            }
        } else if (currentItem.getStatus().equals(getString(R.string.json_status_paused))) {
            isResultOk = removeTemp(fileToDel, id);
        } else {
            // remove file and library reference
            isResultOk = removeCompleted(fileToDel);
        }

        if (removeFromJsonAlso/* && isResultOk*/) {
            // remove entry from JSON and reload Dashboard
            Json.removeEntryFromJsonFile(DashboardActivity.this, currentItem.getId());
        }

        refreshlist(DashboardActivity.this);
        Utils.logger("v", "----------> END delete", DEBUG_TAG);

        return isResultOk;
    }

    private boolean removeTemp(File fileToDel, long id) {
        // update notification
        YTD.removeIdUpdateNotification(id);

        //remove YouTube link from prefs
        YTD.videoinfo.edit().remove(String.valueOf(id) + "_link").apply();
        //YTD.videoinfo.edit().remove(String.valueOf(id) + "_position").apply();

        // delete temp file
        File temp = new File(fileToDel.getAbsolutePath() + DownloadTask.TEMP_SUFFIX);
        if (temp.exists()) {
            return (temp.delete()) ? true : false;
        } else {
            return true;
        }
    }

    public boolean removeCompleted(File fileToDel) {
        // remove file
        if (fileToDel.delete()) {
            // remove library reference
            String mediaUriString;
            try {
                mediaUriString = Utils.getContentUriFromFilePath(fileToDel.getAbsolutePath(), getContentResolver());
                removeFromMediaStore(fileToDel, mediaUriString);
            } catch (NullPointerException e) {
                Utils.logger("w", fileToDel.getName() + " UriString NOT found", DEBUG_TAG);
            }
            return true;
        } else {
            return false;
        }
    }

    private void removeFromMediaStore(File fileToDel, String mediaUriString) {
        if (mediaUriString != null) {
            Uri mediaUri = Uri.parse(mediaUriString);
            // remove media file reference from MediaStore library via ContentResolver
            if (getContentResolver().delete(mediaUri, null, null) > 0) {
                Utils.logger("d", mediaUri.toString() + " Removed", DEBUG_TAG);
            } else {
                Utils.logger("w", mediaUri.toString() + " NOT removed", DEBUG_TAG);
            }
        } else {
            Utils.logger("w", "mediaUriString for " + fileToDel.getName() + " null", DEBUG_TAG);
        }
    }

    public void notifyDeletionUnsuccessful(final DashboardListItem currentItem, File fileToDel) {
        Utils.logger("w", fileToDel.getPath() + " NOT deleted.", DEBUG_TAG);
        Toast.makeText(DashboardActivity.this, getString(R.string.delete_video_failed, currentItem.getFilename()),
                Toast.LENGTH_LONG).show();
    }

    public void notifyDeletionOk(final DashboardListItem currentItem, File fileToDel) {
        Utils.logger("d", fileToDel.getPath() + " successfully deleted.", DEBUG_TAG);
        Toast.makeText(DashboardActivity.this, getString(R.string.delete_video_ok, currentItem.getFilename()),
                Toast.LENGTH_LONG).show();
    }

    public void spawnSearchBar() {
        Utils.logger("d", "showing searchbar...", DEBUG_TAG);

        EditText inputSearch = new EditText(DashboardActivity.this);
        LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        inputSearch.setLayoutParams(layoutParams);

        if (TextUtils.isEmpty(searchText)) {
            inputSearch.setHint(R.string.menu_search);
        } else {
            inputSearch.setText(searchText);
        }

        inputSearch.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
        inputSearch.setSingleLine();
        inputSearch.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
        inputSearch.setId(999);

        LinearLayout layout = (LinearLayout) findViewById(R.id.dashboard);
        layout.addView(inputSearch, 0);
        isSearchBarVisible = true;

        inputSearch.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Utils.logger("d", "Text [" + s + "] - Start [" + start + "] - Before [" + before + "] - Count ["
                        + count + "]", DEBUG_TAG);

                if (count < before)
                    da.resetData();
                da.getFilter().filter(s.toString());
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });
    }

    public void hideSearchBar() {
        Utils.logger("d", "hiding searchbar...", DEBUG_TAG);

        LinearLayout layout = (LinearLayout) findViewById(R.id.dashboard);
        EditText inputSearch = (EditText) findViewById(999);

        // hide keyboard
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(inputSearch.getWindowToken(), 0);

        // store text and remove EditText
        searchText = inputSearch.getEditableText();
        layout.removeView(inputSearch);

        Utils.reload(DashboardActivity.this);

        isSearchBarVisible = false;
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            BugSenseHandler.leaveBreadcrumb("DashboardActivity_filechooser_RESULT_OK");
            @SuppressWarnings("unchecked")
            List<LocalFile> files = (List<LocalFile>) data.getSerializableExtra(FileChooserActivity._Results);

            String path = data.getStringExtra("path");
            String name = data.getStringExtra("name");
            File in = new File(path, name);

            final File chooserSelection = files.get(0);
            //Utils.logger("d", "file-chooser selection: " + chooserFolder.getPath(), DEBUG_TAG);
            //Utils.logger("d", "origin file's folder:   " + currentItem.getPath(), DEBUG_TAG);

            switch (requestCode) {

            case 1: // ------------- > COPY
                File out1 = new File(chooserSelection, name);

                if (chooserSelection.getPath().equals(currentItem.getPath())) {
                    out1 = new File(chooserSelection, "copy_" + currentItem.getFilename());
                }

                if (!out1.exists()) {
                    switch (Utils.pathCheck(chooserSelection)) {
                    case 0:
                        // Path on standard sdcard
                        new AsyncCopy().execute(in, out1);
                        break;
                    case 1:
                        // Path not writable
                        PopUps.showPopUp(getString(R.string.system_warning_title),
                                getString(R.string.system_warning_msg), "alert", DashboardActivity.this);
                        break;
                    case 2:
                        // Path not mounted
                        Toast.makeText(DashboardActivity.this, getString(R.string.sdcard_unmounted_warning),
                                Toast.LENGTH_SHORT).show();
                    }
                } else {
                    PopUps.showPopUp(getString(R.string.long_press_warning_title),
                            getString(R.string.long_press_warning_msg2), "info", DashboardActivity.this);
                }
                break;

            case 2: // ------------- > MOVE
                File out2 = new File(chooserSelection, name);

                if (!chooserSelection.getPath().equals(currentItem.getPath())) {
                    if (!out2.exists()) {
                        switch (Utils.pathCheck(chooserSelection)) {
                        case 0:
                            // Path on standard sdcard
                            new AsyncMove().execute(in, out2);
                            break;
                        case 1:
                            // Path not writable
                            PopUps.showPopUp(getString(R.string.system_warning_title),
                                    getString(R.string.system_warning_msg), "alert", DashboardActivity.this);
                            break;
                        case 2:
                            // Path not mounted
                            Toast.makeText(DashboardActivity.this, getString(R.string.sdcard_unmounted_warning),
                                    Toast.LENGTH_SHORT).show();
                        }
                    } else {
                        PopUps.showPopUp(getString(R.string.long_press_warning_title),
                                getString(R.string.long_press_warning_msg2), "info", DashboardActivity.this);
                    }
                } else {
                    PopUps.showPopUp(getString(R.string.long_press_warning_title),
                            getString(R.string.long_press_warning_msg), "info", DashboardActivity.this);
                }
                break;

            case 3: // ------------- > MENU_BACKUP
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Looper.prepare();

                        String date = new SimpleDateFormat("yyyy-MM-dd'_'HH-mm-ss", Locale.US).format(new Date());
                        final File backup = new File(chooserSelection, date + "_" + YTD.JSON_FILENAME);

                        try {
                            Utils.copyFile(YTD.JSON_FILE, backup);
                            Toast.makeText(sDashboard, getString(R.string.menu_backup_result_ok),
                                    Toast.LENGTH_SHORT).show();
                        } catch (IOException e) {
                            Log.e(DEBUG_TAG, "IOException @ MENU_BACKUP: " + e.getMessage());
                            Toast.makeText(sDashboard, getString(R.string.menu_backup_result_failed),
                                    Toast.LENGTH_LONG).show();
                        }

                        Looper.loop();
                    }
                }).start();
                break;

            case 4: // ------------- > MENU_RESTORE
                AsyncRestore ar = new AsyncRestore();
                ar.execute(chooserSelection);

                break;

            case 5: // ------------- > MENU_IMPORT
                AsyncImport ai = new AsyncImport();
                ai.execute(chooserSelection);
            }
        }
    }

    private class AsyncImport extends AsyncTask<File, Void, String> {

        private ProgressBar progressBar;

        @Override
        protected void onPreExecute() {
            TextView info = (TextView) findViewById(R.id.dashboard_activity_info);
            info.setVisibility(View.GONE);

            ListView list = (ListView) findViewById(R.id.dashboard_list);
            list.setVisibility(View.GONE);

            progressBar = (ProgressBar) findViewById(R.id.dashboard_progressbar);
            progressBar.setVisibility(View.VISIBLE);
        }

        @Override
        protected String doInBackground(File... params) {
            File chooserSelection = params[0];
            String previousJson = Json.readJsonDashboardFile(sDashboard);
            String filename = chooserSelection.getName();

            if (previousJson.contains(filename)) {
                return "e1";
            } else {
                String id = String.valueOf(System.currentTimeMillis());
                String type = YTD.JSON_DATA_TYPE_V;
                String path = chooserSelection.getParent();
                String basename = Utils.getFileNameWithoutExt(filename);
                String size = Utils.MakeSizeHumanReadable((int) chooserSelection.length(), false);

                String ext = Utils.getExtFromFileName(filename).toUpperCase(Locale.ENGLISH);
                String aExt = "unsupported";
                boolean go = false;

                if (ext.equals("WEBM")) {
                    aExt = ".ogg";
                    go = true;
                } else if (ext.equals("MP4") || ext.equals("3GPP")) {
                    aExt = ".aac";
                    go = true;
                } /*else if (ext.equals("FLV")) {
                   aExt = ".auto";
                   go = true;
                  } */else {
                    go = false;
                }

                if (go) {
                    writeThumbToDiskForSelectedFile(chooserSelection, id);

                    Json.addEntryToJsonFile(sDashboard, id, type, id, -1, YTD.JSON_DATA_STATUS_IMPORTED, path,
                            filename, basename, aExt, size, false);

                    return filename;
                } else {
                    return "e2";
                }
            }
        }

        @Override
        protected void onPostExecute(String res) {
            progressBar.setVisibility(View.GONE);
            restartDashboard();

            if (res.equals("e1")) {
                Toast.makeText(DashboardActivity.this, getString(R.string.menu_import_double), Toast.LENGTH_LONG)
                        .show();
            } else if (res.equals("e2")) {
                Toast.makeText(DashboardActivity.this, getString(R.string.unsupported_operation), Toast.LENGTH_LONG)
                        .show();
            } else {
                Toast.makeText(DashboardActivity.this, res + " " + getString(R.string.json_status_imported),
                        Toast.LENGTH_LONG).show();
            }
        }
    }

    private class AsyncRestore extends AsyncTask<File, Void, String> {

        private ProgressBar progressBar;

        @Override
        protected void onPreExecute() {
            TextView info = (TextView) findViewById(R.id.dashboard_activity_info);
            info.setVisibility(View.GONE);

            ListView list = (ListView) findViewById(R.id.dashboard_list);
            list.setVisibility(View.GONE);

            progressBar = (ProgressBar) findViewById(R.id.dashboard_progressbar);
            progressBar.setVisibility(View.VISIBLE);
        }

        @Override
        protected String doInBackground(File... params) {
            File chooserSelection = params[0];
            if (Utils.getExtFromFileName(chooserSelection.getName()).equals("json")) {
                Utils.logger("v", "ext => .json", DEBUG_TAG);
                try {
                    // copy file
                    Utils.copyFile(chooserSelection, YTD.JSON_FILE);

                    // validate the JSON file 
                    String previousJson = Json.readJsonDashboardFile(sDashboard);
                    new JSONObject(previousJson);

                    // empty the Lists
                    idEntries.clear();
                    typeEntries.clear();
                    linkEntries.clear();
                    posEntries.clear();
                    statusEntries.clear();
                    pathEntries.clear();
                    filenameEntries.clear();
                    basenameEntries.clear();
                    audioExtEntries.clear();
                    sizeEntries.clear();
                    partSizeEntries.clear();
                    progressEntries.clear();
                    speedEntries.clear();

                    // refill the lists
                    int entries = parseJson(DashboardActivity.this);

                    Utils.logger("d", "idEntries: " + entries, DEBUG_TAG);

                    for (int i = 0; i < entries; i++) {
                        writeThumbToDiskForSelectedFile(new File(pathEntries.get(i), filenameEntries.get(i)),
                                linkEntries.get(i));
                    }
                    return String.valueOf(entries);
                } catch (JSONException e) {
                    Log.e(DEBUG_TAG, "JSONException @ AsyncRestore: " + e.getMessage());
                    return "e1";
                } catch (IOException e) {
                    Log.e(DEBUG_TAG, "IOException @ AsyncRestore: " + e.getMessage());
                    return "e2";
                } catch (RuntimeException e) {
                    Log.e(DEBUG_TAG, "RuntimeException @ AsyncRestore: " + e.getMessage());
                    return "e4";
                }
            } else {
                return "e3";
            }
        }

        @Override
        protected void onPostExecute(String res) {
            progressBar.setVisibility(View.GONE);
            restartDashboard();

            if (res.equals("e1")) {
                //JSONException e1
                Toast.makeText(sDashboard, sDashboard.getString(R.string.menu_restore_result_failed),
                        Toast.LENGTH_LONG).show();
            } else if (res.equals("e2")) {
                //IOException e2
                Toast.makeText(sDashboard, sDashboard.getString(R.string.menu_restore_result_failed),
                        Toast.LENGTH_LONG).show();
            } else if (res.equals("e3")) {
                //file ext not .json
                Toast.makeText(sDashboard, sDashboard.getString(R.string.invalid_data), Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(sDashboard, getString(R.string.menu_restore_result_ok) + " (" + res + ")",
                        Toast.LENGTH_SHORT).show();
                Utils.logger("d", "Restored " + res + " entries", DEBUG_TAG);
            }
        }
    }

    private void writeThumbToDiskForSelectedFile(final File selectedFile, String pngBasename) {
        Bitmap bmThumbnail = ThumbnailUtils.createVideoThumbnail(selectedFile.getAbsolutePath(),
                Thumbnails.MINI_KIND);
        File bmFile = new File(getDir(YTD.THUMBS_FOLDER, 0), pngBasename + ".png");
        try {
            Utils.logger("d", "trying to write thumbnail for " + selectedFile.getName() + " -> " + pngBasename,
                    DEBUG_TAG);
            FileOutputStream out = new FileOutputStream(bmFile);
            bmThumbnail.compress(Bitmap.CompressFormat.PNG, 90, out);
        } catch (Exception e) {
            Log.e(DEBUG_TAG, "writeThumbToDiskForImportedVideo -> " + e.getMessage());
        }
    }

    private void restartDashboard() {
        Intent intent = DashboardActivity.this.getIntent();
        DashboardActivity.this.finish();
        DashboardActivity.this.startActivity(intent);
    }

    private class AsyncMove extends AsyncTask<File, Void, Integer> {

        File out;
        private boolean delResOk;

        protected void onPreExecute() {
            isAnyAsyncInProgress = true;
            Utils.logger("d", currentItem.getFilename() + " ---> BEGIN move", DEBUG_TAG);
            Toast.makeText(DashboardActivity.this,
                    currentItem.getFilename() + ": " + getString(R.string.move_progress), Toast.LENGTH_SHORT)
                    .show();
        }

        protected Integer doInBackground(File... file) {
            out = file[1];
            try {
                Utils.copyFile(file[0], file[1]);
                delResOk = doDelete(currentItem, file[0], false);
                return 0;
            } catch (IOException e) {
                return 1;
            }
        }

        @Override
        protected void onPostExecute(Integer res) {
            switch (res) {
            case 0:
                Toast.makeText(DashboardActivity.this,
                        currentItem.getFilename() + ": " + getString(R.string.move_ok), Toast.LENGTH_LONG).show();
                Utils.logger("i", currentItem.getFilename() + " --> END move: OK", DEBUG_TAG);

                Utils.scanMedia(DashboardActivity.this, new String[] { out.getAbsolutePath() },
                        new String[] { "video/*" });

                Json.addEntryToJsonFile(DashboardActivity.this, currentItem.getId(), currentItem.getType(),
                        currentItem.getYtId(), currentItem.getPos(), currentItem.getStatus(), out.getParent(),
                        out.getName(), currentItem.getBasename(), currentItem.getAudioExt(), currentItem.getSize(),
                        false);
                break;

            case 1:
                Toast.makeText(DashboardActivity.this,
                        currentItem.getFilename() + ": " + getString(R.string.move_error), Toast.LENGTH_LONG)
                        .show();
                Log.e(DEBUG_TAG, currentItem.getFilename() + " --> END move: FAILED");
            }

            refreshlist(DashboardActivity.this);

            if (!delResOk) {
                Utils.logger("w",
                        currentItem.getFilename() + " --> Copy OK (but not Deletion: original file still in place)",
                        DEBUG_TAG);
            }

            isAnyAsyncInProgress = false;
        }
    }

    private class AsyncCopy extends AsyncTask<File, Void, Integer> {

        File out;

        protected void onPreExecute() {
            isAnyAsyncInProgress = true;
            Utils.logger("d", currentItem.getFilename() + " ---> BEGIN copy", DEBUG_TAG);
            Toast.makeText(DashboardActivity.this,
                    currentItem.getFilename() + ": " + getString(R.string.copy_progress), Toast.LENGTH_SHORT)
                    .show();
        }

        protected Integer doInBackground(File... file) {
            out = file[1];
            try {
                Utils.copyFile(file[0], file[1]);
                return 0;
            } catch (IOException e) {
                return 1;
            }
        }

        @Override
        protected void onPostExecute(Integer res) {
            switch (res) {
            case 0:
                Toast.makeText(DashboardActivity.this,
                        currentItem.getFilename() + ": " + getString(R.string.copy_ok), Toast.LENGTH_LONG).show();
                Utils.logger("i", currentItem.getFilename() + " --> END copy: OK", DEBUG_TAG);

                Utils.scanMedia(DashboardActivity.this, new String[] { out.getAbsolutePath() },
                        new String[] { "video/*" });

                Json.addEntryToJsonFile(DashboardActivity.this, currentItem.getId(), currentItem.getType(),
                        currentItem.getYtId(), currentItem.getPos(), currentItem.getStatus(), out.getParent(),
                        out.getName(), currentItem.getBasename(), currentItem.getAudioExt(), currentItem.getSize(),
                        true);
                break;

            case 1:
                Toast.makeText(DashboardActivity.this,
                        currentItem.getFilename() + ": " + getString(R.string.copy_error), Toast.LENGTH_LONG)
                        .show();
                Log.e(DEBUG_TAG, currentItem.getFilename() + " --> END copy: FAILED");
            }

            refreshlist(DashboardActivity.this);
            isAnyAsyncInProgress = false;
        }
    }

    public static int refreshlist(final Activity activity) {
        entries = 0;
        activity.runOnUiThread(new Runnable() {
            public void run() {

                clearAdapterAndLists();

                // refill the Lists and re-populate the adapter
                entries = parseJson((Context) activity);
                updateProgressBars();
                buildList();

                if (da.isEmpty()) {
                    showEmptyListInfo(activity);
                }

                // refresh the list view
                da.notifyDataSetChanged();
            }
        });
        return entries;
    }

    public static void clearAdapterAndLists() {
        // clear the adapter
        da.clear();

        // empty the Lists
        idEntries.clear();
        typeEntries.clear();
        linkEntries.clear();
        posEntries.clear();
        statusEntries.clear();
        pathEntries.clear();
        filenameEntries.clear();
        basenameEntries.clear();
        audioExtEntries.clear();
        sizeEntries.clear();
        partSizeEntries.clear();
        progressEntries.clear();
        speedEntries.clear();
    }

    private static int parseJson(Context context) {
        // read existing/init new JSON 
        String previousJson = Json.readJsonDashboardFile(context);

        JSONObject jV = null;
        try {
            jV = new JSONObject(previousJson);
            //Utils.logger("v", "current json:\n" + previousJson, DEBUG_TAG);
            @SuppressWarnings("unchecked")
            Iterator<Object> ids = jV.keys();
            while (ids.hasNext()) {
                String id = (String) ids.next();
                JSONObject jO = new JSONObject();
                jO = jV.getJSONObject(id);
                idEntries.add(id);
                typeEntries.add(jO.getString(YTD.JSON_DATA_TYPE));
                linkEntries.add(jO.getString(YTD.JSON_DATA_YTID));
                posEntries.add(jO.getInt(YTD.JSON_DATA_POS));
                statusEntries.add(jO.getString(YTD.JSON_DATA_STATUS));
                pathEntries.add(jO.getString(YTD.JSON_DATA_PATH));
                filenameEntries.add(jO.getString(YTD.JSON_DATA_FILENAME));
                basenameEntries.add(jO.getString(YTD.JSON_DATA_BASENAME));
                audioExtEntries.add(jO.getString(YTD.JSON_DATA_AUDIO_EXT));
                sizeEntries.add(jO.getString(YTD.JSON_DATA_SIZE));
            }
        } catch (JSONException e) {
            Log.e(DEBUG_TAG, "JSONException @ parseJson: " + e.getMessage());
            Toast.makeText(sDashboard, sDashboard.getString(R.string.invalid_data), Toast.LENGTH_LONG).show();
            YTD.JSON_FILE.delete();
        }

        // do sort by filenames
        List<String> oldFilenameEntries = new ArrayList<String>(filenameEntries);
        List<String> oldIdEntries = new ArrayList<String>(idEntries);
        List<String> oldTypeEntries = new ArrayList<String>(typeEntries);
        List<String> oldLinkEntries = new ArrayList<String>(linkEntries);
        List<Integer> oldPosEntries = new ArrayList<Integer>(posEntries);
        List<String> oldStatusEntries = new ArrayList<String>(statusEntries);
        List<String> oldPathEntries = new ArrayList<String>(pathEntries);
        List<String> oldBasenameEntries = new ArrayList<String>(basenameEntries);
        List<String> oldAudioExtEntries = new ArrayList<String>(audioExtEntries);
        List<String> oldSizeEntries = new ArrayList<String>(sizeEntries);

        idEntries.clear();
        typeEntries.clear();
        linkEntries.clear();
        posEntries.clear();
        statusEntries.clear();
        pathEntries.clear();
        basenameEntries.clear();
        audioExtEntries.clear();
        sizeEntries.clear();

        Collections.sort(filenameEntries, String.CASE_INSENSITIVE_ORDER);

        for (int i = 0; i < filenameEntries.size(); i++) {
            for (int j = 0; j < oldFilenameEntries.size(); j++) {
                if (oldFilenameEntries.get(j) == filenameEntries.get(i)) {
                    idEntries.add(oldIdEntries.get(j));
                    typeEntries.add(oldTypeEntries.get(j));
                    linkEntries.add(oldLinkEntries.get(j));
                    posEntries.add(oldPosEntries.get(j));
                    statusEntries.add(oldStatusEntries.get(j));
                    pathEntries.add(oldPathEntries.get(j));
                    basenameEntries.add(oldBasenameEntries.get(j));
                    audioExtEntries.add(oldAudioExtEntries.get(j));
                    sizeEntries.add(oldSizeEntries.get(j));
                }
            }
        }
        return idEntries.size();
    }

    private static void updateProgressBars() {
        for (int i = 0; i < idEntries.size(); i++) {

            try {
                if (statusEntries.get(i).equals(YTD.JSON_DATA_STATUS_IN_PROGRESS)) {

                    String idstr = idEntries.get(i);
                    long idlong = Long.parseLong(idstr);
                    long bytes_downloaded = 0;
                    long bytes_total = 0;
                    int progress = 0;
                    long speed = 0;

                    try {
                        if (Maps.mDownloadSizeMap.get(idlong) != null) {
                            bytes_downloaded = Maps.mDownloadSizeMap.get(idlong); //YTD.downloadPartialSizeMap.get(idlong);
                            bytes_total = Maps.mTotalSizeMap.get(idlong); //YTD.downloadTotalSizeMap.get(idlong);
                            progress = (int) Maps.mDownloadPercentMap.get(idlong); //YTD.downloadPercentMap.get(idlong);
                            speed = Maps.mNetworkSpeedMap.get(idlong);
                        } else {
                            countdown--;
                            Utils.logger("w",
                                    "updateProgressBars: waiting for DM Maps on id " + idstr + " # " + countdown,
                                    DEBUG_TAG);
                            progress = -1;
                            bytes_downloaded = 0;
                            bytes_total = 0;
                            speed = 0;

                            DownloadTask dt = Maps.dtMap.get(idlong);

                            if (countdown == 0 && dt == null) {
                                Utils.logger("w",
                                        "countdown == 0 && dt == null; setting STATUS_PAUSED on id " + idstr,
                                        DEBUG_TAG);
                                Json.addEntryToJsonFile(sDashboard, idstr, typeEntries.get(i), linkEntries.get(i),
                                        posEntries.get(i), YTD.JSON_DATA_STATUS_PAUSED, pathEntries.get(i),
                                        filenameEntries.get(i), basenameEntries.get(i), audioExtEntries.get(i),
                                        sizeEntries.get(i), false);
                            }
                        }
                    } catch (NullPointerException e) {
                        Log.e(DEBUG_TAG, "NPE @ updateProgressBars");
                    }

                    String readableBytesDownloaded = Utils.MakeSizeHumanReadable(bytes_downloaded, false);
                    String readableBytesTotal = Utils.MakeSizeHumanReadable(bytes_total, false);

                    String progressRatio;
                    if (readableBytesTotal.equals("-")) {
                        progressRatio = "";
                    } else {
                        progressRatio = readableBytesDownloaded + "/" + readableBytesTotal;
                    }

                    progressEntries.add(progress);
                    partSizeEntries.add(progressRatio + " (" + String.valueOf(progress) + "%)");
                    speedEntries.add(speed);
                } else {
                    progressEntries.add(100);
                    partSizeEntries.add("-/-");
                    speedEntries.add((long) 0);
                }
            } catch (IndexOutOfBoundsException e) {
                Utils.logger("w", "updateProgressBars: " + e.getMessage(), DEBUG_TAG);
            }
        }
    }

    private static void buildList() {
        for (int i = 0; i < idEntries.size(); i++) {
            String thisSize;
            try {
                String thisStatus = statusEntries.get(i);

                if (thisStatus.equals(YTD.JSON_DATA_STATUS_IN_PROGRESS) && speedEntries.get(i) != 0) {
                    thisSize = partSizeEntries.get(i);
                } else {
                    thisSize = sizeEntries.get(i);
                }

                itemsList.add(new DashboardListItem(idEntries.get(i), typeEntries.get(i), linkEntries.get(i),
                        posEntries.get(i),
                        thisStatus
                                .replace(YTD.JSON_DATA_STATUS_COMPLETED,
                                        sDashboard.getString(R.string.json_status_completed))
                                .replace(YTD.JSON_DATA_STATUS_IN_PROGRESS,
                                        sDashboard.getString(R.string.json_status_in_progress))
                                .replace(YTD.JSON_DATA_STATUS_FAILED,
                                        sDashboard.getString(R.string.json_status_failed))
                                .replace(YTD.JSON_DATA_STATUS_IMPORTED,
                                        sDashboard.getString(R.string.json_status_imported))
                                .replace(YTD.JSON_DATA_STATUS_PAUSED,
                                        sDashboard.getString(R.string.json_status_paused)),
                        pathEntries.get(i), filenameEntries.get(i), basenameEntries.get(i), audioExtEntries.get(i),
                        thisSize, progressEntries.get(i), speedEntries.get(i)));
            } catch (IndexOutOfBoundsException e) {
                Utils.logger("w", "buildList: " + e.getMessage(), DEBUG_TAG);
            }
        }
    }

    // #####################################################################

    public void editId3Tags(View view) {
        BugSenseHandler.leaveBreadcrumb("editId3Tags");
        if (newClick) {
            tagArtist = "";
            tagAlbum = "";
            tagTitle = "";
            tagGenre = "";
            tagYear = "";
        }

        AlertDialog.Builder builder = new AlertDialog.Builder(boxThemeContextWrapper);
        LayoutInflater inflater0 = getLayoutInflater();
        final View id3s = inflater0.inflate(R.layout.dialog_edit_id3, null);

        final EditText artistEt = (EditText) id3s.findViewById(R.id.id3_et_artist);
        final EditText titleEt = (EditText) id3s.findViewById(R.id.id3_et_title);
        final EditText albumEt = (EditText) id3s.findViewById(R.id.id3_et_album);
        final EditText genreEt = (EditText) id3s.findViewById(R.id.id3_et_genre);
        final EditText yearEt = (EditText) id3s.findViewById(R.id.id3_et_year);

        if (tagTitle.isEmpty()) {
            titleEt.setText(currentItem.getBasename());
        } else {
            titleEt.setText(tagTitle);
        }

        if (tagYear.isEmpty()) {
            Calendar cal = new GregorianCalendar();
            int y = cal.get(Calendar.YEAR);
            yearEt.setText(String.valueOf(y));
        } else {
            yearEt.setText(tagYear);
        }

        artistEt.setText(tagArtist);
        albumEt.setText(tagAlbum);
        genreEt.setText(tagGenre);

        builder.setView(id3s).setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int id) {
                tagArtist = artistEt.getText().toString();
                tagAlbum = albumEt.getText().toString();
                tagTitle = titleEt.getText().toString();
                tagGenre = genreEt.getText().toString();
                tagYear = yearEt.getText().toString();
            }
        }).setNegativeButton(R.string.dialogs_negative, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                // cancel
            }
        });

        secureShowDialog(builder);
        newClick = false;
    }

    public void ffmpegJob(final File fileToConvert, final String bitrateType, final String bitrateValue) {
        BugSenseHandler.leaveBreadcrumb("ffmpegJob");
        isFfmpegRunning = true;

        vfilename = currentItem.getFilename();

        // audio job notification init
        aBuilder = new NotificationCompat.Builder(this);
        aNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        aBuilder.setSmallIcon(R.drawable.ic_stat_ytd);
        aBuilder.setContentTitle(vfilename);

        String aExt = currentItem.getAudioExt();
        basename = currentItem.getBasename();

        final String audioFileName;
        // "compose" the audio file
        if (bitrateValue != null) {
            extrTypeIsMp3Conv = true;
            audioFileName = basename + "_" + bitrateType + "-" + bitrateValue + ".mp3";
        } else {
            extrTypeIsMp3Conv = false;
            audioFileName = basename + aExt;

        }

        audioFile = new File(fileToConvert.getParent(), audioFileName);

        if (!audioFile.exists()) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Looper.prepare();

                    FfmpegController ffmpeg = null;
                    try {
                        ffmpeg = new FfmpegController(DashboardActivity.this);

                        // Toast + Notification + Log ::: Audio job in progress...
                        String text = null;
                        if (!extrTypeIsMp3Conv) {
                            text = getString(R.string.audio_extr_progress);
                            type = YTD.JSON_DATA_TYPE_A_E;
                        } else {
                            text = getString(R.string.audio_conv_progress);
                            type = YTD.JSON_DATA_TYPE_A_M;
                        }
                        Toast.makeText(DashboardActivity.this, "YTD: " + text, Toast.LENGTH_LONG).show();

                        aBuilder.setContentTitle(audioFileName);
                        aBuilder.setContentText(text);
                        aBuilder.setOngoing(true);
                        aNotificationManager.notify(2, aBuilder.build());
                        Utils.logger("i", vfilename + " " + text, DEBUG_TAG);
                    } catch (IOException ioe) {
                        Log.e(DEBUG_TAG, "Error loading ffmpeg. " + ioe.getMessage());
                    }

                    ShellDummy shell = new ShellDummy();

                    try {
                        ffmpeg.extractAudio(fileToConvert, audioFile, bitrateType, bitrateValue, shell);
                    } catch (IOException e) {
                        Log.e(DEBUG_TAG, "IOException running ffmpeg" + e.getMessage());
                    } catch (InterruptedException e) {
                        Log.e(DEBUG_TAG, "InterruptedException running ffmpeg" + e.getMessage());
                    }
                    Looper.loop();
                }
            }).start();
        } else {
            PopUps.showPopUp(getString(R.string.long_press_warning_title),
                    getString(R.string.audio_extr_warning_msg), "info", DashboardActivity.this);
            isFfmpegRunning = false;
        }
    }

    private class ShellDummy implements ShellCallback {

        @Override
        public void shellOut(String shellLine) {
            findAudioSuffix(shellLine);
            if (extrTypeIsMp3Conv) {
                getAudioJobProgress(shellLine);
            }
            Utils.logger("d", shellLine, DEBUG_TAG);
        }

        @Override
        public void processComplete(int exitValue) {
            Utils.logger("i", "FFmpeg process exit value: " + exitValue, DEBUG_TAG);
            String text = null;
            Intent audioIntent = new Intent(Intent.ACTION_VIEW);
            if (exitValue == 0) {

                // Toast + Notification + Log ::: Audio job OK
                if (!extrTypeIsMp3Conv) {
                    text = getString(R.string.audio_extr_completed);
                } else {
                    text = getString(R.string.audio_conv_completed);
                }
                Utils.logger("d", vfilename + " " + text, DEBUG_TAG);

                audioFile = addSuffixToAudioFile(basename, audioFile);
                Toast.makeText(DashboardActivity.this, audioFile.getName() + ": " + text, Toast.LENGTH_LONG).show();
                aBuilder.setContentTitle(audioFile.getName());
                aBuilder.setContentText(text);
                aBuilder.setOngoing(false);
                audioIntent.setDataAndType(Uri.fromFile(audioFile), "audio/*");
                PendingIntent contentIntent = PendingIntent.getActivity(DashboardActivity.this, 0, audioIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT);
                aBuilder.setContentIntent(contentIntent);

                // write id3 tags
                if (extrTypeIsMp3Conv) {
                    try {
                        Utils.logger("d", "writing ID3 tags...", DEBUG_TAG);
                        addId3Tags(audioFile, tagArtist, tagAlbum, tagTitle, tagGenre, tagYear);
                    } catch (ID3WriteException e) {
                        Log.e(DEBUG_TAG, "Unable to write id3 tags", e);
                    } catch (IOException e) {
                        Log.e(DEBUG_TAG, "Unable to write id3 tags", e);
                    }
                }

                Utils.scanMedia(getApplicationContext(), new String[] { audioFile.getAbsolutePath() },
                        new String[] { "audio/*" });

                // remove selected video upon successful audio extraction
                if (removeVideo || removeAudio) {
                    final File fileToDel = new File(currentItem.getPath(), currentItem.getFilename());
                    new AsyncDelete().execute(fileToDel);
                }

                // add audio file to the JSON file entry
                Json.addEntryToJsonFile(DashboardActivity.this, currentItem.getId(), type, currentItem.getYtId(),
                        currentItem.getPos(), YTD.JSON_DATA_STATUS_COMPLETED, currentItem.getPath(),
                        audioFile.getName(), currentItem.getBasename(), "",
                        Utils.MakeSizeHumanReadable((int) audioFile.length(), false), true);

                refreshlist(DashboardActivity.this);

                Utils.setNotificationDefaults(aBuilder);
            } else {
                setNotificationForAudioJobError();

                Json.addEntryToJsonFile(DashboardActivity.this, currentItem.getId(), type, currentItem.getYtId(),
                        currentItem.getPos(), YTD.JSON_DATA_STATUS_FAILED, currentItem.getPath(),
                        audioFile.getName(), currentItem.getBasename(), "", "-", true);

                refreshlist(DashboardActivity.this);
            }

            aBuilder.setProgress(0, 0, false);
            aNotificationManager.cancel(2);
            aNotificationManager.notify(2, aBuilder.build());

            isFfmpegRunning = false;
        }

        @Override
        public void processNotStartedCheck(boolean started) {
            if (!started) {
                Utils.logger("w", "FFmpeg process not started or not completed", DEBUG_TAG);

                // Toast + Notification + Log ::: Audio job error
                setNotificationForAudioJobError();
            }
            aNotificationManager.notify(2, aBuilder.build());
            isFfmpegRunning = false;
        }
    }

    public File addSuffixToAudioFile(String aBaseName, File extractedAudioFile) {
        // Rename audio file to add a more detailed suffix, 
        // but only if it has been matched from the ffmpeg console output
        if (!extrTypeIsMp3Conv && extractedAudioFile.exists() && !aSuffix.equals(".audio")) {
            String newName = aBaseName + aSuffix;
            File newFile = new File(currentItem.getPath(), newName);
            if (extractedAudioFile.renameTo(newFile)) {
                Utils.logger("i", "'" + extractedAudioFile.getName() + "' renamed to: '" + newName + "'",
                        DEBUG_TAG);
                return newFile;
            } else {
                Log.e(DEBUG_TAG, "Unable to rename '" + extractedAudioFile.getName() + "' to: '" + aSuffix + "'");
            }
        }
        return extractedAudioFile;
    }

    /* method addId3Tags adapted from Stack Overflow:
     * 
     * http://stackoverflow.com/questions/9707572/android-how-to-get-and-setchange-id3-tagmetadata-of-audio-files/9770646#9770646
     * 
     * Q: http://stackoverflow.com/users/849664/chirag-shah
     * A: http://stackoverflow.com/users/903469/mkjparekh
     */

    public void addId3Tags(File src, String artist, String album, String title, String genre, String year)
            throws IOException, ID3WriteException {
        MusicMetadataSet src_set = new MyID3().read(src);
        if (src_set == null) {
            Utils.logger("w", "no metadata", DEBUG_TAG);
        } else {
            MusicMetadata meta = new MusicMetadata("ytd");

            if (artist == null || artist.isEmpty())
                artist = "YTD";
            meta.setArtist(artist);

            if (album == null || album.isEmpty())
                album = "YTD Extracted Audio";
            meta.setAlbum(album);

            if (title == null || title.isEmpty())
                title = basename;
            meta.setSongTitle(title);

            if (genre != null)
                meta.setGenre(genre);

            if (year != null)
                meta.setYear(year);

            Utils.logger("d", "metadata used for last id3tag:" + "\n  artist: " + artist + "\n  album: " + album
                    + "\n  title: " + title + "\n  genre: " + genre + "\n  year: " + year, DEBUG_TAG);
            new MyID3().update(src, src_set, meta);
        }
    }

    private void findAudioSuffix(String shellLine) {
        Pattern audioPattern = Pattern.compile("#0:0.*: Audio: (.+), .+?(mono|stereo .default.|stereo)(, .+ kb|)");
        Matcher audioMatcher = audioPattern.matcher(shellLine);
        if (audioMatcher.find() && !extrTypeIsMp3Conv) {
            String oggBr = "a";
            String groupTwo = "n";
            if (audioMatcher.group(2).equals("stereo (default)")) {
                if (vfilename.contains("hd")) {
                    oggBr = "192k";
                } else {
                    oggBr = "128k";
                }
                groupTwo = "stereo";
            } else {
                oggBr = "";
                groupTwo = audioMatcher.group(2);
            }

            aSuffix = "_" + groupTwo + "_" + audioMatcher.group(3).replace(", ", "").replace(" kb", "k") + oggBr
                    + "." + audioMatcher.group(1).replaceFirst(" (.*/.*)", "").replace("vorbis", "ogg");

            Utils.logger("i", "Audio suffix found: " + aSuffix, DEBUG_TAG);
        }
    }

    public void setNotificationForAudioJobError() {
        BugSenseHandler.leaveBreadcrumb("setNotificationForAudioJobError");
        String text;
        if (!extrTypeIsMp3Conv) {
            text = getString(R.string.audio_extr_error);
        } else {
            text = getString(R.string.audio_conv_error);
        }
        Log.e(DEBUG_TAG, vfilename + " " + text);
        Toast.makeText(DashboardActivity.this, "YTD: " + text, Toast.LENGTH_LONG).show();
        aBuilder.setContentText(text);
    }

    private void getAudioJobProgress(String shellLine) {
        Pattern totalTimePattern = Pattern.compile("Duration: (..):(..):(..)\\.(..)");
        Matcher totalTimeMatcher = totalTimePattern.matcher(shellLine);
        if (totalTimeMatcher.find()) {
            totSeconds = getTotSeconds(totalTimeMatcher);
        }

        Pattern currentTimePattern = Pattern.compile("time=(..):(..):(..)\\.(..)");
        Matcher currentTimeMatcher = currentTimePattern.matcher(shellLine);
        if (currentTimeMatcher.find()) {
            currentTime = getTotSeconds(currentTimeMatcher);
        }

        if (totSeconds != 0) {
            aBuilder.setProgress(totSeconds, currentTime, false);
            aNotificationManager.notify(2, aBuilder.build());
        }
    }

    private int getTotSeconds(Matcher timeMatcher) {
        int h = Integer.parseInt(timeMatcher.group(1));
        int m = Integer.parseInt(timeMatcher.group(2));
        int s = Integer.parseInt(timeMatcher.group(3));
        int f = Integer.parseInt(timeMatcher.group(4));

        long hToSec = TimeUnit.HOURS.toSeconds(h);
        long mToSec = TimeUnit.MINUTES.toSeconds(m);

        int tot = (int) (hToSec + mToSec + s);
        if (f > 50)
            tot = tot + 1;

        Utils.logger("d", "h=" + h + " m=" + m + " s=" + s + "." + f + " -> tot=" + tot, DEBUG_TAG);
        return tot;
    }

    public void secureShowDialog(AlertDialog.Builder adb) {
        //builder.create();
        if (!((Activity) DashboardActivity.this).isFinishing()) {
            adb.show();
        }
    }

    public void notifyFfmpegNotInstalled() {
        Utils.logger("w", "FFmpeg not installed/enabled", DEBUG_TAG);
        BugSenseHandler.leaveBreadcrumb("notifyFfmpegNotInstalled");
        AlertDialog.Builder adb = new AlertDialog.Builder(boxThemeContextWrapper);
        adb.setTitle(getString(R.string.ffmpeg_not_enabled_title));
        adb.setMessage(getString(R.string.ffmpeg_not_enabled_msg));

        adb.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                startActivity(new Intent(DashboardActivity.this, SettingsActivity.class));
            }

        });

        adb.setNegativeButton(getString(R.string.dialogs_negative), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                // cancel
            }
        });

        secureShowDialog(adb);
    }

    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            isLandscape = true;
        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
            isLandscape = false;
        }
    }

    private String[] retrieveBitrateValueFromSpinner(final Spinner sp) {
        String bitrateEntry = String.valueOf(sp.getSelectedItem());

        String[] bitrateValues = sDashboard.getResources().getStringArray(R.array.mp3_bitrate_entry_values);

        String[] bitrateEntries = sDashboard.getResources().getStringArray(R.array.mp3_bitrate_entries);

        String bitrateType = null;
        if (bitrateEntry.contains("CBR")) {
            bitrateType = "CBR";
        } else {
            bitrateType = "VBR";
        }

        String bitrateValue = null;
        for (int i = 0; i < bitrateValues.length; i++) {
            if (bitrateEntry.equals(bitrateEntries[i]))
                bitrateValue = bitrateValues[i];
        }
        Utils.logger("d", "selected bitrate value: " + bitrateValue + "\nselected bitrate entry: " + bitrateEntry,
                DEBUG_TAG);

        return new String[] { bitrateType, bitrateValue };
    }
}