uk.org.ngo.squeezer.NowPlayingFragment.java Source code

Java tutorial

Introduction

Here is the source code for uk.org.ngo.squeezer.NowPlayingFragment.java

Source

/*
 * Copyright (c) 2012 Google Inc.  All Rights Reserved.
 *
 * 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 uk.org.ngo.squeezer;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBar;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

import uk.org.ngo.squeezer.dialog.AboutDialog;
import uk.org.ngo.squeezer.dialog.EnableWifiDialog;
import uk.org.ngo.squeezer.framework.BaseActivity;
import uk.org.ngo.squeezer.itemlist.AlarmsActivity;
import uk.org.ngo.squeezer.itemlist.AlbumListActivity;
import uk.org.ngo.squeezer.itemlist.CurrentPlaylistActivity;
import uk.org.ngo.squeezer.itemlist.PlayerListActivity;
import uk.org.ngo.squeezer.itemlist.SongListActivity;
import uk.org.ngo.squeezer.model.Artist;
import uk.org.ngo.squeezer.model.Player;
import uk.org.ngo.squeezer.model.PlayerState;
import uk.org.ngo.squeezer.model.PlayerState.RepeatStatus;
import uk.org.ngo.squeezer.model.PlayerState.ShuffleStatus;
import uk.org.ngo.squeezer.model.Song;
import uk.org.ngo.squeezer.service.ConnectionState;
import uk.org.ngo.squeezer.service.ISqueezeService;
import uk.org.ngo.squeezer.service.ServerString;
import uk.org.ngo.squeezer.service.SqueezeService;
import uk.org.ngo.squeezer.service.event.ConnectionChanged;
import uk.org.ngo.squeezer.service.event.HandshakeComplete;
import uk.org.ngo.squeezer.service.event.MusicChanged;
import uk.org.ngo.squeezer.service.event.PlayStatusChanged;
import uk.org.ngo.squeezer.service.event.PlayersChanged;
import uk.org.ngo.squeezer.service.event.PowerStatusChanged;
import uk.org.ngo.squeezer.service.event.RepeatStatusChanged;
import uk.org.ngo.squeezer.service.event.ShuffleStatusChanged;
import uk.org.ngo.squeezer.service.event.SongTimeChanged;
import uk.org.ngo.squeezer.util.ImageFetcher;

public class NowPlayingFragment extends Fragment implements View.OnCreateContextMenuListener {

    private final String TAG = "NowPlayingFragment";

    private BaseActivity mActivity;

    @Nullable
    private ISqueezeService mService = null;

    private TextView albumText;

    private TextView artistText;

    private TextView trackText;

    private ImageView btnContextMenu;

    private TextView currentTime;

    private TextView totalTime;

    private MenuItem menu_item_connect;

    private MenuItem menu_item_disconnect;

    private MenuItem menu_item_poweron;

    private MenuItem menu_item_poweroff;

    private MenuItem menu_item_players;

    private MenuItem menu_item_playlist;

    private MenuItem menu_item_alarm;

    private MenuItem menu_item_search;

    private ImageButton playPauseButton;

    private ImageButton nextButton;

    private ImageButton prevButton;

    private ImageButton shuffleButton;

    private ImageButton repeatButton;

    private ImageView albumArt;

    /** In full-screen mode, shows the current progress through the track. */
    private SeekBar seekBar;

    /** In mini-mode, shows the current progress through the track. */
    private ProgressBar mProgressBar;

    // Updating the seekbar
    private boolean updateSeekBar = true;

    private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            ConnectivityManager connMgr = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
            if (networkInfo.isConnected()) {
                Log.v(TAG, "Received WIFI connected broadcast");
                if (!isConnected()) {
                    // Requires a serviceStub. Else we'll do this on the service
                    // connection callback.
                    if (mService != null && !isManualDisconnect()) {
                        Log.v(TAG, "Initiated connect on WIFI connected");
                        startVisibleConnection();
                    }
                }
            }
        }
    };

    /** Dialog displayed while connecting to the server. */
    private ProgressDialog connectingDialog = null;

    /**
     * Shows the "connecting" dialog if it's not already showing.
     */
    @UiThread
    private void showConnectingDialog() {
        if (connectingDialog == null || !connectingDialog.isShowing()) {
            Preferences preferences = new Preferences(mActivity);
            Preferences.ServerAddress serverAddress = preferences.getServerAddress();

            connectingDialog = ProgressDialog.show(mActivity, getText(R.string.connecting_text),
                    getString(R.string.connecting_to_text, preferences.getServerName(serverAddress)), true, false);
        }
    }

    /**
     * Dismisses the "connecting" dialog if it's showing.
     */
    @UiThread
    private void dismissConnectingDialog() {
        if (connectingDialog != null && connectingDialog.isShowing()) {
            connectingDialog.dismiss();
        }
        connectingDialog = null;
    }

    private final ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            Log.v(TAG, "ServiceConnection.onServiceConnected()");
            NowPlayingFragment.this.onServiceConnected((ISqueezeService) binder);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };

    private boolean mFullHeightLayout;

    /**
     * Called before onAttach. Pull out the layout spec to figure out which layout to use later.
     */
    @Override
    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {
        super.onInflate(activity, attrs, savedInstanceState);

        int layout_height = attrs.getAttributeUnsignedIntValue("http://schemas.android.com/apk/res/android",
                "layout_height", 0);

        mFullHeightLayout = (layout_height == ViewGroup.LayoutParams.FILL_PARENT);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mActivity = (BaseActivity) activity;
    }

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

        mActivity.bindService(new Intent(mActivity, SqueezeService.class), serviceConnection,
                Context.BIND_AUTO_CREATE);
        Log.d(TAG, "did bindService; serviceStub = " + mService);
    }

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

        if (mFullHeightLayout) {
            v = inflater.inflate(R.layout.now_playing_fragment_full, container, false);

            artistText = (TextView) v.findViewById(R.id.artistname);
            nextButton = (ImageButton) v.findViewById(R.id.next);
            prevButton = (ImageButton) v.findViewById(R.id.prev);
            shuffleButton = (ImageButton) v.findViewById(R.id.shuffle);
            repeatButton = (ImageButton) v.findViewById(R.id.repeat);
            currentTime = (TextView) v.findViewById(R.id.currenttime);
            totalTime = (TextView) v.findViewById(R.id.totaltime);
            seekBar = (SeekBar) v.findViewById(R.id.seekbar);

            btnContextMenu = (ImageView) v.findViewById(R.id.context_menu);
            btnContextMenu.setOnCreateContextMenuListener(this);
            btnContextMenu.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    v.showContextMenu();
                }
            });
        } else {
            v = inflater.inflate(R.layout.now_playing_fragment_mini, container, false);

            mProgressBar = (ProgressBar) v.findViewById(R.id.progressbar);
        }

        albumArt = (ImageView) v.findViewById(R.id.album);
        trackText = (TextView) v.findViewById(R.id.trackname);
        albumText = (TextView) v.findViewById(R.id.albumname);
        playPauseButton = (ImageButton) v.findViewById(R.id.pause);

        // Marquee effect on TextViews only works if they're focused.
        trackText.requestFocus();

        playPauseButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mService == null) {
                    return;
                }
                if (isConnected()) {
                    Log.v(TAG, "Pause...");
                    mService.togglePausePlay();
                } else {
                    // When we're not connected, the play/pause
                    // button turns into a green connect button.
                    onUserInitiatesConnect();
                }
            }
        });

        if (mFullHeightLayout) {
            /*
             * TODO: Simplify these following the notes at
             * http://developer.android.com/resources/articles/ui-1.6.html.
             * Maybe. because the TextView resources don't support the
             * android:onClick attribute.
             */
            nextButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mService == null) {
                        return;
                    }
                    mService.nextTrack();
                }
            });

            prevButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mService == null) {
                        return;
                    }
                    mService.previousTrack();
                }
            });

            shuffleButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mService == null) {
                        return;
                    }
                    mService.toggleShuffle();
                }
            });

            repeatButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mService == null) {
                        return;
                    }
                    mService.toggleRepeat();
                }
            });

            seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
                Song seekingSong;

                // Update the time indicator to reflect the dragged thumb
                // position.
                @Override
                public void onProgressChanged(SeekBar s, int progress, boolean fromUser) {
                    if (fromUser) {
                        currentTime.setText(Util.formatElapsedTime(progress));
                    }
                }

                // Disable updates when user drags the thumb.
                @Override
                public void onStartTrackingTouch(SeekBar s) {
                    seekingSong = getCurrentSong();
                    updateSeekBar = false;
                }

                // Re-enable updates. If the current song is the same as when
                // we started seeking then jump to the new point in the track,
                // otherwise ignore the seek.
                @Override
                public void onStopTrackingTouch(SeekBar s) {
                    Song thisSong = getCurrentSong();

                    updateSeekBar = true;

                    if (seekingSong == thisSong) {
                        setSecondsElapsed(s.getProgress());
                    }
                }
            });
        } else {
            // Clicking on the layout goes to NowPlayingActivity.
            v.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    NowPlayingActivity.show(mActivity);
                }
            });
        }

        return v;
    }

    @UiThread
    private void updatePlayPauseIcon(@PlayerState.PlayState String playStatus) {
        playPauseButton.setImageResource((PlayerState.PLAY_STATE_PLAY.equals(playStatus))
                ? mActivity.getAttributeValue(R.attr.ic_action_av_pause)
                : mActivity.getAttributeValue(R.attr.ic_action_av_play));
    }

    @UiThread
    private void updateShuffleStatus(ShuffleStatus shuffleStatus) {
        if (mFullHeightLayout && shuffleStatus != null) {
            shuffleButton.setImageResource(mActivity.getAttributeValue(shuffleStatus.getIcon()));
        }
    }

    @UiThread
    private void updateRepeatStatus(RepeatStatus repeatStatus) {
        if (mFullHeightLayout && repeatStatus != null) {
            repeatButton.setImageResource(mActivity.getAttributeValue(repeatStatus.getIcon()));
        }
    }

    @UiThread
    private void updatePowerMenuItems(boolean canPowerOn, boolean canPowerOff) {
        boolean connected = isConnected();

        // The fragment may no longer be attached to the parent activity.  If so, do nothing.
        if (!isAdded()) {
            return;
        }

        if (menu_item_poweron != null) {
            if (canPowerOn && connected) {
                Player player = getActivePlayer();
                String playerName = player != null ? player.getName() : "";
                menu_item_poweron.setTitle(getString(R.string.menu_item_poweron, playerName));
                menu_item_poweron.setVisible(true);
            } else {
                menu_item_poweron.setVisible(false);
            }
        }

        if (menu_item_poweroff != null) {
            if (canPowerOff && connected) {
                Player player = getActivePlayer();
                String playerName = player != null ? player.getName() : "";
                menu_item_poweroff.setTitle(getString(R.string.menu_item_poweroff, playerName));
                menu_item_poweroff.setVisible(true);
            } else {
                menu_item_poweroff.setVisible(false);
            }
        }
    }

    /**
     * Manages the list of connected players in the action bar.
     *
     * @param players A list of players to show. May be empty (use {@code
     * Collections.<Player>emptyList()}) but not null.
     * @param activePlayer The currently active player. May be null.
     */
    @UiThread
    private void updatePlayerDropDown(@NonNull Collection<Player> players, @Nullable Player activePlayer) {
        if (!isAdded()) {
            return;
        }

        // Only include players that are connected to the server.
        ArrayList<Player> connectedPlayers = new ArrayList<Player>();
        for (Player player : players) {
            if (player.getConnected()) {
                connectedPlayers.add(player);
            }
        }

        ActionBar actionBar = mActivity.getSupportActionBar();

        // If there are multiple players connected then show a spinner allowing the user to
        // choose between them.
        if (connectedPlayers.size() > 1) {
            actionBar.setDisplayShowTitleEnabled(false);
            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
            final ArrayAdapter<Player> playerAdapter = new ArrayAdapter<Player>(actionBar.getThemedContext(),
                    android.R.layout.simple_spinner_dropdown_item, connectedPlayers) {
                @Override
                public View getDropDownView(int position, View convertView, ViewGroup parent) {
                    return Util.getActionBarSpinnerItemView(getContext(), convertView, parent,
                            getItem(position).getName());
                }

                @Override
                public View getView(int position, View convertView, ViewGroup parent) {
                    return Util.getActionBarSpinnerItemView(getContext(), convertView, parent,
                            getItem(position).getName());
                }
            };
            actionBar.setListNavigationCallbacks(playerAdapter, new ActionBar.OnNavigationListener() {
                @Override
                public boolean onNavigationItemSelected(int position, long id) {
                    if (!playerAdapter.getItem(position).equals(mService.getActivePlayer())) {
                        Log.i(TAG, "onNavigationItemSelected.setActivePlayer(" + playerAdapter.getItem(position)
                                + ")");
                        mService.setActivePlayer(playerAdapter.getItem(position));
                        updateUiFromPlayerState(mService.getActivePlayerState());
                    }
                    return true;
                }
            });
            if (activePlayer != null) {
                actionBar.setSelectedNavigationItem(playerAdapter.getPosition(activePlayer));
            }
        } else {
            // 0 or 1 players, disable the spinner, and either show the sole player in the
            // action bar, or the app name if there are no players.
            actionBar.setDisplayShowTitleEnabled(true);
            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);

            if (connectedPlayers.size() == 1) {
                actionBar.setTitle(connectedPlayers.get(0).getName());
            } else {
                // TODO: Alert the user if there are no connected players.
                actionBar.setTitle(R.string.app_name);
            }
        }
    }

    protected void onServiceConnected(@NonNull ISqueezeService service) {
        Log.v(TAG, "Service bound");
        mService = service;

        maybeRegisterCallbacks(mService);

        // Assume they want to connect (unless manually disconnected).
        if (!isConnected() && !isManualDisconnect()) {
            startVisibleConnection();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume...");

        // Start it and have it run forever (until it shuts itself down).
        // This is required so swapping out the activity (and unbinding the
        // service connection in onDestroy) doesn't cause the service to be
        // killed due to zero refcount.  This is our signal that we want
        // it running in the background.
        mActivity.startService(new Intent(mActivity, SqueezeService.class));

        if (mService != null) {
            maybeRegisterCallbacks(mService);
        }

        if (new Preferences(mActivity).isAutoConnect()) {
            mActivity.registerReceiver(broadcastReceiver,
                    new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
        }
    }

    /**
     * Keep track of whether callbacks have been registered
     */
    private boolean mRegisteredCallbacks;

    /**
     * This is called when the service is first connected, and whenever the activity is resumed.
     */
    private void maybeRegisterCallbacks(@NonNull ISqueezeService service) {
        if (!mRegisteredCallbacks) {
            service.getEventBus().registerSticky(this);

            mRegisteredCallbacks = true;
        }
    }

    @UiThread
    private void updateTimeDisplayTo(int secondsIn, int secondsTotal) {
        if (mFullHeightLayout) {
            if (updateSeekBar) {
                if (seekBar.getMax() != secondsTotal) {
                    seekBar.setMax(secondsTotal);
                    totalTime.setText(Util.formatElapsedTime(secondsTotal));
                }
                seekBar.setProgress(secondsIn);
                currentTime.setText(Util.formatElapsedTime(secondsIn));
            }
        } else {
            if (mProgressBar.getMax() != secondsTotal) {
                mProgressBar.setMax(secondsTotal);
            }
            mProgressBar.setProgress(secondsIn);
        }
    }

    /**
     * Update the UI based on the player state. Call this when the active player
     * changes.
     *
     * @param playerState the player state to reflect in the UI.
     */
    @UiThread
    private void updateUiFromPlayerState(@NonNull PlayerState playerState) {
        updateSongInfo(playerState);

        updatePlayPauseIcon(playerState.getPlayStatus());
        updateShuffleStatus(playerState.getShuffleStatus());
        updateRepeatStatus(playerState.getRepeatStatus());
        updatePowerMenuItems(canPowerOn(), canPowerOff());
    }

    /**
     * Update the UI when the song changes, either because the track has changed, or the
     * active player has changed.
     *
     * @param playerState the player state for the song.
     */
    @UiThread
    private void updateSongInfo(@NonNull PlayerState playerState) {
        updateTimeDisplayTo(playerState.getCurrentTimeSecond(), playerState.getCurrentSongDuration());

        Song song = playerState.getCurrentSong();

        if (song != null) {
            albumText.setText(song.getAlbumName());
            trackText.setText(song.getName());
            if (mFullHeightLayout) {
                artistText.setText(song.getArtist());
                totalTime.setText(Util.formatElapsedTime(song.getDuration()));
                if (song.isRemote()) {
                    if (song.getButtons().length() == 0) {
                        nextButton.setEnabled(false);
                        Util.setAlpha(nextButton, 0.25f);
                    } else {
                        // TODO: figure out how to parse the buttons HASH;
                        // for now just assume the next button is enabled
                        nextButton.setEnabled(true);
                        Util.setAlpha(nextButton, 1.0f);
                    }
                    prevButton.setEnabled(false);
                    Util.setAlpha(prevButton, 0.25f);
                    btnContextMenu.setVisibility(View.GONE);
                } else {
                    nextButton.setEnabled(true);
                    Util.setAlpha(nextButton, 1.0f);
                    prevButton.setEnabled(true);
                    Util.setAlpha(prevButton, 1.0f);
                    btnContextMenu.setVisibility(View.VISIBLE);
                }
            }
        } else {
            albumText.setText("");
            trackText.setText("");
            if (mFullHeightLayout) {
                artistText.setText("");
                btnContextMenu.setVisibility(View.GONE);
            }
        }

        if (song == null || !song.hasArtwork()) {
            if (mFullHeightLayout) {
                albumArt.setImageResource(song != null && song.isRemote() ? R.drawable.icon_iradio_noart_fullscreen
                        : R.drawable.icon_album_noart_fullscreen);
            } else {
                albumArt.setImageResource(song != null && song.isRemote() ? R.drawable.icon_iradio_noart
                        : R.drawable.icon_album_noart);
            }
            return;
        }

        ImageFetcher.getInstance(mActivity).loadImage(song.getArtworkUrl(), albumArt);
    }

    private boolean setSecondsElapsed(int seconds) {
        return mService != null && mService.setSecondsElapsed(seconds);
    }

    private PlayerState getPlayerState() {
        if (mService == null) {
            return null;
        }
        return mService.getPlayerState();
    }

    private Player getActivePlayer() {
        if (mService == null) {
            return null;
        }
        return mService.getActivePlayer();
    }

    private Song getCurrentSong() {
        PlayerState playerState = getPlayerState();
        return playerState != null ? playerState.getCurrentSong() : null;
    }

    private boolean isConnected() {
        return mService != null && mService.isConnected();
    }

    private boolean isConnectInProgress() {
        return mService != null && mService.isConnectInProgress();
    }

    private boolean canPowerOn() {
        return mService != null && mService.canPowerOn();
    }

    private boolean canPowerOff() {
        return mService != null && mService.canPowerOff();
    }

    @Override
    public void onPause() {
        Log.d(TAG, "onPause...");

        dismissConnectingDialog();

        if (new Preferences(mActivity).isAutoConnect()) {
            mActivity.unregisterReceiver(broadcastReceiver);
        }

        if (mRegisteredCallbacks) {
            mService.cancelSubscriptions(this);
            mService.getEventBus().unregister(this);
            mRegisteredCallbacks = false;
        }

        super.onPause();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mService != null) {
            mActivity.unbindService(serviceConnection);
        }
    }

    /**
     * Builds a context menu suitable for the currently playing song.
     * <p>
     * Takes the general song context menu, and disables items that make no sense for the song that
     * is currently playing.
     * <p>
     * {@inheritDoc}
     *
     * @param menu
     * @param v
     * @param menuInfo
     */
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getActivity().getMenuInflater();
        inflater.inflate(R.menu.songcontextmenu, menu);

        menu.findItem(R.id.play_now).setVisible(false);
        menu.findItem(R.id.play_next).setVisible(false);
        menu.findItem(R.id.add_to_playlist).setVisible(false);

        menu.findItem(R.id.view_this_album).setVisible(true);
        menu.findItem(R.id.view_albums_by_song).setVisible(true);
        menu.findItem(R.id.view_songs_by_artist).setVisible(true);
    }

    /**
     * Handles clicks on the context menu.
     * <p>
     * {@inheritDoc}
     *
     * @param item
     *
     * @return
     */
    @Override
    public boolean onContextItemSelected(MenuItem item) {
        Song song = getCurrentSong();
        if (song == null || song.isRemote()) {
            return false;
        }

        // Note: Very similar to code in SongView:doItemContext().  Refactor?
        switch (item.getItemId()) {
        case R.id.download:
            mActivity.downloadItem(song);
            return true;

        case R.id.view_this_album:
            SongListActivity.show(getActivity(), song.getAlbum());
            return true;

        case R.id.view_albums_by_song:
            AlbumListActivity.show(getActivity(), new Artist(song.getArtistId(), song.getArtist()));
            return true;

        case R.id.view_songs_by_artist:
            SongListActivity.show(getActivity(), new Artist(song.getArtistId(), song.getArtist()));
            return true;

        default:
            throw new IllegalStateException("Unknown menu ID.");
        }
    }

    /**
     * @see android.support.v4.app.Fragment#onCreateOptionsMenu(android.view.Menu,
     * android.view.MenuInflater)
     */
    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // I confess that I don't understand why using the inflater passed as
        // an argument here doesn't work -- but if you do it crashes without
        // a stracktrace on API 7.
        MenuInflater i = mActivity.getMenuInflater();
        i.inflate(R.menu.now_playing_fragment, menu);

        menu_item_connect = menu.findItem(R.id.menu_item_connect);
        menu_item_disconnect = menu.findItem(R.id.menu_item_disconnect);
        menu_item_poweron = menu.findItem(R.id.menu_item_poweron);
        menu_item_poweroff = menu.findItem(R.id.menu_item_poweroff);
        menu_item_players = menu.findItem(R.id.menu_item_players);
        menu_item_playlist = menu.findItem(R.id.menu_item_playlist);
        menu_item_alarm = menu.findItem(R.id.menu_item_alarm);
        menu_item_search = menu.findItem(R.id.menu_item_search);
    }

    /**
     * Sets the state of assorted option menu items based on whether or not there is a connection to
     * the server, and if so, whether any players are connected.
     */
    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        boolean connected = isConnected();

        // Don't show an option to connect if there's no server to connect to.
        boolean knowServerAddress = new Preferences(mActivity).getServerAddress() != null;
        menu_item_connect.setEnabled(knowServerAddress);

        // These are all set at the same time, so one check is sufficient
        if (menu_item_connect != null) {
            // Set visibility and enabled state of menu items that are not player-specific.
            menu_item_connect.setVisible(!connected);
            menu_item_disconnect.setVisible(connected);

            // Set visibility and enabled state of menu items that are player-specific and
            // require a connection to the server.
            boolean haveConnectedPlayers = connected && mService != null
                    && !mService.getConnectedPlayers().isEmpty();

            menu_item_players.setVisible(haveConnectedPlayers);
            menu_item_playlist.setVisible(haveConnectedPlayers);
            menu_item_alarm.setVisible(haveConnectedPlayers);
            if (connected)
                menu_item_alarm.setTitle(ServerString.ALARM.getLocalizedString());
            menu_item_search.setEnabled(connected);
        }

        // Don't show the item to go to CurrentPlaylistActivity if in CurrentPlaylistActivity.
        if (mActivity instanceof CurrentPlaylistActivity && menu_item_playlist != null) {
            menu_item_playlist.setVisible(false);
        }
        // Don't show the item to go to alarms if in AlarmsActivity.
        if (mActivity instanceof AlarmsActivity && menu_item_alarm != null) {
            menu_item_alarm.setVisible(false);
        }

        updatePowerMenuItems(canPowerOn(), canPowerOff());
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_item_settings:
            SettingsActivity.show(mActivity);
            return true;
        case R.id.menu_item_search:
            mActivity.onSearchRequested();
            return true;
        case R.id.menu_item_connect:
            onUserInitiatesConnect();
            return true;
        case R.id.menu_item_disconnect:
            mService.disconnect();
            return true;
        case R.id.menu_item_poweron:
            mService.powerOn();
            return true;
        case R.id.menu_item_poweroff:
            mService.powerOff();
            return true;
        case R.id.menu_item_playlist:
            CurrentPlaylistActivity.show(mActivity);
            break;
        case R.id.menu_item_players:
            PlayerListActivity.show(mActivity);
            return true;
        case R.id.menu_item_alarm:
            AlarmsActivity.show(mActivity);
            return true;
        case R.id.menu_item_about:
            new AboutDialog().show(getFragmentManager(), "AboutDialog");
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * Has the user manually disconnected from the server?
     *
     * @return true if they have, false otherwise.
     */
    private boolean isManualDisconnect() {
        return getActivity() instanceof DisconnectedActivity;
    }

    private void onUserInitiatesConnect() {
        if (mService == null) {
            Log.e(TAG, "serviceStub is null.");
            return;
        }
        startVisibleConnection();
    }

    public void startVisibleConnection() {
        Log.v(TAG, "startVisibleConnection");
        if (mService == null) {
            return;
        }

        Preferences preferences = new Preferences(mActivity);

        // If we are configured to automatically connect on Wi-Fi availability
        // we will also give the user the opportunity to enable Wi-Fi
        if (preferences.isAutoConnect()) {
            WifiManager wifiManager = (WifiManager) mActivity.getSystemService(Context.WIFI_SERVICE);
            if (!wifiManager.isWifiEnabled()) {
                FragmentManager fragmentManager = getFragmentManager();
                if (fragmentManager != null) {
                    EnableWifiDialog.show(getFragmentManager());
                } else {
                    Log.i(getTag(), "fragment manager is null so we can't show EnableWifiDialog");
                }
                return;
                // When a Wi-Fi connection is made this method will be called again by the
                // broadcastReceiver
            }
        }

        Preferences.ServerAddress serverAddress = preferences.getServerAddress();
        String ipPort = serverAddress.address;
        if (ipPort == null) {
            // Set up a server connection, if it is not present
            DisconnectedActivity.show(mActivity);
            return;
        }

        if (isConnectInProgress()) {
            Log.v(TAG, "Connection is already in progress, connecting aborted");
            return;
        }
        Log.v(TAG, "startConnect, ipPort: " + ipPort);
        mService.startConnect(ipPort, preferences.getUserName(serverAddress, "test"),
                preferences.getPassword(serverAddress, "test1"));
    }

    @MainThread
    public void onEventMainThread(ConnectionChanged event) {
        Log.d(TAG, "ConnectionChanged: " + event);

        // The fragment may no longer be attached to the parent activity.  If so, do nothing.
        if (!isAdded()) {
            return;
        }

        // Handle any of the reasons for disconnection, clear the dialog and show the
        // DisconnectedActivity.
        if (event.connectionState == ConnectionState.DISCONNECTED) {
            dismissConnectingDialog();
            DisconnectedActivity.show(mActivity);
            return;
        }

        if (event.connectionState == ConnectionState.CONNECTION_FAILED) {
            dismissConnectingDialog();
            DisconnectedActivity.showConnectionFailed(mActivity);
            return;
        }

        if (event.connectionState == ConnectionState.LOGIN_FAILED) {
            dismissConnectingDialog();
            DisconnectedActivity.showLoginFailed(mActivity);
            return;
        }

        // Any other event means that a connection is in progress, make sure the dialog is showing.
        showConnectingDialog();

        // Ensure that option menu item state is adjusted as appropriate.
        getActivity().supportInvalidateOptionsMenu();

        playPauseButton.setImageResource(mActivity.getAttributeValue(R.attr.ic_action_av_connect));

        if (mFullHeightLayout) {
            nextButton.setEnabled(false);
            prevButton.setEnabled(false);
            shuffleButton.setEnabled(false);
            repeatButton.setEnabled(false);

            albumArt.setImageResource(R.drawable.icon_album_noart_fullscreen);
            nextButton.setImageResource(0);
            prevButton.setImageResource(0);
            shuffleButton.setImageResource(0);
            repeatButton.setImageResource(0);
            updatePlayerDropDown(Collections.<Player>emptyList(), null);
            artistText.setText(getText(R.string.disconnected_text));
            currentTime.setText("--:--");
            totalTime.setText("--:--");
            seekBar.setEnabled(false);
            seekBar.setProgress(0);
        } else {
            albumArt.setImageResource(R.drawable.icon_album_noart);
            mProgressBar.setEnabled(false);
            mProgressBar.setProgress(0);
        }
    }

    @MainThread
    public void onEventMainThread(HandshakeComplete event) {
        // Event might arrive before this fragment has connected to the service (e.g.,
        // the activity connected before this fragment did).
        // XXX: Verify that this is possible, since the fragment can't register for events
        // until it's connected to the service.
        if (mService == null) {
            return;
        }

        Log.d(TAG, "Handshake complete");

        dismissConnectingDialog();

        if (mFullHeightLayout) {
            nextButton.setEnabled(true);
            prevButton.setEnabled(true);
            shuffleButton.setEnabled(true);
            repeatButton.setEnabled(true);

            nextButton.setImageResource(mActivity.getAttributeValue(R.attr.ic_action_av_next));
            prevButton.setImageResource(mActivity.getAttributeValue(R.attr.ic_action_av_previous));
            seekBar.setEnabled(true);
        } else {
            mProgressBar.setEnabled(true);
        }

        PlayerState playerState = getPlayerState();

        // May be no players connected.
        // TODO: These views should be cleared if there's no player connected.
        if (playerState == null)
            return;

        updateUiFromPlayerState(playerState);
    }

    @MainThread
    public void onEventMainThread(MusicChanged event) {
        if (event.player.equals(mService.getActivePlayer())) {
            updateSongInfo(event.playerState);
        }
    }

    @MainThread
    public void onEventMainThread(PlayersChanged event) {
        updatePlayerDropDown(event.players.values(), mService.getActivePlayer());
        updateUiFromPlayerState(mService.getActivePlayerState());
    }

    @MainThread
    public void onEventMainThread(PlayStatusChanged event) {
        updatePlayPauseIcon(event.playStatus);
    }

    @MainThread
    public void onEventMainThread(PowerStatusChanged event) {
        if (event.player.equals(mService.getActivePlayer())) {
            updatePowerMenuItems(event.canPowerOn, event.canPowerOff);
        }
    }

    @MainThread
    public void onEventMainThread(RepeatStatusChanged event) {
        if (event.player.equals(mService.getActivePlayer())) {
            updateRepeatStatus(event.repeatStatus);
            if (!event.initial) {
                Toast.makeText(mActivity, mActivity.getServerString(event.repeatStatus.getText()),
                        Toast.LENGTH_SHORT).show();
            }
        }
    }

    @MainThread
    public void onEventMainThread(ShuffleStatusChanged event) {
        if (event.player.equals(mService.getActivePlayer())) {
            updateShuffleStatus(event.shuffleStatus);
            if (!event.initial) {
                Toast.makeText(mActivity, mActivity.getServerString(event.shuffleStatus.getText()),
                        Toast.LENGTH_SHORT).show();
            }
        }
    }

    @MainThread
    public void onEventMainThread(SongTimeChanged event) {
        if (event.player.equals(mService.getActivePlayer())) {
            updateTimeDisplayTo(event.currentPosition, event.duration);
        }
    }
}