/*
* Copyright (C) 2005-2009 Team XBMC
* http://xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC Remote; see the file license. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
package org.xbmc.android.remote.presentation.controller;
import java.util.ArrayList;
import java.util.HashMap;
import org.xbmc.android.remote.R;
import org.xbmc.android.remote.business.ManagerFactory;
import org.xbmc.android.remote.business.NowPlayingPollerThread;
import org.xbmc.android.remote.presentation.activity.PlaylistActivity;
import org.xbmc.android.remote.presentation.widget.OneLabelItemView;
import org.xbmc.android.util.ConnectionFactory;
import org.xbmc.api.business.DataResponse;
import org.xbmc.api.business.IControlManager;
import org.xbmc.api.business.IEventClientManager;
import org.xbmc.api.business.IMusicManager;
import org.xbmc.api.data.IControlClient.ICurrentlyPlaying;
import org.xbmc.api.info.PlayStatus;
import org.xbmc.api.object.INamedResource;
import org.xbmc.api.object.Song;
import org.xbmc.eventclient.ButtonCodes;
import org.xbmc.httpapi.client.MusicClient;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
public class MusicPlaylistController extends ListController implements IController, Callback {
public static final String TAG = "MusicPlaylistLogic";
public static final int ITEM_CONTEXT_PLAY = 1;
public static final int ITEM_CONTEXT_REMOVE = 2;
public static final int MESSAGE_PLAYLIST_SIZE = 701;
public static final String BUNDLE_PLAYLIST_SIZE = "playlist_size";
private PlaylistActivity mPlaylistActivity;
private Handler mNowPlayingHandler;
private SongAdapter mSongAdapter;
private IControlManager mControlManager;
private IMusicManager mMusicManager;
private IEventClientManager mEventClient;
private int mPlayStatus = PlayStatus.UNKNOWN;
private int mPlayListId = -1;
private int mCurrentPosition = -1;
private int mLastPosition = -1;
private static Bitmap sPlayingBitmap;
public void onCreate(final PlaylistActivity activity, Handler handler, final AbsListView list) {
mPlaylistActivity = activity;
mMusicManager = ManagerFactory.getMusicManager(this);
mControlManager = ManagerFactory.getControlManager(this);
mEventClient = ManagerFactory.getEventClientManager(this);
mNowPlayingHandler = new Handler(this);
if (!isCreated()) {
super.onCreate(activity, handler, list);
activity.registerForContextMenu(mList);
mFallbackBitmap = BitmapFactory.decodeResource(activity.getResources(), R.drawable.icon_song_light);
sPlayingBitmap = BitmapFactory.decodeResource(activity.getResources(), R.drawable.icon_play);
mMusicManager.getPlaylistPosition(new DataResponse<Integer>() {
public void run() {
mCurrentPosition = value;
}
}, mActivity.getApplicationContext());
mMusicManager.getPlaylist(new DataResponse<ArrayList<String>>() {
public void run() {
if (value.size() > 0) {
final ArrayList<PlaylistItem> items = new ArrayList<PlaylistItem>();
int i = 0;
for (String path : value) {
items.add(new PlaylistItem(path, i++));
}
setTitle("Music playlist (" + (value.size() > MusicClient.PLAYLIST_LIMIT ? MusicClient.PLAYLIST_LIMIT + "+" : value.size()) + ")" );
mSongAdapter = new SongAdapter(activity, items);
((AdapterView<ListAdapter>) mList).setAdapter(mSongAdapter);
if (mCurrentPosition >= 0) {
mList.setSelection(mCurrentPosition);
}
} else {
setTitle("Music playlist");
setNoDataMessage("No tracks in playlist.", R.drawable.icon_playlist_dark);
}
}
}, mActivity.getApplicationContext());
mList.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final PlaylistItem item = (PlaylistItem)mList.getAdapter().getItem(((OneLabelItemView)view).position);
final DataResponse<Boolean> doNothing = new DataResponse<Boolean>();
mControlManager.setPlaylistId(doNothing, mPlayListId < 0 ? 0 : mPlayListId, mActivity.getApplicationContext());
mMusicManager.setPlaylistSong(doNothing, item.position, mActivity.getApplicationContext());
}
});
mList.setOnKeyListener(new ListControllerOnKeyListener<Song>());
setTitle("Music playlist...");
}
}
/**
* This is called from the thread with a message containing updated info of
* what's currently playing.
*
* @param msg
* Message object containing currently playing info
*/
public synchronized boolean handleMessage(Message msg) {
final Bundle data = msg.getData();
final ICurrentlyPlaying currentlyPlaying = (ICurrentlyPlaying) data.getSerializable(NowPlayingPollerThread.BUNDLE_CURRENTLY_PLAYING);
switch (msg.what) {
case NowPlayingPollerThread.MESSAGE_PROGRESS_CHANGED:
mPlayStatus = currentlyPlaying.getPlayStatus();
if (currentlyPlaying.isPlaying()) {
mPlaylistActivity.setTime(Song.getDuration(currentlyPlaying.getTime() + 1));
} else {
mPlaylistActivity.clear();
}
return true;
case NowPlayingPollerThread.MESSAGE_PLAYLIST_ITEM_CHANGED:
mLastPosition = data.getInt(NowPlayingPollerThread.BUNDLE_LAST_PLAYPOSITION);
onTrackChanged(currentlyPlaying);
return true;
case NowPlayingPollerThread.MESSAGE_PLAYSTATE_CHANGED:
mPlayListId = data.getInt(NowPlayingPollerThread.BUNDLE_LAST_PLAYLIST);
return true;
case MESSAGE_PLAYLIST_SIZE:
final int size = msg.getData().getInt(BUNDLE_PLAYLIST_SIZE);
mPlaylistActivity.setNumItems(size == 0 ? "empty" : size + " tracks");
return true;
case NowPlayingPollerThread.MESSAGE_CONNECTION_ERROR:
case NowPlayingPollerThread.MESSAGE_RECONFIGURE:
mPlayStatus = PlayStatus.UNKNOWN;
return true;
default:
return false;
}
}
public void setupButtons(View prev, View stop, View playpause, View next) {
// setup buttons
prev.setOnClickListener(new OnRemoteAction(ButtonCodes.REMOTE_SKIP_MINUS));
stop.setOnClickListener(new OnRemoteAction(ButtonCodes.REMOTE_STOP));
next.setOnClickListener(new OnRemoteAction(ButtonCodes.REMOTE_SKIP_PLUS));
playpause.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
switch (mPlayStatus) {
case PlayStatus.PLAYING:
mEventClient.sendButton("R1", ButtonCodes.REMOTE_PAUSE, false, true, true, (short)0, (byte)0);
break;
case PlayStatus.PAUSED:
mEventClient.sendButton("R1", ButtonCodes.REMOTE_PLAY, false, true, true, (short)0, (byte)0);
break;
case PlayStatus.STOPPED:
final DataResponse<Boolean> doNothing = new DataResponse<Boolean>();
//mControlManager.setPlaylistId(doNothing, mPlayListId < 0 ? 0 : mPlayListId, mActivity.getApplicationContext());
mControlManager.setPlaylistPos(doNothing, mPlayListId < 0 ? 0 : mPlayListId, mLastPosition < 0 ? 0 : mLastPosition, mActivity.getApplicationContext());
break;
}
}
});
}
/**
* Handles the push- release button code. Switches image of the pressed
* button, vibrates and executes command.
*/
private class OnRemoteAction implements OnClickListener {
private final String mAction;
public OnRemoteAction(String action) {
mAction = action;
}
public void onClick(View v) {
mEventClient.sendButton("R1", mAction, false, true, true, (short) 0, (byte) 0);
}
}
public void onTrackChanged(ICurrentlyPlaying newSong) {
final SongAdapter adapter = mSongAdapter;
if (adapter != null) {
final int currentPos = mCurrentPosition;
final int newPos = newSong.getPlaylistPosition();
// clear previous song's icon
OneLabelItemView view = adapter.getViewAtPosition(currentPos);
if (currentPos >= 0 && view != null) {
view.setCover(BitmapFactory.decodeResource(mActivity.getResources(), R.drawable.icon_song_light));
Log.i(TAG, "Resetting previous icon at position " + currentPos + " (" + view.title + ")");
} else {
Log.i(TAG, "NOT resetting previous icon at position " + currentPos);
}
// set new song's play icon
view = adapter.getViewAtPosition(newPos);
mCurrentPosition = newPos;
if (view != null) {
view.setCover(BitmapFactory.decodeResource(mActivity.getResources(), R.drawable.icon_play));
} else {
mList.setSelection(newPos);
}
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
// be aware that this must be explicitly called by your activity!
/* final OneHolder<PlaylistItem>holder = (OneHolder<PlaylistItem>)((AdapterContextMenuInfo)menuInfo).targetView.getTag();
menu.setHeaderTitle(holder.holderItem.filename);
menu.add(0, ITEM_CONTEXT_PLAY, 1, "Play");
menu.add(0, ITEM_CONTEXT_REMOVE, 2, "Remove");*/
}
public void onContextItemSelected(MenuItem item) {
// be aware that this must be explicitly called by your activity!
final PlaylistItem playlistItem = (PlaylistItem)mList.getAdapter().getItem(((OneLabelItemView)((AdapterContextMenuInfo)item.getMenuInfo()).targetView).position);
switch (item.getItemId()) {
case ITEM_CONTEXT_PLAY:
mMusicManager.setPlaylistSong(new DataResponse<Boolean>(), playlistItem.position, mActivity.getApplicationContext());
break;
case ITEM_CONTEXT_REMOVE:
mMusicManager.removeFromPlaylist(new DataResponse<Boolean>(), playlistItem.path, mActivity.getApplicationContext());
break;
default:
return;
}
}
private class SongAdapter extends ArrayAdapter<PlaylistItem> {
private final HashMap<Integer, OneLabelItemView> mItemPositions = new HashMap<Integer, OneLabelItemView>();
SongAdapter(Activity activity, ArrayList<PlaylistItem> items) {
super(activity, 0, items);
Handler handler = mNowPlayingHandler;
if (handler != null) {
Message msg = Message.obtain();
Bundle bundle = msg.getData();
bundle.putInt(BUNDLE_PLAYLIST_SIZE, items.size());
msg.what = MESSAGE_PLAYLIST_SIZE;
handler.sendMessage(msg);
}
}
public View getView(int position, View convertView, ViewGroup parent) {
final OneLabelItemView view;
if (convertView == null) {
view = new OneLabelItemView(mActivity, parent.getWidth(), mFallbackBitmap, mList.getSelector(), true);
} else {
view = (OneLabelItemView)convertView;
mItemPositions.remove(view.position);
}
final PlaylistItem item = this.getItem(position);
view.reset();
view.position = position;
view.title = item.filename;
if (position == mCurrentPosition) {
view.setCover(sPlayingBitmap);
} else {
view.setCover(mFallbackBitmap);
}
mItemPositions.put(view.position, view);
return view;
}
public OneLabelItemView getViewAtPosition(int position) {
if (mItemPositions.containsKey(position)) {
return mItemPositions.get(position);
}
return null;
}
}
private static class PlaylistItem implements INamedResource{
public final String path;
public final String filename;
public final int position;
public PlaylistItem(String path, int position) {
this.path = path;
this.filename = path.substring(path.replaceAll("\\\\", "/").lastIndexOf('/') + 1);
this.position = position;
}
public String getShortName() {
return filename;
}
}
public void onActivityPause() {
ConnectionFactory.unSubscribeNowPlayingPollerThread(mActivity.getApplicationContext(), mNowPlayingHandler, true);
if (mMusicManager != null) {
mMusicManager.setController(null);
mMusicManager.postActivity();
}
if (mControlManager != null) {
mControlManager.setController(null);
}
if (mEventClient != null) {
mEventClient.setController(null);
}
super.onActivityPause();
}
public void onActivityResume(Activity activity) {
super.onActivityResume(activity);
ConnectionFactory.subscribeNowPlayingPollerThread(activity.getApplicationContext(), mNowPlayingHandler);
if (mEventClient != null) {
mEventClient.setController(this);
}
if (mMusicManager != null) {
mMusicManager.setController(this);
}
if (mControlManager != null) {
mControlManager.setController(this);
}
}
private static final long serialVersionUID = 755529227668553163L;
}