com.audiokernel.euphonyrmt.fragments.NowPlayingFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.audiokernel.euphonyrmt.fragments.NowPlayingFragment.java

Source

/*
 * Copyright (C) 2010-2014 The MPDroid Project
 *
 * 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.audiokernel.euphonyrmt.fragments;

import com.audiokernel.euphonyrmt.MPDApplication;
import com.audiokernel.euphonyrmt.R;
import com.audiokernel.euphonyrmt.helpers.AlbumCoverDownloadListener;
import com.audiokernel.euphonyrmt.helpers.AlbumInfo;
import com.audiokernel.euphonyrmt.helpers.CoverAsyncHelper;
import com.audiokernel.euphonyrmt.helpers.CoverManager;
import com.audiokernel.euphonyrmt.helpers.MPDControl;
import com.audiokernel.euphonyrmt.helpers.UpdateTrackInfo;
import com.audiokernel.euphonyrmt.library.SimpleLibraryActivity;

import org.a0z.mpd.MPDCommand;
import org.a0z.mpd.MPDStatus;
import org.a0z.mpd.Tools;
import org.a0z.mpd.event.StatusChangeListener;
import org.a0z.mpd.event.TrackPositionListener;
import org.a0z.mpd.exception.MPDException;
import org.a0z.mpd.item.Music;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.AttrRes;
import android.support.annotation.IdRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.widget.PopupMenuCompat;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.ProgressBar;
import android.widget.RatingBar;
import android.widget.SeekBar;
import android.widget.TextView;

import java.io.IOException;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class NowPlayingFragment extends Fragment implements StatusChangeListener, TrackPositionListener,
        OnSharedPreferenceChangeListener, OnMenuItemClickListener, UpdateTrackInfo.FullTrackInfoUpdate {

    /** In milliseconds. */
    private static final long ANIMATION_DURATION = 1000L;

    private static final int POPUP_ALBUM = 2;

    private static final int POPUP_ALBUM_ARTIST = 1;

    private static final int POPUP_ARTIST = 0;

    private static final int POPUP_COVER_BLACKLIST = 7;

    private static final int POPUP_COVER_SELECTIVE_CLEAN = 8;

    private static final int POPUP_CURRENT = 6;

    private static final int POPUP_FOLDER = 3;

    private static final int POPUP_SHARE = 5;

    private static final int POPUP_STREAM = 4;

    private static final String TAG = "NowPlayingFragment";

    private final MPDApplication mApp = MPDApplication.getInstance();

    private final Timer mVolTimer = new Timer();

    private FragmentActivity mActivity;

    private TextView mAlbumNameText;

    private TextView mArtistNameText;

    private TextView mAudioNameText = null;

    private ImageView mCoverArt;

    private CoverAsyncHelper mCoverAsyncHelper = null;

    private AlbumCoverDownloadListener mCoverDownloadListener;

    private Music mCurrentSong = null;

    private Handler mHandler;

    private boolean mIsAudioNameTextEnabled = false;

    private ImageButton mPlayPauseButton = null;

    private View.OnTouchListener mPopupMenuStreamTouchListener = null;

    private View.OnTouchListener mPopupMenuTouchListener = null;

    private Timer mPosTimer = null;

    private ImageButton mRepeatButton = null;

    private SharedPreferences mSharedPreferences;

    private ImageButton mShuffleButton = null;

    private View mSongInfo = null;

    private TextView mSongNameText;

    private RatingBar mSongRating = null;

    private ImageButton mStopButton = null;

    private SeekBar mTrackSeekBar = null;

    private TextView mTrackTime = null;

    private TextView mTrackTotalTime = null;

    private TimerTask mVolTimerTask = null;

    private ImageView mVolumeIcon = null;

    private SeekBar mVolumeSeekBar = null;

    private TextView mYearNameText;

    /**
     * A convenience method to find a resource and set it as selected.
     *
     * @param view     The view to find the resource in.
     * @param resource The resource to find in the view.
     * @return The TextView found in the {@code view}, set as selected.
     */
    private static TextView findSelected(final View view, @IdRes final int resource) {
        final TextView textView = (TextView) view.findViewById(resource);

        textView.setSelected(true);

        return textView;
    }

    /**
     * This method sets up a resource with the button event handler.
     *
     * @param view      The {@code View} with which to setup the {@code ImageButton}.
     * @param resource  The resource to find in the view.
     * @param longPress Whether long press is supported by this event button.
     * @return The generated {@code ImageButton}.
     */
    private static ImageButton getEventButton(final View view, @IdRes final int resource, final boolean longPress) {
        final ImageButton button = (ImageButton) view.findViewById(resource);
        final ButtonEventHandler buttonEventHandler = new ButtonEventHandler();

        button.setOnClickListener(buttonEventHandler);
        if (longPress) {
            button.setOnLongClickListener(buttonEventHandler);
        }

        return button;
    }

    protected static int getPlayPauseResource(final int state) {
        final int resource;

        if (MPDApplication.getInstance().isLightThemeSelected()) {
            if (state == MPDStatus.STATE_PLAYING) {
                resource = R.drawable.ic_media_pause_light;
            } else {
                resource = R.drawable.ic_media_play_light;
            }
        } else {
            if (state == MPDStatus.STATE_PLAYING) {
                resource = R.drawable.ic_media_pause;
            } else {
                resource = R.drawable.ic_media_play;
            }
        }

        return resource;
    }

    /**
     * Retrieve styled attribute information for the repeat button.
     *
     * @param on True if repeat is enabled, false otherwise.
     * @return Returns the enabled repeat styled attribute if on, returns the disabled repeat styled
     * attribute otherwise.
     */
    private static int getRepeatAttribute(final boolean on) {
        final int attribute;

        if (on) {
            attribute = R.attr.repeatEnabled;
        } else {
            attribute = R.attr.repeatDisabled;
        }

        return attribute;
    }

    /**
     * Retrieve styled attribute information for the random button.
     *
     * @param on True if random is enabled, false otherwise.
     * @return Returns the enabled random styled attribute if on, returns the disabled random styled
     * attribute otherwise.
     */
    private static int getShuffleAttribute(final boolean on) {
        final int attribute;

        if (on) {
            attribute = R.attr.shuffleEnabled;
        } else {
            attribute = R.attr.shuffleDisabled;
        }

        return attribute;
    }

    /**
     * Generates the initial track {@link android.widget.SeekBar}.
     *
     * @param view The view in which to setup the {@code SeekBar} for.
     * @return The constructed SeekBar for the track position modification.
     */
    private static SeekBar getTrackSeekBar(final View view) {
        final SeekBar.OnSeekBarChangeListener seekBarTrackListener = new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) {
            }

            @Override
            public void onStartTrackingTouch(final SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(final SeekBar seekBar) {
                MPDControl.run(MPDControl.ACTION_SEEK, seekBar.getProgress());
            }
        };

        final SeekBar seekBarTrack = (SeekBar) view.findViewById(R.id.progress_track);
        seekBarTrack.setOnSeekBarChangeListener(seekBarTrackListener);

        return seekBarTrack;
    }

    private void applyViewVisibility(final View view, final String property) {
        if (mSharedPreferences.getBoolean(property, false)) {
            view.setVisibility(View.VISIBLE);
        } else {
            view.setVisibility(View.GONE);
        }
    }

    @Override
    public void connectionStateChanged(final boolean connected, final boolean connectionLost) {
        if (connected) {
            forceStatusUpdate();
        } else {
            mSongNameText.setText(R.string.notConnected);
        }
    }

    private void downloadCover(final AlbumInfo albumInfo) {
        mCoverAsyncHelper.downloadCover(albumInfo, true);
    }

    private void forceStatusUpdate() {
        final MPDStatus status = mApp.oMPDAsyncHelper.oMPD.getStatus();

        if (status.isValid()) {
            volumeChanged(status, -1);
            updateStatus(status);
            updateTrackInfo(status, true);
            setButtonAttribute(getRepeatAttribute(status.isRepeat()), mRepeatButton);
            setButtonAttribute(getShuffleAttribute(status.isRandom()), mShuffleButton);
            setStickerVisibility();
        }
    }

    /**
     * Run during fragment initialization, this sets up the cover art popup menu and the coverArt
     * ImageView.
     *
     * @param view The view to setup the coverArt ImageView in.
     * @return The resulting ImageView.
     */
    private ImageView getCoverArt(final View view) {
        final ImageView coverArt = (ImageView) view.findViewById(R.id.albumCover);
        final PopupMenu coverMenu = new PopupMenu(mActivity, coverArt);
        final Menu menu = coverMenu.getMenu();

        coverArt.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                scrollToNowPlaying();
            }
        });

        menu.add(Menu.NONE, POPUP_COVER_BLACKLIST, Menu.NONE, R.string.otherCover);
        menu.add(Menu.NONE, POPUP_COVER_SELECTIVE_CLEAN, Menu.NONE, R.string.resetCover);
        coverMenu.setOnMenuItemClickListener(this);
        coverArt.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(final View v) {
                final boolean isConsumed;

                if (mCurrentSong != null) {
                    menu.setGroupVisible(Menu.NONE, new AlbumInfo(mCurrentSong).isValid());
                    coverMenu.show();
                    isConsumed = true;
                } else {
                    isConsumed = false;
                }

                return isConsumed;
            }
        });

        return coverArt;
    }

    /**
     * This sets up the {@code CoverAsyncHelper} for this class.
     *
     * @param view The view in which to setup the {@code CoverAsyncHelper} for.
     * @return The CoverAsyncHelper used as a field in this class.
     */
    private CoverAsyncHelper getCoverAsyncHelper(final View view) {
        final CoverAsyncHelper coverAsyncHelper = new CoverAsyncHelper();
        final ProgressBar coverArtProgress = (ProgressBar) view.findViewById(R.id.albumCoverProgress);

        // Scale cover images down to screen width
        coverAsyncHelper.setCoverMaxSizeFromScreen(mActivity);
        coverAsyncHelper.setCachedCoverMaxSize(mCoverArt.getWidth());

        mCoverDownloadListener = new AlbumCoverDownloadListener(mCoverArt, coverArtProgress, true);
        coverAsyncHelper.addCoverDownloadListener(mCoverDownloadListener);

        return coverAsyncHelper;
    }

    /**
     * This method generates selected track information to send to another application.
     *
     * The current format of this method should output:
     * header artist - title and if the output is a stream, the URL should be suffixed on the end.
     *
     * @return The track information to send to another application.
     */
    private String getShareString() {
        final char[] separator = { ' ', '-', ' ' };
        final String fullPath = mCurrentSong.getFullPath();
        final String sharePrefix = getString(R.string.sharePrefix);
        final String trackArtist = mCurrentSong.getArtist();
        final String trackTitle = mCurrentSong.getTitle();
        final int initialLength = trackTitle.length() + sharePrefix.length() + 64;
        final StringBuilder shareString = new StringBuilder(initialLength);

        shareString.append(sharePrefix);
        shareString.append(' ');

        if (trackArtist != null) {
            shareString.append(trackArtist);
            shareString.append(separator);
        }
        shareString.append(trackTitle);

        /** If track title is empty, the full path will have been substituted.*/
        if (mCurrentSong.isStream() && !fullPath.startsWith(trackTitle)) {
            shareString.append(separator);
            shareString.append(fullPath);
        }

        return shareString.toString();
    }

    /**
     * Run during fragment initialization, this sets up the song info popup menu.
     *
     * @param view The view in which to setup the song info View for this class.
     * @return The song info view used as a field in this class.
     */
    private View getSongInfo(final View view) {
        final View songInfo = view.findViewById(R.id.songInfo);

        final PopupMenu popupMenu = new PopupMenu(mActivity, songInfo);
        final Menu menu = popupMenu.getMenu();
        menu.add(Menu.NONE, POPUP_ALBUM, Menu.NONE, R.string.goToAlbum);
        menu.add(Menu.NONE, POPUP_ARTIST, Menu.NONE, R.string.goToArtist);
        menu.add(Menu.NONE, POPUP_ALBUM_ARTIST, Menu.NONE, R.string.goToAlbumArtist);
        menu.add(Menu.NONE, POPUP_FOLDER, Menu.NONE, R.string.goToFolder);
        menu.add(Menu.NONE, POPUP_CURRENT, Menu.NONE, R.string.goToCurrent);
        menu.add(Menu.NONE, POPUP_SHARE, Menu.NONE, R.string.share);
        popupMenu.setOnMenuItemClickListener(this);
        mPopupMenuTouchListener = PopupMenuCompat.getDragToOpenListener(popupMenu);

        final PopupMenu popupMenuStream = new PopupMenu(mActivity, songInfo);
        final Menu menuStream = popupMenuStream.getMenu();
        menuStream.add(Menu.NONE, POPUP_STREAM, Menu.NONE, R.string.goToStream);
        menuStream.add(Menu.NONE, POPUP_CURRENT, Menu.NONE, R.string.goToCurrent);
        menuStream.add(Menu.NONE, POPUP_SHARE, Menu.NONE, R.string.share);
        popupMenuStream.setOnMenuItemClickListener(this);
        mPopupMenuStreamTouchListener = PopupMenuCompat.getDragToOpenListener(popupMenuStream);

        songInfo.setOnClickListener(new OnClickListener() {

            /**
             * Checks whether the album artist should be on the popup menu for the current track.
             *
             * @return True if the album artist popup menu entry should be visible, false otherwise.
             */
            private boolean isAlbumArtistVisible() {
                boolean albumArtistEnabled = false;
                final String albumArtist = mCurrentSong.getAlbumArtist();

                if (albumArtist != null && !albumArtist.isEmpty()) {
                    final String artist = mCurrentSong.getArtist();

                    if (isArtistVisible() && !albumArtist.equals(artist)) {
                        albumArtistEnabled = true;
                    }
                }

                return albumArtistEnabled;
            }

            /**
             * Checks whether the album should be on the popup menu for the current track.
             *
             * @return True if the album popup menu entry should be visible, false otherwise.
             */
            private boolean isAlbumVisible() {
                final boolean isAlbumVisible;
                final String album = mCurrentSong.getAlbum();

                if (album != null && !album.isEmpty()) {
                    isAlbumVisible = true;
                } else {
                    isAlbumVisible = false;
                }

                return isAlbumVisible;
            }

            /**
             * Checks whether the artist should be on the popup menu for the current track.
             *
             * @return True if the artist popup menu entry should be visible, false otherwise.
             */
            private boolean isArtistVisible() {
                final boolean isArtistVisible;
                final String artist = mCurrentSong.getArtist();

                if (artist != null && !artist.isEmpty()) {
                    isArtistVisible = true;
                } else {
                    isArtistVisible = false;
                }

                return isArtistVisible;
            }

            /**
             * This method checks the dynamic entries for visibility prior to showing the song info
             * popup menu.
             *
             * @param v The view for the song info popup menu.
             */
            @Override
            public void onClick(final View v) {
                if (mCurrentSong != null) {
                    if (mCurrentSong.isStream()) {
                        popupMenuStream.show();
                    } else {
                        // Enable / Disable menu items that need artist and album defined.
                        menu.findItem(POPUP_ALBUM).setVisible(isAlbumVisible());
                        menu.findItem(POPUP_ARTIST).setVisible(isArtistVisible());
                        menu.findItem(POPUP_ALBUM_ARTIST).setVisible(isAlbumArtistVisible());
                        popupMenu.show();
                    }
                }
            }
        });

        return songInfo;
    }

    private float getTrackRating() {
        float rating = 0.0f;

        try {
            rating = (float) mApp.oMPDAsyncHelper.oMPD.getStickerManager().getRating(mCurrentSong);
        } catch (final IOException | MPDException e) {
            Log.e(TAG, "Failed to get the current track rating.", e);
        }

        return rating / 2.0f;
    }

    /**
     * Generates the volume {@link android.widget.SeekBar}.
     *
     * @param view The view in which to setup the {@code SeekBar} for.
     * @return The constructed SeekBar for the volume modification.
     */
    private SeekBar getVolumeSeekBar(final View view) {
        final SeekBar.OnSeekBarChangeListener seekBarListener = new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) {

            }

            @Override
            public void onStartTrackingTouch(final SeekBar seekBar) {
                mVolTimerTask = new TimerTask() {
                    private int mLastSentVol = -1;

                    private SeekBar mProgress;

                    @Override
                    public void run() {
                        final int progress = mProgress.getProgress();

                        if (mLastSentVol != progress) {
                            mLastSentVol = progress;
                            MPDControl.run(MPDControl.ACTION_VOLUME_SET, progress);
                        }
                    }

                    public TimerTask setProgress(final SeekBar prg) {
                        mProgress = prg;
                        return this;
                    }
                }.setProgress(seekBar);

                mVolTimer.scheduleAtFixedRate(mVolTimerTask, (long) MPDCommand.MIN_VOLUME,
                        (long) MPDCommand.MAX_VOLUME);
            }

            @Override
            public void onStopTrackingTouch(final SeekBar seekBar) {
                mVolTimerTask.cancel();
                mVolTimerTask.run();
            }
        };

        final SeekBar volumeSeekBar = (SeekBar) view.findViewById(R.id.progress_volume);
        volumeSeekBar.setOnSeekBarChangeListener(seekBarListener);

        return volumeSeekBar;
    }

    /**
     * This method handles any simple library activity item ids.
     *
     * @param itemId The itemId to attempt to handle.
     * @return {@code true} if this is handled by a simple library activity,
     * {@code false} otherwise.
     */
    private boolean isSimpleLibraryItem(final int itemId) {
        Intent intent = null;

        switch (itemId) {
        case POPUP_ALBUM:
        case POPUP_ALBUM_ARTIST:
        case POPUP_ARTIST:
        case POPUP_FOLDER:
            if (mCurrentSong != null) {
                intent = simpleLibraryMusicItem(itemId);
            }
            break;
        case POPUP_STREAM:
            intent = new Intent(mActivity, SimpleLibraryActivity.class);
            intent.putExtra("streams", true);
            break;
        default:
            break;
        }

        if (intent != null) {
            /**
             * Set the result for SimpleLibraryActivity to
             * return so getCallingActivity() will work.
             */
            startActivityForResult(intent, 1);
        }

        return intent != null;
    }

    /**
     * This method handles any simple library activity item ids which handle music items.
     *
     * @param itemId The itemId to attempt to handle.
     * @return An intent to start the {@link com.audiokernel.euphonyrmt.library.SimpleLibraryActivity}.
     */
    private Intent simpleLibraryMusicItem(final int itemId) {
        final Intent intent = new Intent(mActivity, SimpleLibraryActivity.class);

        switch (itemId) {
        case POPUP_ALBUM:
            intent.putExtra("album", mCurrentSong.getAlbumAsAlbum());
            break;
        case POPUP_ALBUM_ARTIST:
            intent.putExtra("artist", mCurrentSong.getAlbumArtistAsArtist());
            break;
        case POPUP_ARTIST:
            intent.putExtra("artist", mCurrentSong.getArtistAsArtist());
            break;
        case POPUP_FOLDER:
            final String path = mCurrentSong.getFullPath();
            final String parent = mCurrentSong.getParent();
            if (path == null || parent == null) {
                break;
            }
            intent.putExtra("folder", parent);
            break;
        default:
            break;
        }

        return intent;
    }

    @Override
    public void libraryStateChanged(final boolean updating, final boolean dbChanged) {
    }

    @Override
    public void onAttach(final Activity activity) {
        super.onAttach(activity);
        mActivity = (FragmentActivity) activity;

    }

    /**
     * This is called when cover art needs to be updated due to server information change.
     *
     * @param albumInfo The current albumInfo
     */
    @Override
    public final void onCoverUpdate(final AlbumInfo albumInfo) {
        final int noCoverResource = AlbumCoverDownloadListener.getLargeNoCoverResource();
        mCoverArt.setImageResource(noCoverResource);

        if (albumInfo != null) {
            downloadCover(albumInfo);
        }
    }

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new Handler();
        setHasOptionsMenu(false);
        mActivity.setTitle(R.string.nowPlaying);
    }

    @Override
    public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
            final Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);

        final Animation fadeIn = AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_in);
        final Animation fadeOut = AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out);
        final int viewLayout;
        final View view;

        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);

        mSharedPreferences.registerOnSharedPreferenceChangeListener(this);

        if (mApp.isTabletUiEnabled()) {
            viewLayout = R.layout.main_fragment_tablet;
        } else {
            viewLayout = R.layout.main_fragment;
        }

        view = inflater.inflate(viewLayout, container, false);

        mTrackTime = (TextView) view.findViewById(R.id.trackTime);
        mTrackTotalTime = (TextView) view.findViewById(R.id.trackTotalTime);
        mVolumeIcon = (ImageView) view.findViewById(R.id.volume_icon);

        /** These load the TextView resource, and set it as selected. */
        mAlbumNameText = findSelected(view, R.id.albumName);
        mArtistNameText = findSelected(view, R.id.artistName);
        mAudioNameText = findSelected(view, R.id.audioName);
        mSongNameText = findSelected(view, R.id.songName);
        mSongNameText.setText(R.string.notConnected);
        mYearNameText = findSelected(view, R.id.yearName);
        applyViewVisibility(mYearNameText, "enableAlbumYearText");
        mSongRating = (RatingBar) view.findViewById(R.id.songRating);
        mSongRating.setOnRatingBarChangeListener(new RatingChangedHandler());
        mSongRating.setVisibility(View.GONE);

        /** These get the event button, then setup listeners for them. */
        mPlayPauseButton = getEventButton(view, R.id.playpause, true);
        mRepeatButton = getEventButton(view, R.id.repeat, false);
        mShuffleButton = getEventButton(view, R.id.shuffle, false);
        mStopButton = getEventButton(view, R.id.stop, true);
        applyViewVisibility(mStopButton, "enableStopButton");

        /** Same as above, but these don't require a stored field. */
        getEventButton(view, R.id.next, false);
        getEventButton(view, R.id.prev, false);

        /** These have methods to initialize everything required to get them setup. */
        mCoverArt = getCoverArt(view);
        mCoverAsyncHelper = getCoverAsyncHelper(view);
        mSongInfo = getSongInfo(view);
        mTrackSeekBar = getTrackSeekBar(view);
        mVolumeSeekBar = getVolumeSeekBar(view);

        fadeIn.setDuration(ANIMATION_DURATION);
        fadeOut.setDuration(ANIMATION_DURATION);

        Log.i(TAG, "Initialization succeeded");

        return view;
    }

    @Override
    public void onDestroy() {
        final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
        settings.unregisterOnSharedPreferenceChangeListener(this);
        super.onDestroy();
    }

    @Override
    public void onDestroyView() {
        mCoverArt.setImageResource(AlbumCoverDownloadListener.getNoCoverResource());
        mCoverDownloadListener.freeCoverDrawable();
        super.onDestroyView();
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mActivity = null;
    }

    @Override
    public boolean onMenuItemClick(final MenuItem item) {
        final AlbumInfo albumInfo;
        boolean result = true;
        final int itemId = item.getItemId();

        switch (item.getItemId()) {
        case POPUP_COVER_BLACKLIST:
            albumInfo = new AlbumInfo(mCurrentSong);
            CoverManager.getInstance().markWrongCover(albumInfo);
            downloadCover(albumInfo);
            updateQueueCovers(albumInfo);
            break;
        case POPUP_COVER_SELECTIVE_CLEAN:
            albumInfo = new AlbumInfo(mCurrentSong);
            CoverManager.getInstance().clear(albumInfo);
            downloadCover(albumInfo);
            updateQueueCovers(albumInfo);
            break;
        case POPUP_CURRENT:
            scrollToNowPlaying();
            break;
        case POPUP_SHARE:
            final Intent intent = new Intent(Intent.ACTION_SEND, null);
            intent.putExtra(Intent.EXTRA_TEXT, getShareString());
            intent.setType("text/plain");
            startActivity(intent);
            break;
        default:
            result = isSimpleLibraryItem(itemId);
            break;
        }

        return result;
    }

    @Override
    public void onResume() {
        super.onResume();
        final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
        mIsAudioNameTextEnabled = settings.getBoolean("enableAudioText", false);
        forceStatusUpdate();
    }

    @Override
    public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
        switch (key) {
        case CoverManager.PREFERENCE_CACHE:
        case CoverManager.PREFERENCE_LASTFM:
        case CoverManager.PREFERENCE_LOCALSERVER:
            CoverAsyncHelper.setCoverRetrieversFromPreferences();
            break;
        case "enableStopButton":
            applyViewVisibility(mStopButton, key);
            break;
        case "enableAlbumYearText":
            applyViewVisibility(mYearNameText, key);
            break;
        case "enableAudioText":
            mIsAudioNameTextEnabled = sharedPreferences.getBoolean(key, false);
            updateAudioNameText(mApp.oMPDAsyncHelper.oMPD.getStatus());
            break;
        case "enableRating":
            setStickerVisibility();
            updateTrackInfo(mApp.oMPDAsyncHelper.oMPD.getStatus(), false);
            break;
        default:
            break;
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        if (mApp.updateTrackInfo == null) {
            mApp.updateTrackInfo = new UpdateTrackInfo();
        }
        mApp.updateTrackInfo.addCallback(this);
        mApp.oMPDAsyncHelper.addStatusChangeListener(this);
        mApp.oMPDAsyncHelper.addTrackPositionListener(this);
        mApp.setActivity(this);
    }

    @Override
    public void onStop() {
        super.onStop();

        mApp.updateTrackInfo.removeCallback(this);
        mApp.oMPDAsyncHelper.removeStatusChangeListener(this);
        mApp.oMPDAsyncHelper.removeTrackPositionListener(this);
        stopPosTimer();
        mApp.unsetActivity(this);
    }

    /**
     * Called when a track information change has been detected.
     *
     * @param updatedSong The currentSong item object.
     * @param album       The album change.
     * @param artist      The artist change.
     * @param date        The date change.
     * @param title       The title change.
     */
    @Override
    public final void onTrackInfoUpdate(final Music updatedSong, final float trackRating, final CharSequence album,
            final CharSequence artist, final CharSequence date, final CharSequence title) {
        mCurrentSong = updatedSong;
        mAlbumNameText.setText(album);
        mArtistNameText.setText(artist);
        mSongNameText.setText(title);
        mSongRating.setRating(trackRating);
        mYearNameText.setText(date);
        updateAudioNameText(mApp.oMPDAsyncHelper.oMPD.getStatus());
    }

    @Override
    public void playlistChanged(final MPDStatus mpdStatus, final int oldPlaylistVersion) {
        /**
         * If the current song is a stream, the metadata can change in place, and that will only
         * change the playlist, not the track, so, update if we detect a stream.
         */
        if (mCurrentSong != null && mCurrentSong.isStream() || mpdStatus.isState(MPDStatus.STATE_STOPPED)) {
            updateTrackInfo(mpdStatus, false);
        }
    }

    @Override
    public void randomChanged(final boolean random) {
        setButtonAttribute(getShuffleAttribute(random), mShuffleButton);
    }

    @Override
    public void repeatChanged(final boolean repeating) {
        setButtonAttribute(getRepeatAttribute(repeating), mRepeatButton);
    }

    private void scrollToNowPlaying() {
        final QueueFragment queueFragment = (QueueFragment) mActivity.getSupportFragmentManager()
                .findFragmentById(R.id.queue_fragment);

        if (queueFragment == null) {
            Log.w(TAG, "Queue fragment was not available when scrolling to playing track.");
        } else {
            queueFragment.scrollToNowPlaying();
        }
    }

    /**
     * Sets a buttons attributes.
     *
     * @param attribute The attribute resource to set the button to.
     * @param button    The button with which to set the attribute resource.
     */
    private void setButtonAttribute(@AttrRes final int attribute, final ImageButton button) {
        final int[] attrs = { attribute };
        final TypedArray ta = mActivity.obtainStyledAttributes(attrs);
        final Drawable drawableFromTheme = ta.getDrawable(0);

        button.setImageDrawable(drawableFromTheme);
        button.invalidate();
        ta.recycle();
    }

    private void setStickerVisibility() {
        if (mApp.oMPDAsyncHelper.oMPD.getStickerManager().isAvailable()) {
            applyViewVisibility(mSongRating, "enableRating");
        } else {
            mSongRating.setVisibility(View.GONE);
        }
    }

    private void startPosTimer(final long start, final long total) {
        stopPosTimer();
        mPosTimer = new Timer();
        final TimerTask posTimerTask = new PosTimerTask(start, total);
        mPosTimer.scheduleAtFixedRate(posTimerTask, 0L, DateUtils.SECOND_IN_MILLIS);
    }

    @Override
    public void stateChanged(final MPDStatus mpdStatus, final int oldState) {
        if (mActivity != null) {
            updateStatus(mpdStatus);
            updateAudioNameText(mpdStatus);
        }
    }

    @Override
    public void stickerChanged(final MPDStatus mpdStatus) {
        if (mSongRating.getVisibility() == View.VISIBLE && mCurrentSong != null) {
            /** This track is not necessarily the track that was changed. */
            final float rating = getTrackRating();
            mSongRating.setRating(rating);
        }
    }

    private void stopPosTimer() {
        if (null != mPosTimer) {
            mPosTimer.cancel();
            mPosTimer = null;
        }
    }

    /**
     * Toggle the track progress bar. This should be called only when the track changes, for
     * position changes, startPosTimer() is sufficient.
     *
     * @param status A current {@code MPDStatus} object.
     */
    private void toggleTrackProgress(final MPDStatus status) {
        final long totalTime = status.getTotalTime();

        if (totalTime == 0) {
            mSongRating.setVisibility(View.GONE);
            mTrackTime.setVisibility(View.INVISIBLE);
            mTrackTotalTime.setVisibility(View.INVISIBLE);
            stopPosTimer();
            mTrackSeekBar.setProgress(0);
            mTrackSeekBar.setEnabled(false);
        } else {
            final long elapsedTime = status.getElapsedTime();

            if (status.isState(MPDStatus.STATE_PLAYING)) {
                startPosTimer(elapsedTime, totalTime);
            } else {
                stopPosTimer();
                updateTrackProgress(elapsedTime, totalTime);
            }

            mTrackSeekBar.setMax((int) totalTime);

            mTrackTime.setVisibility(View.VISIBLE);
            mTrackTotalTime.setVisibility(View.VISIBLE);
            mTrackSeekBar.setEnabled(true);
        }
    }

    /**
     * This enables or disables the volume, depending on the volume given by the server.
     *
     * @param volume The current volume value.
     */
    private void toggleVolumeBar(final int volume) {
        if (volume < MPDCommand.MIN_VOLUME || volume > MPDCommand.MAX_VOLUME) {
            mVolumeSeekBar.setEnabled(false);
            mVolumeSeekBar.setVisibility(View.GONE);
            mVolumeIcon.setVisibility(View.GONE);
        } else {
            mVolumeSeekBar.setEnabled(true);
            mVolumeSeekBar.setVisibility(View.VISIBLE);
            mVolumeIcon.setVisibility(View.VISIBLE);
        }
    }

    @Override
    public void trackChanged(final MPDStatus mpdStatus, final int oldTrack) {
        updateTrackInfo(mpdStatus, false);
    }

    @Override
    public void trackPositionChanged(final MPDStatus status) {
        toggleTrackProgress(status);
    }

    /**
     * This will update the audioNameText (extra track information) field.
     *
     * @param status An {@code MPDStatus} object.
     */
    private void updateAudioNameText(final MPDStatus status) {
        StringBuilder optionalTrackInfo = null;

        if (mCurrentSong != null && mIsAudioNameTextEnabled && !status.isState(MPDStatus.STATE_STOPPED)) {

            final char[] separator = { ' ', '|', ' ' };
            final String fileExtension = Tools.getExtension(mCurrentSong.getFullPath());
            final long bitRate = status.getBitrate();
            final int bitsPerSample = status.getBitsPerSample();
            final int sampleRate = status.getSampleRate();
            optionalTrackInfo = new StringBuilder(40);

            /**
             * Check each individual bit of info, the sever can give
             * out empty (and buggy) information from time to time.
             */
            if (fileExtension != null) {
                optionalTrackInfo.append(fileExtension.toUpperCase());
            }

            /** The server can give out buggy (and empty) information from time to time. */
            if (bitRate > 0L) {
                if (optionalTrackInfo.length() > 0) {
                    optionalTrackInfo.append(separator);
                }
                optionalTrackInfo.append(bitRate);
                optionalTrackInfo.append("kbps");
            }

            if (bitsPerSample > 0) {
                if (optionalTrackInfo.length() > 0) {
                    optionalTrackInfo.append(separator);
                }
                optionalTrackInfo.append(bitsPerSample);
                optionalTrackInfo.append("bits");
            }

            if (sampleRate > 1000) {
                if (optionalTrackInfo.length() > 0) {
                    optionalTrackInfo.append(separator);
                }
                optionalTrackInfo.append(Math.abs(sampleRate / 1000.0f));
                optionalTrackInfo.append("kHz");
            }

            if (optionalTrackInfo.length() > 0) {
                mAudioNameText.setText(optionalTrackInfo);
                mAudioNameText.setVisibility(View.VISIBLE);
            }
        }

        if (optionalTrackInfo == null || optionalTrackInfo.length() == 0) {
            mAudioNameText.setVisibility(View.GONE);
        }
    }

    private void updateQueueCovers(final AlbumInfo albumInfo) {
        final QueueFragment queueFragment = (QueueFragment) mActivity.getSupportFragmentManager()
                .findFragmentById(R.id.queue_fragment);

        if (queueFragment == null) {
            Log.w(TAG, "Queue fragment was not available for cover update.");
        } else {
            queueFragment.updateCover(albumInfo);
        }
    }

    private void updateStatus(final MPDStatus status) {
        toggleTrackProgress(status);

        mPlayPauseButton.setImageResource(getPlayPauseResource(status.getState()));

        View.OnTouchListener currentListener = null;
        if (mCurrentSong != null) {
            if (mCurrentSong.isStream()) {
                currentListener = mPopupMenuStreamTouchListener;
            } else {
                currentListener = mPopupMenuTouchListener;
            }
        }
        mSongInfo.setOnTouchListener(currentListener);
    }

    private void updateTrackInfo(final MPDStatus status, final boolean forcedUpdate) {
        if (mApp.oMPDAsyncHelper.oMPD.isConnected() && isAdded()) {
            toggleTrackProgress(status);
            mApp.updateTrackInfo.refresh(status, forcedUpdate);
        }
    }

    /**
     * Update the track progress numbers and track {@code SeekBar} object.
     *
     * @param elapsed        The current track elapsed time.
     * @param totalTrackTime The current track total time.
     */
    private void updateTrackProgress(final long elapsed, final long totalTrackTime) {
        /** In case the total track time is flawed. */
        final long elapsedTime;

        if (elapsed > totalTrackTime) {
            elapsedTime = totalTrackTime;
        } else {
            elapsedTime = elapsed;
        }

        mTrackSeekBar.setProgress((int) elapsedTime);

        mHandler.post(new Runnable() {
            @Override
            public void run() {
                mTrackTime.setText(Music.timeToString(elapsedTime));
                mTrackTotalTime.setText(Music.timeToString(totalTrackTime));
            }
        });
    }

    @Override
    public void volumeChanged(final MPDStatus mpdStatus, final int oldVolume) {
        final int volume = mpdStatus.getVolume();

        toggleVolumeBar(volume);
        mVolumeSeekBar.setProgress(volume);
    }

    private static class ButtonEventHandler implements OnClickListener, View.OnLongClickListener {

        @Override
        public void onClick(final View v) {
            MPDControl.run(v.getId());
        }

        @Override
        public boolean onLongClick(final View v) {
            final boolean isConsumed;
            final MPDApplication app = MPDApplication.getInstance();
            final MPDStatus mpdStatus = app.oMPDAsyncHelper.oMPD.getStatus();

            if (v.getId() == R.id.playpause && !mpdStatus.isState(MPDStatus.STATE_STOPPED)) {
                MPDControl.run(MPDControl.ACTION_STOP);
                isConsumed = true;
            } else {
                isConsumed = false;
            }

            return isConsumed;
        }
    }

    /**
     * This class runs a timer to keep the time elapsed since last track elapsed time updated for
     * the purpose of keeping the track progress up to date without continual server polling.
     */
    private class PosTimerTask extends TimerTask {

        private final long mTimerStartTime;

        private long mElapsedTime = 0L;

        private long mStartTrackTime = 0L;

        private long mTotalTrackTime = 0L;

        private PosTimerTask(final long start, final long total) {
            super();
            mStartTrackTime = start;
            mTotalTrackTime = total;
            mTimerStartTime = new Date().getTime();
        }

        @Override
        public void run() {
            final long elapsedSinceTimerStart = new Date().getTime() - mTimerStartTime;

            mElapsedTime = mStartTrackTime + elapsedSinceTimerStart / DateUtils.SECOND_IN_MILLIS;

            updateTrackProgress(mElapsedTime, mTotalTrackTime);
        }
    }

    private class RatingChangedHandler implements RatingBar.OnRatingBarChangeListener {

        @Override
        public void onRatingChanged(final RatingBar ratingBar, final float rating, final boolean fromUser) {
            final int trackRating = (int) rating * 2;
            if (fromUser && mCurrentSong != null) {
                try {
                    mApp.oMPDAsyncHelper.oMPD.getStickerManager().setRating(mCurrentSong, trackRating);
                } catch (final IOException | MPDException e) {
                    Log.e(TAG, "Failed to set the rating.", e);
                }
                Log.d(TAG, "Rating changed to " + rating);
            }
        }
    }
}