com.oakesville.mythling.MediaActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.oakesville.mythling.MediaActivity.java

Source

/**
 * Copyright 2015 Donald Oakes
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.oakesville.mythling;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;

import org.json.JSONException;
import org.json.JSONObject;

import com.oakesville.mythling.VideoPlaybackDialog.PlaybackDialogListener;
import com.oakesville.mythling.app.AppData;
import com.oakesville.mythling.app.AppSettings;
import com.oakesville.mythling.app.BadSettingsException;
import com.oakesville.mythling.app.Localizer;
import com.oakesville.mythling.firetv.FireTvEpgActivity;
import com.oakesville.mythling.media.AllTunersInUseException;
import com.oakesville.mythling.media.Category;
import com.oakesville.mythling.media.Cut;
import com.oakesville.mythling.media.Download;
import com.oakesville.mythling.media.Item;
import com.oakesville.mythling.media.Listable;
import com.oakesville.mythling.media.LiveStreamInfo;
import com.oakesville.mythling.media.MediaList;
import com.oakesville.mythling.media.MediaSettings;
import com.oakesville.mythling.media.MediaSettings.MediaType;
import com.oakesville.mythling.media.MediaSettings.SortType;
import com.oakesville.mythling.media.MediaSettings.ViewType;
import com.oakesville.mythling.media.PlaybackOptions;
import com.oakesville.mythling.media.PlaybackOptions.PlaybackOption;
import com.oakesville.mythling.media.Recording;
import com.oakesville.mythling.media.SearchResults;
import com.oakesville.mythling.media.StorageGroup;
import com.oakesville.mythling.media.TvShow;
import com.oakesville.mythling.prefs.PrefDismissDialog;
import com.oakesville.mythling.prefs.PrefDismissDialog.PrefDismissListener;
import com.oakesville.mythling.prefs.PrefsActivity;
import com.oakesville.mythling.util.FrontendPlayer;
import com.oakesville.mythling.util.HttpHelper;
import com.oakesville.mythling.util.HttpHelper.AuthType;
import com.oakesville.mythling.util.MediaListParser;
import com.oakesville.mythling.util.MediaStreamProxy;
import com.oakesville.mythling.util.MediaStreamProxy.ProxyInfo;
import com.oakesville.mythling.util.MythTvParser;
import com.oakesville.mythling.util.Recorder;
import com.oakesville.mythling.util.Reporter;
import com.oakesville.mythling.util.ServiceFrontendPlayer;
import com.oakesville.mythling.util.SocketFrontendPlayer;
import com.oakesville.mythling.util.Transcoder;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DownloadManager;
import android.app.DownloadManager.Request;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnKeyListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Toast;

/**
 * Base class for the different ways to view collections of MythTV media.
 */
public abstract class MediaActivity extends Activity {
    private static final String TAG = MediaActivity.class.getSimpleName();

    static final String DETAIL_FRAGMENT = "detailFragment";
    static final String LIST_FRAGMENT = "listFragment";
    static final int MEDIA_ACTIVITY_CONTEXT_MENU_GROUP_ID = 1;
    static final int LIST_FRAGMENT_CONTEXT_MENU_GROUP_ID = 2;
    /**
     * from R.array.item_long_click_menu
     */
    static final int LONG_CLICK_MENU_PLAY = 0;
    static final int LONG_CLICK_MENU_TRANSCODE = 1;
    static final int LONG_CLICK_MENU_DOWNLOAD = 2;
    static final int LONG_CLICK_MENU_DELETE = 3;

    static final String PATH = "path";
    static final String SEL_ITEM_INDEX = "idx";
    static final String CURRENT_TOP = "curTop";
    static final String TOP_OFFSET = "topOff";
    static final String GRAB_FOCUS = "grab";
    static final String MODE_SWITCH = "modeSwitch";

    private ListableListAdapter listAdapter;

    ListableListAdapter getListAdapter() {
        return listAdapter;
    }

    void setListAdapter(ListableListAdapter listAdapter) {
        this.listAdapter = listAdapter;
        getListView().setAdapter(listAdapter);
    }

    private String path;

    protected String getPath() {
        return path;
    }

    protected void setPath(String path) {
        this.path = path;
    }

    protected List<Listable> getListables() {
        if (mediaList == null) // FIXME: issue #68
            return new ArrayList<Listable>();
        return mediaList.getListables(getPath());
    }

    protected List<Listable> getListables(String path) {
        if (mediaList == null) // FIXME: issue #68
            return new ArrayList<Listable>();
        return mediaList.getListables(path);
    }

    private int currentTop = 0; // top item in the list

    int getCurrentTop() {
        return currentTop;
    }

    void setCurrentTop(int currentTop) {
        this.currentTop = currentTop;
    }

    private int topOffset = 0;

    int getTopOffset() {
        return topOffset;
    }

    void setTopOffset(int topOffset) {
        this.topOffset = topOffset;
    }

    private int selItemIndex = 0;

    int getSelItemIndex() {
        return selItemIndex;
    }

    void setSelItemIndex(int selItemIndex) {
        this.selItemIndex = selItemIndex;
    }

    // these need to be retained after orientation change, so their kept with AppData
    protected MediaList mediaList;
    protected Map<String, StorageGroup> storageGroups;

    private static AppData appData;

    public static AppData getAppData() {
        return appData;
    }

    public static void setAppData(AppData data) {
        appData = data;
    }

    private AppSettings appSettings;

    public AppSettings getAppSettings() {
        return appSettings;
    }

    // it seems there's a reason we keep a ref separate from appSettings.getMediaSettings()
    // (see MainActivity/MediaListActivity/MediaPagerActivity.populate())
    private MediaType mediaType;

    protected MediaType getMediaType() {
        return mediaType;
    }

    protected void setMediaType(MediaType mt) {
        this.mediaType = mt;
    }

    public String getCharSet() {
        return "UTF-8";
    }

    private MenuItem mediaMenuItem;
    private MenuItem searchMenuItem;
    private MenuItem viewMenuItem;
    private MenuItem sortMenuItem;
    private MenuItem moviesMenuItem;
    private MenuItem tvSeriesMenuItem;
    private MenuItem musicMenuItem;
    private MenuItem mythwebMenuItem;
    private MenuItem stopMenuItem;

    private ProgressBar progressBar;

    private ProgressDialog countdownDialog;
    private int count;
    private Timer timer;

    protected boolean modeSwitch; // tracking for back button

    public void refresh() {
        currentTop = 0;
        topOffset = 0;
        selItemIndex = 0;
    }

    public abstract ListView getListView();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        appSettings = new AppSettings(getApplicationContext());
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (supportsMusic())
            registerPlaybackReceiver();
    }

    @Override
    public void onPause() {
        unregisterPlaybackReceiver();
        if (downloadReceivers != null) {
            List<Long> idsToRemove = new ArrayList<Long>();
            for (long downloadId : downloadReceivers.keySet()) {
                unregisterDownloadReceiver(downloadId);
                idsToRemove.add(downloadId);
            }
            for (long id : idsToRemove)
                downloadReceivers.remove(id);
        }
        super.onPause();
    }

    private BroadcastReceiver playbackReceiver;

    private void registerPlaybackReceiver() {
        if (playbackReceiver == null) {
            playbackReceiver = new BroadcastReceiver() {
                public void onReceive(Context context, Intent intent) {
                    showStopMenuItem(false);
                }
            };
        }
        registerReceiver(playbackReceiver, new IntentFilter(MusicPlaybackService.ACTION_PLAYBACK_STOPPED));
    }

    private void unregisterPlaybackReceiver() {
        if (playbackReceiver != null) { // null if music not supported
            unregisterReceiver(playbackReceiver);
            playbackReceiver = null;
        }
    }

    private class DownloadBroadcastReceiver extends BroadcastReceiver {
        private Item item;
        private long downloadId;

        public DownloadBroadcastReceiver(Item item, long downloadId) {
            this.item = item;
            this.downloadId = downloadId;
        }

        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
                long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
                if (downloadId == id) {
                    item.setDownloadId(downloadId);
                    unregisterDownloadReceiver(downloadId);
                    if (downloadReceivers != null) // how else?
                        downloadReceivers.remove(downloadId);
                    onResume();
                }
            }
        }
    }

    private Map<Long, DownloadBroadcastReceiver> downloadReceivers;

    private void registerDownloadReceiver(Item item, long downloadId) {
        if (downloadReceivers == null)
            downloadReceivers = new HashMap<Long, DownloadBroadcastReceiver>();
        DownloadBroadcastReceiver receiver = downloadReceivers.get(downloadId);
        if (receiver != null) {
            unregisterDownloadReceiver(downloadId);
            downloadReceivers.remove(downloadId);
        }
        receiver = new DownloadBroadcastReceiver(item, downloadId);
        registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
        downloadReceivers.put(downloadId, receiver);
    }

    private void unregisterDownloadReceiver(long downloadId) {
        if (downloadReceivers != null) { // how otherwise?
            DownloadBroadcastReceiver receiver = downloadReceivers.get(downloadId);
            if (receiver != null)
                unregisterReceiver(receiver);
        }
    }

    protected ProgressBar createProgressBar() {
        progressBar = (ProgressBar) findViewById(R.id.progress);
        progressBar.setVisibility(View.GONE);
        progressBar.setIndeterminate(true);
        progressBar.setScaleX(0.20f);
        progressBar.setScaleY(0.20f);
        return progressBar;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MediaSettings mediaSettings = appSettings.getMediaSettings();

        mediaMenuItem = menu.findItem(R.id.menu_media);
        if (mediaMenuItem != null) {
            if (mediaList != null)
                mediaMenuItem.setTitle(
                        Localizer.getMediaLabel(mediaSettings.getType()) + " (" + mediaList.getCount() + ")");
            else
                mediaMenuItem.setTitle(Localizer.getMediaLabel(mediaSettings.getType()));
            if (mediaSettings.isMusic())
                mediaMenuItem.getSubMenu().findItem(R.id.media_music).setChecked(true);
            else if (mediaSettings.isLiveTv())
                mediaMenuItem.getSubMenu().findItem(R.id.media_tv).setChecked(true);
            else if (mediaSettings.isMovies())
                mediaMenuItem.getSubMenu().findItem(R.id.media_movies).setChecked(true);
            else if (mediaSettings.isTvSeries())
                mediaMenuItem.getSubMenu().findItem(R.id.media_tv_series).setChecked(true);
            else if (mediaSettings.isVideos())
                mediaMenuItem.getSubMenu().findItem(R.id.media_videos).setChecked(true);
            else
                mediaMenuItem.getSubMenu().findItem(R.id.media_recordings).setChecked(true);

            moviesMenuItem = mediaMenuItem.getSubMenu().findItem(R.id.media_movies);
            showMoviesMenuItem(supportsMovies());
            tvSeriesMenuItem = mediaMenuItem.getSubMenu().findItem(R.id.media_tv_series);
            showTvSeriesMenuItem(supportsTvSeries());
            musicMenuItem = mediaMenuItem.getSubMenu().findItem(R.id.media_music);
            showMusicMenuItem(supportsMusic());
        }

        searchMenuItem = menu.findItem(R.id.menu_search);
        showSearchMenu(supportsSearch());

        sortMenuItem = menu.findItem(R.id.menu_sort);
        showSortMenu(supportsSort());

        viewMenuItem = menu.findItem(R.id.menu_view);
        showViewMenu(supportsViewMenu());

        stopMenuItem = menu.findItem(R.id.menu_stop);
        showStopMenuItem(isPlayingMusic());

        return super.onPrepareOptionsMenu(menu);
    }

    protected void showSearchMenu(boolean show) {
        if (searchMenuItem != null) {
            searchMenuItem.setEnabled(show);
            searchMenuItem.setVisible(show);
        }
    }

    protected void showMoviesMenuItem(boolean show) {
        if (moviesMenuItem != null) {
            moviesMenuItem.setEnabled(show);
            moviesMenuItem.setVisible(show);
        }
    }

    protected void showTvSeriesMenuItem(boolean show) {
        if (tvSeriesMenuItem != null) {
            tvSeriesMenuItem.setEnabled(show);
            tvSeriesMenuItem.setVisible(show);
        }
    }

    protected void showMusicMenuItem(boolean show) {
        if (musicMenuItem != null) {
            musicMenuItem.setEnabled(show);
            musicMenuItem.setVisible(show);
        }
    }

    protected void showStopMenuItem(boolean show) {
        if (stopMenuItem != null) {
            stopMenuItem.setEnabled(show);
            stopMenuItem.setVisible(show);
        }
    }

    protected void showViewMenu(boolean show) {
        if (viewMenuItem != null) {
            MediaSettings mediaSettings = appSettings.getMediaSettings();
            if (show) {
                viewMenuItem.setIcon(mediaSettings.getViewIcon());
                if (mediaSettings.getViewType() == ViewType.detail)
                    viewMenuItem.getSubMenu().findItem(R.id.view_detail).setChecked(true);
                else if (mediaSettings.getViewType() == ViewType.split)
                    viewMenuItem.getSubMenu().findItem(R.id.view_split).setChecked(true);
                else
                    viewMenuItem.getSubMenu().findItem(R.id.view_list).setChecked(true);
            }

            viewMenuItem.setEnabled(show);
            viewMenuItem.setVisible(show);
        }
    }

    protected void showMythwebMenu(boolean show) {
        if (mythwebMenuItem != null) {
            mythwebMenuItem.setEnabled(show);
            mythwebMenuItem.setVisible(show);
        }
    }

    protected void showSortMenu(boolean show) {
        if (sortMenuItem != null) {
            if (show) {
                MediaSettings mediaSettings = appSettings.getMediaSettings();
                sortMenuItem.setTitle(Localizer.getSortLabel(mediaSettings.getSortType()));
                boolean isLiveTv = mediaSettings.getType() == MediaType.liveTv;
                sortMenuItem.getSubMenu().findItem(R.id.sort_byTitle).setVisible(!isLiveTv);
                sortMenuItem.getSubMenu().findItem(R.id.sort_byDate).setVisible(!isLiveTv);
                sortMenuItem.getSubMenu().findItem(R.id.sort_byRating).setVisible(!isLiveTv);
                sortMenuItem.getSubMenu().findItem(R.id.sort_byChannel).setVisible(isLiveTv);
                sortMenuItem.getSubMenu().findItem(R.id.sort_byCallsign).setVisible(isLiveTv);
                if (isLiveTv) {
                    if (mediaSettings.getSortType() == SortType.byCallsign)
                        sortMenuItem.getSubMenu().findItem(R.id.sort_byCallsign).setChecked(true);
                    else
                        sortMenuItem.getSubMenu().findItem(R.id.sort_byChannel).setChecked(true);
                } else {
                    if (mediaSettings.getSortType() == SortType.byDate)
                        sortMenuItem.getSubMenu().findItem(R.id.sort_byDate).setChecked(true);
                    else if (mediaSettings.getSortType() == SortType.byRating)
                        sortMenuItem.getSubMenu().findItem(R.id.sort_byRating).setChecked(true);
                    else
                        sortMenuItem.getSubMenu().findItem(R.id.sort_byTitle).setChecked(true);
                }
            }
            sortMenuItem.setEnabled(show);
            sortMenuItem.setVisible(show);
        }
    }

    protected boolean supportsSearch() {
        return getAppSettings().isMythlingMediaServices();
    }

    protected boolean supportsSort() {
        if (mediaList == null)
            return false;
        if (getAppSettings().isMythlingMediaServices() && mediaList.getMediaType() == MediaType.videos)
            return false; // temporarily until issue #29 is fixed
        return mediaList != null && mediaList.supportsSort();
    }

    protected boolean supportsViewMenu() {
        return mediaList != null && mediaList.canHaveArtwork();
    }

    protected boolean supportsMovies() {
        return getAppSettings().isVideosCategorization();
    }

    protected boolean supportsTvSeries() {
        return getAppSettings().isVideosCategorization();
    }

    protected boolean supportsMusic() {
        return true;
    }

    protected boolean supportsMythwebMenu() {
        return getAppSettings().isMythWebAccessEnabled();
    }

    protected boolean isListView() {
        return appSettings.getMediaSettings().getViewType() == ViewType.list;
    }

    protected boolean isDetailView() {
        return appSettings.getMediaSettings().getViewType() == ViewType.detail;
    }

    protected boolean isSplitView() {
        return appSettings.getMediaSettings().getViewType() == ViewType.split;
    }

    protected boolean isTv() {
        // appSettings can be null during orientation change
        return appSettings == null ? false : appSettings.isTv();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        if (getAppSettings().isFireTv())
            getMenuInflater().inflate(R.menu.firetv_main, menu);
        else
            getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            if (modeSwitch)
                onBackPressed();
            else
                NavUtils.navigateUpFromSameTask(this);
            return true;
        }
        try {
            if (sortMenuItem != null)
                sortMenuItem.setTitle(Localizer.getSortLabel(appSettings.getMediaSettings().getSortType()));
            if (viewMenuItem != null)
                viewMenuItem.setIcon(appSettings.getMediaSettings().getViewIcon());

            if (item.getItemId() == R.id.media_music) {
                ViewType oldView = appSettings.getMediaSettings().getViewType();
                appSettings.setMediaType(MediaType.music);
                item.setChecked(true);
                mediaMenuItem.setTitle(Localizer.getMediaLabel(MediaType.music));
                showViewMenu(supportsViewMenu());
                showSortMenu(supportsSort());
                ViewType newView = appSettings.getMediaSettings().getViewType();
                if (oldView != newView)
                    applyViewChange(oldView, newView);
                else
                    refresh();
                return true;
            } else if (item.getItemId() == R.id.media_videos) {
                ViewType oldView = appSettings.getMediaSettings().getViewType();
                appSettings.setMediaType(MediaType.videos);
                item.setChecked(true);
                mediaMenuItem.setTitle(Localizer.getMediaLabel(MediaType.videos));
                showViewMenu(supportsViewMenu());
                showSortMenu(supportsSort());
                ViewType newView = appSettings.getMediaSettings().getViewType();
                if (oldView != newView)
                    applyViewChange(oldView, newView);
                else
                    refresh();
                return true;
            } else if (item.getItemId() == R.id.media_recordings) {
                ViewType oldView = appSettings.getMediaSettings().getViewType();
                appSettings.setMediaType(MediaType.recordings); // clears view type
                item.setChecked(true);
                mediaMenuItem.setTitle(Localizer.getMediaLabel(MediaType.recordings));
                showViewMenu(supportsViewMenu());
                showSortMenu(supportsSort());
                ViewType newView = appSettings.getMediaSettings().getViewType();
                if (oldView != newView)
                    applyViewChange(oldView, newView);
                else
                    refresh();
                return true;
            } else if (item.getItemId() == R.id.media_tv) {
                ViewType oldView = appSettings.getMediaSettings().getViewType();
                appSettings.setMediaType(MediaType.liveTv); // clears view type
                item.setChecked(true);
                mediaMenuItem.setTitle(Localizer.getMediaLabel(MediaType.liveTv));
                showViewMenu(supportsViewMenu());
                showSortMenu(supportsSort());
                ViewType newView = appSettings.getMediaSettings().getViewType();
                if (oldView != newView)
                    applyViewChange(oldView, newView);
                else
                    refresh();
                return true;
            } else if (item.getItemId() == R.id.media_movies) {
                ViewType oldView = appSettings.getMediaSettings().getViewType();
                appSettings.setMediaType(MediaType.movies);
                item.setChecked(true);
                mediaMenuItem.setTitle(Localizer.getMediaLabel(MediaType.movies));
                showViewMenu(supportsViewMenu());
                showSortMenu(supportsSort());
                ViewType newView = appSettings.getMediaSettings().getViewType();
                if (oldView != newView)
                    applyViewChange(oldView, newView);
                else
                    refresh();
                return true;
            } else if (item.getItemId() == R.id.media_tv_series) {
                ViewType oldView = appSettings.getMediaSettings().getViewType();
                appSettings.setMediaType(MediaType.tvSeries);
                item.setChecked(true);
                mediaMenuItem.setTitle(Localizer.getMediaLabel(MediaType.tvSeries));
                showViewMenu(supportsViewMenu());
                showSortMenu(supportsSort());
                ViewType newView = appSettings.getMediaSettings().getViewType();
                if (oldView != newView)
                    applyViewChange(oldView, newView);
                else
                    refresh();
                return true;
            } else if (item.getItemId() == R.id.sort_byTitle) {
                appSettings.setSortType(SortType.byTitle);
                item.setChecked(true);
                sortMenuItem.setTitle(R.string.menu_byTitle);
                sort();
                return true;
            } else if (item.getItemId() == R.id.sort_byDate) {
                appSettings.setSortType(SortType.byDate);
                item.setChecked(true);
                sortMenuItem.setTitle(R.string.menu_byDate);
                sort();
                return true;
            } else if (item.getItemId() == R.id.sort_byRating) {
                appSettings.setSortType(SortType.byRating);
                item.setChecked(true);
                sortMenuItem.setTitle(R.string.menu_byRating);
                sort();
                return true;
            } else if (item.getItemId() == R.id.sort_byChannel) {
                appSettings.setSortType(SortType.byChannel);
                item.setChecked(true);
                sortMenuItem.setTitle(R.string.menu_byChannel);
                sort();
                return true;
            } else if (item.getItemId() == R.id.sort_byCallsign) {
                appSettings.setSortType(SortType.byCallsign);
                item.setChecked(true);
                sortMenuItem.setTitle(R.string.menu_byCallsign);
                sort();
                return true;
            } else if (item.getItemId() == R.id.view_list) {
                appSettings.setViewType(ViewType.list);
                item.setChecked(true);
                viewMenuItem.setIcon(R.drawable.ic_menu_list);
                goListView();
                return true;
            } else if (item.getItemId() == R.id.view_detail) {
                appSettings.setViewType(ViewType.detail);
                item.setChecked(true);
                viewMenuItem.setIcon(R.drawable.ic_menu_detail);
                goDetailView();
                return true;
            } else if (item.getItemId() == R.id.view_split) {
                ViewType oldView = appSettings.getMediaSettings().getViewType();
                appSettings.setViewType(ViewType.split);
                item.setChecked(true);
                viewMenuItem.setIcon(R.drawable.ic_menu_split);
                goSplitView();
                if (oldView == ViewType.list)
                    initSplitView();
                return true;
            } else if (item.getItemId() == R.id.menu_refresh) {
                refresh();
                return true;
            } else if (item.getItemId() == R.id.menu_settings) {
                startActivity(new Intent(this, PrefsActivity.class));
                return true;
            } else if (item.getItemId() == R.id.menu_search) {
                return onSearchRequested();
            } else if (item.getItemId() == R.id.menu_guide) {
                startActivity(
                        new Intent(this, appSettings.isFireTv() ? FireTvEpgActivity.class : EpgActivity.class));
                return true;
            } else if (item.getItemId() == R.id.menu_help) {
                String url = getString(R.string.url_help);
                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url), getApplicationContext(),
                        WebViewActivity.class));
                return true;
            } else if (item.getItemId() == R.id.menu_stop) {
                Intent stopMusic = new Intent(this, MusicPlaybackService.class);
                stopMusic.setAction(MusicPlaybackService.ACTION_STOP);
                startService(stopMusic);
                return true;
            }
        } catch (BadSettingsException ex) {
            stopProgress();
            Toast.makeText(getApplicationContext(), getString(R.string.bad_setting_) + "\n" + ex.getMessage(),
                    Toast.LENGTH_LONG).show();
        } catch (Exception ex) {
            Log.e(TAG, ex.getMessage(), ex);
            if (getAppSettings().isErrorReportingEnabled())
                new Reporter(ex).send();
            stopProgress();
            Toast.makeText(getApplicationContext(), getString(R.string.error_) + ex.toString(), Toast.LENGTH_LONG)
                    .show();
        }

        return super.onOptionsItemSelected(item);
    }

    protected void applyViewChange(ViewType oldView, ViewType newView) {
        if (newView == ViewType.detail && oldView != ViewType.detail) {
            getAppSettings().clearCache(); // refresh after nav
            goDetailView();
        } else if (oldView == ViewType.detail && newView != ViewType.detail) {
            getAppSettings().clearCache(); // refresh after nav
            if (newView == ViewType.split)
                goSplitView();
            else
                goListView();
        } else if (oldView != newView) {
            if (newView == ViewType.split)
                goSplitView();
            else
                goListView();
            refresh();
        }
    }

    protected void showItemInDetailPane(int position) {
        showItemInDetailPane(position, false);
    }

    protected void showItemInDetailPane(int position, boolean grabFocus) {
        ItemDetailFragment detailFragment = new ItemDetailFragment();
        Bundle arguments = new Bundle();
        arguments.putInt(SEL_ITEM_INDEX, position);
        arguments.putBoolean(GRAB_FOCUS, grabFocus);
        detailFragment.setArguments(arguments);
        // FIXME: Issue #69
        getFragmentManager().beginTransaction().replace(R.id.detail_container, detailFragment, DETAIL_FRAGMENT)
                .commit();
    }

    protected void showSubListPane(String path) {
        showSubListPane(path, -1, false);
    }

    protected void showSubListPane(String path, boolean grabFocus) {
        showSubListPane(path, -1, grabFocus);
    }

    protected void showSubListPane(String path, int selIdx) {
        showSubListPane(path, selIdx, false);
    }

    protected void showSubListPane(String path, int selIdx, boolean grabFocus) {
        ItemListFragment listFragment = new ItemListFragment();
        Bundle arguments = new Bundle();
        arguments.putString(PATH, path);
        arguments.putInt(SEL_ITEM_INDEX, selIdx);
        arguments.putBoolean(GRAB_FOCUS, grabFocus);
        listFragment.setArguments(arguments);
        getFragmentManager().beginTransaction().replace(R.id.detail_container, listFragment, LIST_FRAGMENT)
                .commit();
    }

    protected void playItem(final Item item) {
        try {
            if (appSettings.isDevicePlayback()) {
                if (item.isMusic()) {
                    Uri uri;
                    if (item.isDownloaded()) {
                        uri = getDownload(item);
                    } else {
                        String base = appSettings.getMythTvServicesBaseUrlWithCredentials() + "/Content/";
                        if (appSettings.isMythlingMediaServices()) {
                            uri = Uri.parse(base + "GetMusic?Id=" + item.getId());
                        } else {
                            uri = Uri.parse(base + "GetFile?StorageGroup=" + appSettings.getMusicStorageGroup()
                                    + "&FileName=" + URLEncoder.encode(item.getFilePath(), "UTF-8"));
                        }
                    }
                    if (appSettings.isExternalMusicPlayer()) {
                        Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
                        intent.setDataAndType(uri, "audio/*");
                        startActivity(intent);
                    } else {
                        startProgress();
                        Toast.makeText(getApplicationContext(),
                                getString(R.string.playing) + ": " + item.getTitle(), Toast.LENGTH_LONG).show();
                        Intent playMusic = new Intent(this, MusicPlaybackService.class);
                        playMusic.setData(uri);
                        playMusic.putExtra(MusicPlaybackService.EXTRA_MESSENGER, new Messenger(new Handler() {
                            public void handleMessage(Message msg) {
                                if (msg.what == MusicPlaybackService.MESSAGE_PLAYER_PREPARED) {
                                    stopProgress();
                                    showStopMenuItem(true);
                                }
                            }
                        }));
                        playMusic.setAction(MusicPlaybackService.ACTION_PLAY);
                        startService(playMusic);
                    }
                } else {
                    if (item.isLiveTv()) {
                        TvShow tvShow = (TvShow) item;
                        if (tvShow.getEndTime()
                                .compareTo(Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime()) < 0) {
                            new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
                                    .setTitle(getString(R.string.live_tv))
                                    .setMessage(getString(R.string.show_already_ended_) + "\n" + item.getTitle()
                                            + "\n" + tvShow.getChannelInfo() + "\n" + tvShow.getShowTimeInfo())
                                    .setPositiveButton(R.string.ok, null).show();
                            onResume();
                            return;
                        }
                    }
                    String playbackNetwork = item.isDownloaded() ? PlaybackOptions.NETWORK_DOWNLOAD
                            : appSettings.getPlaybackNetwork();
                    PlaybackOption playbackOption = appSettings.getPlaybackOptions().getOption(item.getType(),
                            item.getFormat(), playbackNetwork);
                    if (appSettings.getMediaSettings().getViewType() != ViewType.list
                            && playbackOption.isAlways()) {
                        // detail or split mode -- no dialog if stream mode pref is set
                        startProgress();
                        if (playbackOption.isHls())
                            new StreamHlsTask(item).execute((URL) null);
                        else
                            playRawVideoStream(item);
                    } else {
                        VideoPlaybackDialog dialog = getVideoPlaybackDialog(item);
                        dialog.show(getFragmentManager(), "StreamVideoDialog");
                    }
                }
            } else {
                // frontend playback
                final FrontendPlayer player;
                if (item.isSearchResult()) {
                    SearchResults searchResults = ((SearchActivity) this).searchResults;
                    String basePath = null;
                    if (item.isMusic())
                        basePath = searchResults.getMusicBase();
                    else if (item.getStorageGroup() == null)
                        basePath = searchResults.getVideoBase();
                    if (basePath != null)
                        player = new SocketFrontendPlayer(appSettings, basePath, item, getCharSet());
                    else
                        player = new ServiceFrontendPlayer(appSettings, item);
                } else {
                    if (item.isMusic() || item.getStorageGroup() == null) // frontend services require storage groups
                        player = new SocketFrontendPlayer(appSettings, mediaList.getBasePath(), item, getCharSet());
                    else
                        player = new ServiceFrontendPlayer(appSettings, item);
                }

                if (player.checkIsPlaying()) {
                    new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
                            .setTitle(getString(R.string.interrupt)).setMessage(getString(R.string.stop_playback_))
                            .setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int which) {
                                    player.stop();
                                    startFrontendPlayback(item, player);
                                }
                            }).setNegativeButton(getString(R.string.no), null).show();
                } else {
                    startFrontendPlayback(item, player);
                }
            }
        } catch (Exception ex) {
            stopProgress();
            onResume();
            Log.e(TAG, ex.getMessage(), ex);
            if (getAppSettings().isErrorReportingEnabled())
                new Reporter(ex).send();
            Toast.makeText(getApplicationContext(), getString(R.string.error_) + ex.toString(), Toast.LENGTH_LONG)
                    .show();
        }
    }

    protected void downloadItem(final Item item) {
        try {
            final URL baseUrl = getAppSettings().getMythTvServicesBaseUrlWithCredentials();

            String fileUrl = baseUrl + "/Content/GetFile?";
            if (item.getStorageGroup() == null)
                fileUrl += "StorageGroup=None&";
            else
                fileUrl += "StorageGroup=" + item.getStorageGroup().getName() + "&";
            fileUrl += "FileName=" + URLEncoder.encode(item.getFilePath(), "UTF-8");

            Uri uri = Uri.parse(fileUrl.toString());
            ProxyInfo proxyInfo = MediaStreamProxy.needsAuthProxy(uri);
            if (proxyInfo != null) {
                // needs proxying to support authentication since DownloadManager doesn't support
                MediaStreamProxy proxy = new MediaStreamProxy(proxyInfo,
                        AuthType.valueOf(appSettings.getMythTvServicesAuthType()));
                proxy.init();
                proxy.start();
                fileUrl = "http://" + proxy.getLocalhost().getHostAddress() + ":" + proxy.getPort() + uri.getPath();
                if (uri.getQuery() != null)
                    fileUrl += "?" + uri.getQuery();
            }

            Log.i(TAG, "Media download URL: " + fileUrl);

            stopProgress();

            DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
            Request request = new Request(Uri.parse(fileUrl));
            request.setTitle(item.getOneLineTitle());
            String downloadFilePath = null;
            try {
                if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
                    File downloadDir = Environment
                            .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
                    if (downloadDir.exists()) {
                        String downloadPath = AppSettings.getExternalStorageDir() + "/";
                        if (getPath() != null && !getPath().isEmpty() && !getPath().equals("/"))
                            downloadPath += getPath() + "/";
                        File destDir = new File(downloadDir + "/" + downloadPath);
                        if (destDir.isDirectory() || destDir.mkdirs()) {
                            downloadFilePath = downloadPath + item.getDownloadFilename();
                            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,
                                    downloadFilePath);
                            request.allowScanningByMediaScanner();
                        }
                    }
                }
            } catch (IllegalStateException ex) {
                // store internal
            } catch (Exception ex) {
                // log, report and store internal
                Log.e(TAG, ex.getMessage(), ex);
                if (getAppSettings().isErrorReportingEnabled())
                    new Reporter(ex).send();
                Toast.makeText(getApplicationContext(), getString(R.string.error_) + ex.toString(),
                        Toast.LENGTH_LONG).show();
            }
            long downloadId = dm.enqueue(request);
            registerDownloadReceiver(item, downloadId);
            Toast.makeText(getApplicationContext(), getString(R.string.downloading_) + item.getOneLineTitle(),
                    Toast.LENGTH_LONG).show();
            getAppData().addDownload(new Download(item.getId(), downloadId, downloadFilePath, new Date()));
            if (item.isRecording() && (mediaList.isMythTv28() || getAppSettings().isMythlingMediaServices()))
                new GetCutListTask((Recording) item, downloadId).execute();
        } catch (Exception ex) {
            stopProgress();
            Log.e(TAG, ex.getMessage(), ex);
            if (getAppSettings().isErrorReportingEnabled())
                new Reporter(ex).send();
            Toast.makeText(getApplicationContext(), getString(R.string.error_) + ex.toString(), Toast.LENGTH_LONG)
                    .show();
        }
    }

    protected Uri getDownload(Item item) throws IOException {
        if (item.getDownloadId() == null)
            return null;
        DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
        Uri uri = dm.getUriForDownloadedFile(item.getDownloadId());
        if (uri == null) {
            item.setDownloadId(null);
            throw new IOException("Cannot find download for: " + item);
        } else {
            return uri;
        }
    }

    /**
     * Starts a transcode without immediately watching.
     */
    protected void transcodeItem(final Item item) {
        new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_info)
                .setTitle(getString(R.string.hls_transcode))
                .setMessage(getString(R.string.begin_transcode) + ":\n" + item.getDialogTitle() + "\n"
                        + getString(R.string.with_video_quality))
                .setPositiveButton(getString(R.string.internal), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        try {
                            new TranscodeVideoTask(item, false)
                                    .execute(getAppSettings().getMythTvServicesBaseUrl());
                            Toast.makeText(getApplicationContext(),
                                    getString(R.string.transcoding_) + item.getOneLineTitle(), Toast.LENGTH_LONG)
                                    .show();
                        } catch (MalformedURLException ex) {
                            stopProgress();
                            Log.e(TAG, ex.getMessage(), ex);
                            if (getAppSettings().isErrorReportingEnabled())
                                new Reporter(ex).send();
                            Toast.makeText(getApplicationContext(), getString(R.string.error_) + ex.toString(),
                                    Toast.LENGTH_LONG).show();
                        }
                    }
                }).setNeutralButton(getString(R.string.external), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        try {
                            new TranscodeVideoTask(item, true).execute(getAppSettings().getMythTvServicesBaseUrl());
                            Toast.makeText(getApplicationContext(),
                                    getString(R.string.transcoding_) + item.getOneLineTitle(), Toast.LENGTH_LONG)
                                    .show();
                        } catch (MalformedURLException ex) {
                            stopProgress();
                            Log.e(TAG, ex.getMessage(), ex);
                            if (getAppSettings().isErrorReportingEnabled())
                                new Reporter(ex).send();
                            Toast.makeText(getApplicationContext(), getString(R.string.error_) + ex.toString(),
                                    Toast.LENGTH_LONG).show();
                        }
                    }
                }).setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        stopProgress();
                        // onResume();
                    }
                }).show();
    }

    protected void deleteRecording(final Recording recording) {
        new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
                .setTitle(getString(R.string.confirm_delete))
                .setMessage(getString(R.string.delete_recording) + ":\n" + recording.getDialogTitle())
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        try {
                            new DeleteRecordingTask(recording).execute(getAppSettings().getMythTvServicesBaseUrl());
                            // remove in this same thread to avoid confusing user
                            boolean removed = mediaList.removeItem(recording);
                            if (!removed) {
                                // must be categorized
                                Category cat = mediaList.getCategory(recording.getTitle());
                                if (cat != null)
                                    removed = cat.removeItem(recording);
                            }
                            if (removed) {
                                mediaList.setCount(mediaList.getCount() - 1);
                                onResume();
                            }
                        } catch (MalformedURLException ex) {
                            stopProgress();
                            Log.e(TAG, ex.getMessage(), ex);
                            if (getAppSettings().isErrorReportingEnabled())
                                new Reporter(ex).send();
                            Toast.makeText(getApplicationContext(), getString(R.string.error_) + ex.toString(),
                                    Toast.LENGTH_LONG).show();
                        }
                    }
                }).setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        stopProgress();
                    }
                }).show();
    }

    protected void goListView() {
        findViewById(R.id.detail_container).setVisibility(View.GONE);
        if (!getAppSettings().isTv() && getListView() != null && getListView().getCheckedItemPosition() >= 0) {
            getListView().setItemChecked(getListView().getCheckedItemPosition(), false);
        }
    }

    protected void goDetailView() {
        if (mediaList.getMediaType() == MediaType.recordings
                && getAppSettings().getMediaSettings().getSortType() == SortType.byTitle) {
            getAppSettings().clearCache(); // refresh since we're switching to flattened hierarchy
            selItemIndex = 0;
        }

        Uri uri = new Uri.Builder().path(getPath()).build();
        Intent intent = new Intent(Intent.ACTION_VIEW, uri, getApplicationContext(), MediaPagerActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra(SEL_ITEM_INDEX, selItemIndex);
        startActivity(intent);
    }

    public void goMainActivity() {
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }

    protected void goSplitView() {
        findViewById(R.id.detail_container).setVisibility(View.VISIBLE);
    }

    public void sort() throws IOException, JSONException, ParseException {
        startProgress();
        refreshMediaList();
    }

    private void startFrontendPlayback(Item item, final FrontendPlayer player) {
        if (item.isLiveTv()) {
            new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle(item.getTitle())
                    .setMessage(getString(R.string.todo_frontend_live_tv))
                    .setPositiveButton(getString(R.string.ok), null).show();
        } else {
            try {
                player.play();

                // reset progress
                count = 0;
                // prepare for a progress bar dialog
                countdownDialog = new ProgressDialog(this);
                countdownDialog.setCancelable(true);
                String msg = getString(R.string.playing) + " " + item.getTitle();
                if (item.getListSubText() != null)
                    msg += "\n" + item.getListSubText();
                countdownDialog.setMessage(msg);
                countdownDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                countdownDialog.setProgressPercentFormat(null);
                countdownDialog.setProgressNumberFormat(null);
                countdownDialog.setMax(10);
                countdownDialog.setCancelable(true);
                countdownDialog.setButton(Dialog.BUTTON_POSITIVE, getString(R.string.ok),
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                                stopTimer();
                            }
                        });
                countdownDialog.setButton(Dialog.BUTTON_NEGATIVE, getString(R.string.stop),
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                player.stop();
                                dialog.dismiss();
                                stopTimer();
                            }
                        });
                countdownDialog.setCanceledOnTouchOutside(true);
                countdownDialog.show();
                countdownDialog.setProgress(10);

                tick();
            } catch (Exception ex) {
                Log.e(TAG, ex.getMessage(), ex);
                if (getAppSettings().isErrorReportingEnabled())
                    new Reporter(ex).send();
                stopTimer();
            }
        }
    }

    @Override
    protected void onStop() {
        if (countdownDialog != null && countdownDialog.isShowing())
            countdownDialog.dismiss();
        stopTimer();
        super.onStop();
    }

    private void stopTimer() {
        if (timer != null)
            timer.cancel();
        count = 0;
    }

    private void tick() {
        if (timer != null)
            timer.cancel();
        timer = new Timer();
        timer.schedule(new TimerTask() {
            public void run() {
                countdownDialog.setProgress(10 - count);
                if (count == 10) {
                    countdownDialog.dismiss();
                    stopTimer();
                } else {
                    count++;
                    tick();
                }
            }
        }, 1000);
    }

    protected void refreshMediaList() {
        try {
            selItemIndex = 0;
            currentTop = 0;
            topOffset = 0;
            getAppSettings().clearMediaSettings(); // in case prefs changed
            if (getAppSettings().getMediaSettings().getType() == MediaType.movies && !supportsMovies())
                getAppSettings().setMediaType(MediaType.valueOf(AppSettings.DEFAULT_MEDIA_TYPE));
            if (getAppSettings().getMediaSettings().getType() == MediaType.tvSeries && !supportsTvSeries())
                getAppSettings().setMediaType(MediaType.valueOf(AppSettings.DEFAULT_MEDIA_TYPE));
            if (getAppSettings().getMediaSettings().getType() == MediaType.music && !supportsMusic())
                getAppSettings().setMediaType(MediaType.valueOf(AppSettings.DEFAULT_MEDIA_TYPE));

            new RefreshTask().execute(getAppSettings().getUrls(getAppSettings().getMediaListUrl()));
        } catch (Exception ex) {
            throw new RuntimeException(ex.getMessage(), ex);
        }
    }

    protected void handleEmptyMediaList() {
        if (isSplitView()) {
            try {
                showSubListPane(null);
            } catch (IllegalStateException ex) {
                // "Can not perform this action after onSaveInstanceState"
                Log.e(TAG, ex.getMessage(), ex);
                if (getAppSettings().isErrorReportingEnabled())
                    new Reporter(ex).send();
            }
        }
        if (getAppSettings().isTv()) {
            // empty list - set focus on action bar
            setFocusOnActionBar();
        }
    }

    protected void setFocusOnActionBar() {
        int actionBarResId = getResources().getIdentifier("action_bar_container", "id", "android");
        View actionBarView = getWindow().getDecorView().findViewById(actionBarResId);
        if (actionBarView != null)
            actionBarView.requestFocus();
    }

    protected void updateActionMenu() {
        showMoviesMenuItem(supportsMovies());
        showTvSeriesMenuItem(supportsTvSeries());
        showMusicMenuItem(supportsMusic());
        showSortMenu(supportsSort());
        showViewMenu(supportsViewMenu());
        showSearchMenu(supportsSearch());

        if (mediaMenuItem != null)
            mediaMenuItem.setTitle(Localizer.getMediaLabel(getAppSettings().getMediaSettings().getType()) + " ("
                    + mediaList.getCount() + ")");

        showStopMenuItem(isPlayingMusic());
    }

    protected void populate() throws IOException, JSONException, ParseException {
        // default does nothing
    }

    private boolean isPlayingMusic() {
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (MusicPlaybackService.class.getName().equals(service.service.getClassName()))
                return true;
        }
        return false;
    }

    private class RefreshTask extends AsyncTask<URL, Integer, Long> {
        private String mediaListJson;
        private String storageGroupsJson;

        private Exception ex;
        private String msg;

        protected Long doInBackground(URL... urls) {
            Long before = null;
            try {
                MediaSettings mediaSettings = getAppSettings().getMediaSettings();

                HttpHelper downloader = getAppSettings().getMediaListDownloader(urls);
                before = Long.valueOf(System.currentTimeMillis());
                byte[] bytes = downloader.get();
                mediaListJson = new String(bytes, downloader.getCharSet());
                if (mediaListJson.startsWith("<")) {
                    // just display html
                    ex = new IOException(mediaListJson);
                    return -1L;
                }

                URL sgUrl = new URL(getAppSettings().getMythTvServicesBaseUrl() + "/Myth/GetStorageGroupDirs");
                HttpHelper sgDownloader = getAppSettings().getMediaListDownloader(getAppSettings().getUrls(sgUrl));
                storageGroupsJson = new String(sgDownloader.get());
                storageGroups = new MythTvParser(getAppSettings(), storageGroupsJson).parseStorageGroups();
                if (mediaSettings.getType() == MediaType.music
                        && storageGroups.get(appSettings.getMusicStorageGroup()) == null
                        && !appSettings.isMythlingMediaServices()) {
                    msg = getString(R.string.music_storage_group_required);
                    return -1L;
                }

                MediaListParser mediaListParser = getAppSettings().getMediaListParser(mediaListJson);
                if (getAppSettings().isMythlingMediaServices()) {
                    mediaList = mediaListParser.parseMediaList(mediaSettings.getType(), storageGroups);
                } else {
                    boolean hasMediaStorageGroup = mediaSettings.getType() == MediaType.recordings
                            || mediaSettings.getType() == MediaType.liveTv
                            || (mediaSettings.getType() != MediaType.music
                                    && storageGroups.get(appSettings.getVideoStorageGroup()) != null);
                    if (hasMediaStorageGroup) {
                        mediaList = ((MythTvParser) mediaListParser).parseMediaList(mediaSettings.getType(),
                                storageGroups);
                    } else {
                        // no storage group for media type
                        URL baseUrl = getAppSettings().getMythTvServicesBaseUrl();
                        String basePath = null;
                        if (mediaSettings.getType() == MediaType.videos
                                || mediaSettings.getType() == MediaType.movies
                                || mediaSettings.getType() == MediaType.tvSeries) {
                            // handle videos by getting the base path setting
                            downloader = getAppSettings().getMediaListDownloader(
                                    getAppSettings().getUrls(new URL(baseUrl + "/Myth/GetHostName")));
                            String hostName = new MythTvParser(getAppSettings(), new String(downloader.get()))
                                    .parseString();
                            String key = "VideoStartupDir";
                            downloader = getAppSettings().getMediaListDownloader(getAppSettings().getUrls(
                                    new URL(baseUrl + "/Myth/GetSetting?Key=" + key + "&HostName=" + hostName)));
                            basePath = new MythTvParser(getAppSettings(), new String(downloader.get()))
                                    .parseMythTvSetting(key);
                            if (basePath == null) {
                                // try without host name
                                downloader = getAppSettings().getMediaListDownloader(
                                        getAppSettings().getUrls(new URL(baseUrl + "/Myth/GetSetting?Key=" + key)));
                                basePath = new MythTvParser(getAppSettings(), new String(downloader.get()))
                                        .parseMythTvSetting(key);
                            }
                        }
                        mediaList = ((MythTvParser) mediaListParser).parseMediaList(mediaSettings.getType(),
                                storageGroups, basePath);
                    }
                }

                mediaList.setCharSet(downloader.getCharSet());

                if (mediaList.supportsTranscode() && getAppSettings().isRetrieveTranscodeStatuses())
                    updateTranscodeStatuses(mediaList.getAllItems());

                return 0L;
            } catch (SocketTimeoutException ex) {
                long secs = (System.currentTimeMillis() - before) / 1000;
                String msg = getString(R.string.socket_timeout_after_) + secs + " " + getString(R.string.seconds)
                        + ". " + getString(R.string.socket_timeout_suggest);
                this.ex = new SocketTimeoutException(msg);
                // log the original exception to avoid losing cause,
                // since SocketTimeoutException(String, Throwable) is "internal use only"
                Log.e(TAG, ex.getMessage(), ex);
                if (getAppSettings().isErrorReportingEnabled())
                    new Reporter(ex).send();
                return -1L;

            } catch (Exception ex) {
                this.ex = ex;
                Log.e(TAG, ex.getMessage(), ex);
                if (getAppSettings().isErrorReportingEnabled())
                    new Reporter(ex).send();
                return -1L;
            }
        }

        protected void onPostExecute(Long result) {
            if (result != 0L) {
                stopProgress();
                new Handler().post(new Runnable() {
                    public void run() {
                        handleEmptyMediaList();
                    }
                });
                if (ex != null)
                    Toast.makeText(getApplicationContext(), ex.toString(), Toast.LENGTH_LONG).show();
                else if (msg != null)
                    Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
            } else {
                AppData appData = new AppData(getApplicationContext());
                appData.setMediaList(mediaList);
                appData.setStorageGroups(storageGroups);
                setAppData(appData);
                setMediaType(appData.getMediaList().getMediaType());
                getAppSettings().setLastLoad(System.currentTimeMillis());

                try {
                    mediaList.setDownloads(appData.getDownloads());
                    appData.writeStorageGroups(storageGroupsJson);
                    appData.writeMediaList(mediaListJson);
                    stopProgress();
                    populate();
                } catch (Exception ex) {
                    stopProgress();
                    Log.e(TAG, ex.getMessage(), ex);
                    if (getAppSettings().isErrorReportingEnabled())
                        new Reporter(ex).send();
                }
            }
        }
    }

    private class StreamHlsTask extends AsyncTask<URL, Integer, Long> {
        private Item item;
        private LiveStreamInfo streamInfo;
        private Exception ex;

        public StreamHlsTask(Item item) {
            this.item = item;
        }

        protected Long doInBackground(URL... urls) {
            try {
                Transcoder transcoder = new Transcoder(getAppSettings());

                // TODO: do this retry for tv playback
                int ct = 0;
                int maxTries = 3;
                // empty relative url i think means myth has not started transcoding
                while ((streamInfo == null || streamInfo.getRelativeUrl().isEmpty()) && ct < maxTries) {
                    transcoder.beginTranscode(item);
                    streamInfo = transcoder.getStreamInfo();
                    ct++;
                    Thread.sleep(1000);
                }

                if (streamInfo == null || streamInfo.getRelativeUrl().isEmpty())
                    throw new IOException(getString(R.string.transcoding_not_started));

                transcoder.waitAvailable();

                return 0L;
            } catch (Exception ex) {
                this.ex = ex;
                Log.e(TAG, ex.getMessage(), ex);
                if (getAppSettings().isErrorReportingEnabled())
                    new Reporter(ex).send();
                return -1L;
            }
        }

        protected void onPostExecute(Long result) {
            if (result != 0L) {
                stopProgress();
                if (ex != null)
                    Toast.makeText(getApplicationContext(), ex.toString(), Toast.LENGTH_LONG).show();
                onResume();
            } else {
                try {
                    playLiveStream(streamInfo, item);
                } catch (Exception ex) {
                    Log.e(TAG, ex.getMessage(), ex);
                    if (getAppSettings().isErrorReportingEnabled())
                        new Reporter(ex).send();
                    Toast.makeText(getApplicationContext(), ex.toString(), Toast.LENGTH_LONG).show();
                }
            }
        }
    }

    protected class TranscodeVideoTask extends AsyncTask<URL, Integer, Long> {
        private Item item;
        private boolean externalVideoQuality; // false means internal
        private Exception ex;

        public TranscodeVideoTask(Item item, boolean externalVideoQuality) {
            this.item = item;
            this.externalVideoQuality = externalVideoQuality;
        }

        protected Long doInBackground(URL... urls) {
            try {
                Transcoder transcoder = new Transcoder(getAppSettings());
                transcoder.beginTranscode(item, externalVideoQuality ? AppSettings.EXTERNAL_VIDEO_QUALITY
                        : AppSettings.INTERNAL_VIDEO_QUALITY);
                return 0L;
            } catch (Exception ex) {
                this.ex = ex;
                Log.e(TAG, ex.getMessage(), ex);
                if (getAppSettings().isErrorReportingEnabled())
                    new Reporter(ex).send();
                return -1L;
            }
        }

        protected void onPostExecute(Long result) {
            stopProgress();
            if (result != 0L) {
                if (ex != null)
                    Toast.makeText(getApplicationContext(), ex.toString(), Toast.LENGTH_LONG).show();
                onResume();
            }
        }
    }

    /**
     * Uses internal video player.
     */
    protected class PlayWithCutListTask extends GetCutListTask {
        private Uri playbackUri;
        private PlaybackOption playbackOption;

        public PlayWithCutListTask(Uri uri, Recording rec, PlaybackOption opt) {
            super(rec);
            this.playbackUri = uri;
            this.playbackOption = opt;
        }

        protected void onPostExecute(Long result) {
            super.onPostExecute(result);
            // play regardless of cutlist success
            Intent videoIntent = new Intent(Intent.ACTION_VIEW);
            videoIntent.setDataAndType(playbackUri, "video/*");
            videoIntent.setClass(getApplicationContext(), VideoPlayerActivity.class);
            videoIntent.putExtra(VideoPlayerActivity.PLAYER, playbackOption.getPlayer());
            if (getRecording().isLengthKnown())
                videoIntent.putExtra(VideoPlayerActivity.ITEM_LENGTH_SECS, getRecording().getLength());
            String streamingAuthType = getAppSettings().getMythTvServicesAuthType();
            if (streamingAuthType != AuthType.None.toString())
                videoIntent.putExtra(VideoPlayerActivity.AUTH_TYPE, streamingAuthType);
            if (getRecording().hasCutList())
                videoIntent.putExtra(VideoPlayerActivity.ITEM_CUT_LIST, getRecording().getCutList());
            startActivity(videoIntent);
        }
    }

    protected class GetCutListTask extends AsyncTask<URL, Integer, Long> {
        private Recording recording;

        protected Recording getRecording() {
            return recording;
        }

        private long downloadId;

        public GetCutListTask(Recording rec, long downloadId) {
            this.recording = rec;
            this.downloadId = downloadId;
        }

        public GetCutListTask(Recording rec) {
            this.recording = rec;
        }

        protected Long doInBackground(URL... urls) {
            try {
                AppSettings appSettings = getAppSettings();
                URL url = new URL(appSettings.getCutListBaseUrl() + "ChanId=" + recording.getChannelId()
                        + "&StartTime=" + recording.getStartTimeParam());
                boolean mythlingServices = appSettings.isMythlingMediaServices();
                String authType = mythlingServices ? appSettings.getBackendWebAuthType()
                        : appSettings.getMythTvServicesAuthType();
                HttpHelper downloader = new HttpHelper(appSettings.getUrls(url), authType, appSettings.getPrefs());
                if (mythlingServices)
                    downloader.setCredentials(appSettings.getMythlingServicesUser(),
                            appSettings.getMythlingServicesPassword());
                else
                    downloader.setCredentials(appSettings.getMythTvServicesUser(),
                            appSettings.getMythTvServicesPassword());

                String cutListJson = new String(downloader.get());
                // use mythtv parser since services are compatible
                ArrayList<Cut> cutList = Cut.parseCutList(new JSONObject(cutListJson));
                if (!cutList.isEmpty())
                    recording.setCutList(cutList);
                return 0L;
            } catch (Exception ex) {
                Log.e(TAG, ex.getMessage(), ex);
                if (getAppSettings().isErrorReportingEnabled())
                    new Reporter(ex).send();
                return -1L;
            }
        }

        protected void onPostExecute(Long result) {
            stopProgress();
            if (result != 0L) {
                Toast.makeText(getApplicationContext(), getString(R.string.unable_to_retrieve_cutlist),
                        Toast.LENGTH_LONG).show();
            } else {
                try {
                    if (downloadId > 0 && recording.getCutList() != null)
                        getAppData().addDownloadCutList(recording.getId(), recording.getCutList());
                } catch (Exception ex) {
                    Toast.makeText(getApplicationContext(), ex.getMessage(), Toast.LENGTH_LONG).show();
                    Log.e(TAG, ex.getMessage(), ex);
                    if (getAppSettings().isErrorReportingEnabled())
                        new Reporter(ex).send();
                }
            }
        }
    }

    protected class DeleteRecordingTask extends AsyncTask<URL, Integer, Long> {
        private Recording recording;
        private Exception ex;

        public DeleteRecordingTask(Recording recording) {
            this.recording = recording;
        }

        protected Long doInBackground(URL... urls) {
            try {
                Recorder recorder = new Recorder(getAppSettings());
                recorder.deleteRecording(recording);
                return 0L;
            } catch (Exception ex) {
                this.ex = ex;
                Log.e(TAG, ex.getMessage(), ex);
                if (getAppSettings().isErrorReportingEnabled())
                    new Reporter(ex).send();
                return -1L;
            }
        }

        protected void onPostExecute(Long result) {
            stopProgress();
            if (result != 0L) {
                if (ex != null)
                    Toast.makeText(getApplicationContext(), ex.toString(), Toast.LENGTH_LONG).show();
                onResume();
            }
        }
    }

    protected class StreamTvTask extends AsyncTask<URL, Integer, Long> {
        private TvShow tvShow;
        private Recording recording;
        private LiveStreamInfo streamInfo;
        private boolean raw;
        private Exception ex;

        public StreamTvTask(TvShow tvShow, boolean raw) {
            this.tvShow = tvShow;
            this.raw = raw;
        }

        protected Long doInBackground(URL... urls) {
            try {
                Recorder recorder = new Recorder(getAppSettings(), storageGroups);
                boolean recordAvail = recorder.scheduleRecording(tvShow);

                if (!recordAvail)
                    recorder.waitAvailable();

                recording = recorder.getRecording();

                if (!raw) {
                    tvShow.setStorageGroup(recorder.getRecording().getStorageGroup());
                    Transcoder transcoder = new Transcoder(getAppSettings());
                    boolean streamAvail = transcoder.beginTranscode(recorder.getRecording());

                    streamInfo = transcoder.getStreamInfo();

                    if (!streamAvail)
                        transcoder.waitAvailable(); // this is the long pole
                }

                return 0L;
            } catch (Exception ex) {
                this.ex = ex;
                Log.e(TAG, ex.getMessage(), ex);
                if (getAppSettings().isErrorReportingEnabled())
                    new Reporter(ex).send();
                return -1L;
            }
        }

        protected void onPostExecute(Long result) {
            stopProgress();
            if (result != 0L) {
                if (ex instanceof AllTunersInUseException) {
                    new AlertDialog.Builder(MediaActivity.this).setIcon(android.R.drawable.ic_dialog_alert)
                            .setTitle(getString(R.string.recording_conflict))
                            .setMessage(getString(R.string.tuners_in_use_) + ex.getMessage())
                            .setPositiveButton(R.string.ok, null).show();
                } else {
                    if (ex != null)
                        Toast.makeText(getApplicationContext(), ex.toString(), Toast.LENGTH_LONG).show();
                    onResume();
                }
            } else {
                try {
                    recording.setForLiveTv(true); // for playbackOption purposes
                    if (raw)
                        playRawVideoStream(recording);
                    else
                        playLiveStream(streamInfo, recording);
                } catch (Exception ex) {
                    Log.e(TAG, ex.getMessage(), ex);
                    if (getAppSettings().isErrorReportingEnabled())
                        new Reporter(ex).send();
                    Toast.makeText(getApplicationContext(), ex.toString(), Toast.LENGTH_LONG).show();
                }
            }
        }
    }

    /**
     * Must be called from background thread
     */
    protected void updateTranscodeStatuses(List<Item> items) throws IOException {
        URL tcUrl = new URL(appSettings.getMythTvServicesBaseUrl() + "/Content/GetLiveStreamList");
        HttpHelper tcDownloader = new HttpHelper(appSettings.getUrls(tcUrl),
                appSettings.getMythTvServicesAuthType(), appSettings.getPrefs());
        tcDownloader.setCredentials(appSettings.getMythTvServicesUser(), appSettings.getMythTvServicesPassword());
        String liveStreamJson = new String(tcDownloader.get(), "UTF-8");
        List<LiveStreamInfo> liveStreams = new MythTvParser(appSettings, liveStreamJson).parseStreamInfoList();

        // ugly performance-optimized code follows
        long startTime = System.currentTimeMillis();
        int desiredRes = getAppSettings().getVideoRes();
        int desiredVidBr = getAppSettings().getVideoBitrate();
        int desiredAudBr = getAppSettings().getAudioBitrate();
        int[] resValues = getAppSettings().getVideoResValues();
        int[] vidBrValues = getAppSettings().getVideoBitrateValues();
        int[] audBrValues = getAppSettings().getAudioBitrateValues();
        for (Item item : items) {
            List<String> sgPaths = item.getStorageGroupPaths();
            for (LiveStreamInfo liveStream : liveStreams) {
                if (liveStream.isCompleted()) {
                    if (sgPaths != null) {
                        for (String sgPath : sgPaths) {
                            if (liveStream.getFile().equals(sgPath)) {
                                if (liveStream.matchesQuality(desiredRes, desiredVidBr, desiredAudBr, resValues,
                                        vidBrValues, audBrValues)) {
                                    item.setTranscoded(true);
                                    break;
                                }
                            }
                        }
                    }
                }
                if (item.isTranscoded())
                    break; // match already found
            }
        }

        if (BuildConfig.DEBUG)
            Log.d(TAG, " -> transcode status check time: " + (System.currentTimeMillis() - startTime) + " ms");
    }

    /**
     * Requires MythTV content.cpp patch to work without storage groups.
     */
    private void playRawVideoStream(Item item) throws IOException, JSONException {

        Uri uri;
        if (item.isDownloaded()) {
            uri = getDownload(item);
        } else {
            URL baseUrl = getAppSettings().getMythTvServicesBaseUrlWithCredentials();
            String fileUrl = baseUrl + "/Content/GetFile?";
            if (item.getStorageGroup() == null)
                fileUrl += "StorageGroup=None&";
            else
                fileUrl += "StorageGroup=" + item.getStorageGroup().getName() + "&";
            fileUrl += "FileName=" + URLEncoder.encode(item.getFilePath(), "UTF-8");

            uri = Uri.parse(fileUrl);
        }

        PlaybackOption playbackOption = getPlaybackOption(item, PlaybackOptions.STREAM_FILE);
        boolean isExternalPlayer = playbackOption.isAppPlayer();
        if (!isExternalPlayer && item.isRecording() && isUseCutList()) {
            new PlayWithCutListTask(uri, (Recording) item, playbackOption).execute();
        } else {
            stopProgress();
            Intent videoIntent = new Intent(Intent.ACTION_VIEW);
            videoIntent.setDataAndType(uri, "video/*");
            if (!isExternalPlayer) {
                videoIntent.setClass(getApplicationContext(), VideoPlayerActivity.class);
                videoIntent.putExtra(VideoPlayerActivity.PLAYER, playbackOption.getPlayer());
                if (!item.isDownloaded()) {
                    if (item.isLengthKnown())
                        videoIntent.putExtra(VideoPlayerActivity.ITEM_LENGTH_SECS, item.getLength());
                    String streamingAuthType = getAppSettings().getMythTvServicesAuthType();
                    if (streamingAuthType != AuthType.None.toString())
                        videoIntent.putExtra(VideoPlayerActivity.AUTH_TYPE, streamingAuthType);
                }
            }
            startActivity(videoIntent);
        }
    }

    protected void playLiveStream(LiveStreamInfo streamInfo, Item item) throws IOException, JSONException {
        String streamUrl = appSettings.getMythTvServicesBaseUrlWithCredentials() + streamInfo.getRelativeUrl();

        // avoid retrieving unnecessary audio-only streams
        int lastDot = streamUrl.lastIndexOf('.');
        streamUrl = streamUrl.substring(0, lastDot) + ".av" + streamUrl.substring(lastDot);

        stopProgress();

        PlaybackOption playbackOption = getPlaybackOption(item, PlaybackOptions.STREAM_HLS);
        boolean isExternalPlayer = playbackOption.isAppPlayer();
        if (!isExternalPlayer && item.isRecording() && isUseCutList()) {
            new PlayWithCutListTask(Uri.parse(streamUrl), (Recording) item, playbackOption).execute();
        } else {
            Intent videoIntent = new Intent(Intent.ACTION_VIEW);
            videoIntent.setDataAndType(Uri.parse(streamUrl), "video/*");
            if (!playbackOption.isAppPlayer()) {
                videoIntent.setClass(getApplicationContext(), VideoPlayerActivity.class);
                videoIntent.putExtra(VideoPlayerActivity.PLAYER, playbackOption.getPlayer());
                if (item.isLengthKnown())
                    videoIntent.putExtra(VideoPlayerActivity.ITEM_LENGTH_SECS, item.getLength());
            }
            startActivity(videoIntent);
        }
    }

    boolean isUseCutList() {
        boolean useCutList = false;
        if (isTv() && mediaList != null)
            useCutList = mediaList.isMythTv28() || getAppSettings().isMythlingMediaServices();
        else // pref may be invalid if not using mythtv 0.28, but just let user be warned
            useCutList = !getAppSettings().getAutoSkip().equals(AppSettings.AUTO_SKIP_OFF);
        return useCutList;
    }

    void initListViewOnItemClickListener() {
        if (getListables().size() > 0) {
            getListView().setOnItemClickListener(new OnItemClickListener() {
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    currentTop = getListView().getFirstVisiblePosition();
                    View topV = getListView().getChildAt(0);
                    topOffset = (topV == null) ? 0 : topV.getTop();
                    selItemIndex = position;
                    Listable listable = getListables().get(position);
                    if (listable instanceof Item) {
                        Item item = (Item) listable;
                        if (isSplitView()) {
                            getListAdapter().setSelection(selItemIndex);
                            getListView().setItemChecked(selItemIndex, true);
                            if (appSettings.isTv()) {
                                // play the item since already selected
                                playItem(item);
                            } else {
                                showItemInDetailPane(position, true);
                            }
                        } else {
                            playItem(item);
                        }
                    } else {
                        // must be category
                        Category category = (Category) listable;
                        String cat = category.getName();
                        String catpath = "".equals(getPath()) ? cat : getPath() + "/" + cat;
                        if (isSplitView()) {
                            getListAdapter().setSelection(selItemIndex);
                            getListView().setItemChecked(position, true);
                            showSubListPane(catpath, 0);
                        } else {
                            Uri uri = new Uri.Builder().path(catpath).build();
                            startActivity(new Intent(Intent.ACTION_VIEW, uri, getApplicationContext(),
                                    MediaListActivity.class));
                        }
                    }
                }
            });
        }
    }

    void initListViewOnItemSelectedListener() {
        if (getListables().size() > 0 && getAppSettings().isTv()) {
            getListView().setOnItemSelectedListener(new OnItemSelectedListener() {
                public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                    Listable sel = getListables().get(position);
                    getListView().setItemChecked(selItemIndex, false);
                    selItemIndex = position;
                    if (sel instanceof Item) {
                        showItemInDetailPane(position, false);
                    } else {
                        boolean grab = isSplitView() && getIntent().getBooleanExtra(GRAB_FOCUS, false);
                        showSubListPane(getPath() + "/" + ((Category) sel).getName(), grab);
                    }
                    getIntent().putExtra(GRAB_FOCUS, false);
                }

                public void onNothingSelected(AdapterView<?> parent) {
                }
            });
            getListView().setOnFocusChangeListener(new OnFocusChangeListener() {
                public void onFocusChange(View v, boolean hasFocus) {
                    getListView().setItemChecked(selItemIndex, !hasFocus);
                }
            });
        }
    }

    void initListViewDpadHandler() {
        if (getAppSettings().isTv()) {
            getListView().setOnKeyListener(new OnKeyListener() {
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    if (event.getAction() == KeyEvent.ACTION_DOWN) {
                        if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                            Listable listable = getListables().get(getSelItemIndex());
                            if (listable instanceof Category) {
                                getListView().performItemClick(getListAdapter().getView(selItemIndex, null, null),
                                        selItemIndex, getListAdapter().getItemId(selItemIndex));
                                return true;
                            } else if (isSplitView()) {
                                getListAdapter().setSelection(selItemIndex);
                                getListView().setItemChecked(selItemIndex, true);
                                showItemInDetailPane(selItemIndex, true);
                                return true;
                            }
                        } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT && !"".equals(getPath())) {
                            onBackPressed();
                            return true;
                        }
                    }
                    return false;
                }
            });
        }
    }

    void initSplitView() {
        if (selItemIndex != -1 && getListables().size() > 0) {
            getListView().setItemChecked(selItemIndex, true);
            Listable preSel = getListables().get(selItemIndex);
            if (preSel instanceof Item)
                showItemInDetailPane(selItemIndex);
            else
                showSubListPane(getPath() + "/" + ((Category) preSel).getName());
        }
    }

    protected void setPathFromIntent() {
        try {
            String newPath = getIntent().getDataString();
            setPath(newPath == null ? "" : URLDecoder.decode(newPath, "UTF-8"));
        } catch (UnsupportedEncodingException ex) {
            Log.e(TAG, ex.getMessage(), ex);
            if (getAppSettings().isErrorReportingEnabled())
                new Reporter(ex).send();
            Toast.makeText(getApplicationContext(), getString(R.string.error_) + ex.toString(), Toast.LENGTH_LONG)
                    .show();
            setPath(""); // TODO correct?
        }
    }

    protected PlaybackOption getPlaybackOption(Item item, String streamType) throws IOException, JSONException {
        String playbackNetwork = item.isDownloaded() ? PlaybackOptions.NETWORK_DOWNLOAD
                : appSettings.getPlaybackNetwork();
        MediaType type = item.getType();
        String format = item.getFormat();
        if (item instanceof Recording && ((Recording) item).isForLiveTv()) {
            type = MediaType.liveTv;
            format = TvShow.LIVE_TV_FORMAT;
        }
        return appSettings.getPlaybackOptions().getOption(type, format, playbackNetwork, streamType);
    }

    protected VideoPlaybackDialog getVideoPlaybackDialog(Item item) {
        final VideoPlaybackDialog dialog = new VideoPlaybackDialog(getAppSettings(), item);
        dialog.setListener(new PlaybackDialogListener() {
            public void onClickPlay(final Item item, final PlaybackOption option) {
                if (PlaybackOptions.PLAYER_LIBVLC.equals(option.getPlayer())
                        && !getAppSettings().isCpuCompatibleWithLibVlcPlayer()
                        && !getAppSettings().isIgnoreLibVlcCpuCompatibility()) {
                    PrefDismissDialog dlg = new PrefDismissDialog(getAppSettings(),
                            getString(R.string.title_libvlc_playback),
                            getString(R.string.unsupported_cpu_for_libvlc),
                            AppSettings.IGNORE_LIBVLC_CPU_COMPATIBILITY);
                    dlg.setListener(new PrefDismissListener() {
                        public String getPositiveBtnLabel() {
                            return getString(R.string.play);
                        }

                        public void onClickPositive() {
                            startVideoPlayback(item, option);
                        }

                        public void onClickNegative() {
                            getVideoPlaybackDialog(item).show(getFragmentManager(), "StreamVideoDialog");
                            return;
                        }
                    });
                    dlg.setCheckboxText(getString(R.string.always_ignore_libvlc_cpu_compatibility));
                    dlg.setDefaultChecked(false);
                    dlg.show(getFragmentManager());
                } else {
                    startVideoPlayback(item, option);
                }
            }

            public void onClickCancel() {
                stopProgress();
                // onResume();
            }
        });
        return dialog;
    }

    private void startVideoPlayback(Item item, PlaybackOption option) {
        startProgress();
        if (item.isLiveTv()) {
            new StreamTvTask((TvShow) item, !option.isHls()).execute();
        } else {
            if (option.isHls()) {
                new StreamHlsTask(item).execute();
            } else {
                try {
                    playRawVideoStream(item);
                } catch (Exception ex) {
                    stopProgress();
                    onResume();
                    Log.e(TAG, ex.getMessage(), ex);
                    if (getAppSettings().isErrorReportingEnabled())
                        new Reporter(ex).send();
                    Toast.makeText(getApplicationContext(), getString(R.string.error_) + ex.toString(),
                            Toast.LENGTH_LONG).show();
                }
            }
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        if (v == getListView()) {
            AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
            Listable listable = (Listable) getListView().getItemAtPosition(info.position);
            if (listable instanceof Item) {
                Item item = (Item) listable;
                menu.setHeaderTitle(item.getDialogTitle());
                if (isSplitView()) {
                    getListView().performItemClick(getListView().getChildAt(info.position), info.position,
                            getListView().getAdapter().getItemId(info.position));
                } else {
                    getListView().setItemChecked(info.position, true);
                }
                SparseArray<String> menuItems = getLongClickMenuItems(item);
                for (int i = 0; i < menuItems.size(); i++) {
                    int id = menuItems.keyAt(i);
                    menu.add(MEDIA_ACTIVITY_CONTEXT_MENU_GROUP_ID, id, id, menuItems.get(id));
                }
            }
        }
    }

    SparseArray<String> getLongClickMenuItems(Item item) {
        String[] menuItems = getResources().getStringArray(R.array.item_long_click_menu);
        SparseArray<String> relevantItems = new SparseArray<String>();
        for (int i = 0; i < menuItems.length; i++) {
            if (i == LONG_CLICK_MENU_PLAY)
                relevantItems.put(i, menuItems[i]);
            else if (i == LONG_CLICK_MENU_TRANSCODE && !item.isMusic())
                relevantItems.put(i, menuItems[i]);
            else if (i == LONG_CLICK_MENU_DOWNLOAD && !item.isLiveTv())
                relevantItems.put(i, menuItems[i]);
            else if (i == LONG_CLICK_MENU_DELETE && item.isRecording())
                relevantItems.put(i, menuItems[i]);
        }
        return relevantItems;
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        if (item.getGroupId() == MEDIA_ACTIVITY_CONTEXT_MENU_GROUP_ID) {
            AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
            if (item.getItemId() == LONG_CLICK_MENU_PLAY) {
                Item it = (Item) getListView().getItemAtPosition(info.position);
                playItem(it);
                return true;
            } else if (item.getItemId() == LONG_CLICK_MENU_TRANSCODE) {
                Item it = (Item) getListView().getItemAtPosition(info.position);
                transcodeItem(it);
                return true;
            } else if (item.getItemId() == LONG_CLICK_MENU_DOWNLOAD) {
                Item it = (Item) getListView().getItemAtPosition(info.position);
                downloadItem(it);
                return true;
            } else if (item.getItemId() == LONG_CLICK_MENU_DELETE) {
                Recording rec = (Recording) getListView().getItemAtPosition(info.position);
                int size = getListables().size();
                if (size == 1 || size == info.position + 1)
                    setSelItemIndex(getSelItemIndex() - 1);
                deleteRecording(rec);
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_DOWN && getAppSettings().isTv()) {
            if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
                setFocusOnActionBar();
                return true;
            }
        }
        return super.dispatchKeyEvent(event);
    }

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putInt(SEL_ITEM_INDEX, selItemIndex);
        savedInstanceState.putInt(CURRENT_TOP, currentTop);
        savedInstanceState.putInt(TOP_OFFSET, topOffset);
    }

    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        selItemIndex = savedInstanceState.getInt(SEL_ITEM_INDEX, 0);
        currentTop = savedInstanceState.getInt(CURRENT_TOP, 0);
        topOffset = savedInstanceState.getInt(TOP_OFFSET, 0);
    }

    protected void startProgress() {
        progressBar.setVisibility(View.VISIBLE);
    }

    protected void stopProgress() {
        progressBar.setVisibility(View.GONE);
    }
}