/*
* MediaPlayerDemoScreen.java
*
* Copyright � 1998-2011 Research In Motion Limited
*
* 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.
*
* Note: For the sake of simplicity, this sample application may not leverage
* resource bundles and resource strings. However, it is STRONGLY recommended
* that application developers make use of the localization features available
* within the BlackBerry development platform to ensure a seamless application
* experience across a variety of languages and geographies. For more information
* on localizing your application, please refer to the BlackBerry Java Development
* Environment Development Guide associated with this release.
*/
package com.rim.samples.device.mediakeysdemo.mediaplayerdemo.mediaplayerlib;
import net.rim.device.api.command.Command;
import net.rim.device.api.command.CommandHandler;
import net.rim.device.api.command.ReadOnlyCommandMetadata;
import net.rim.device.api.media.MediaActionHandler;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.Keypad;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.MenuItem;
import net.rim.device.api.ui.Screen;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.XYEdges;
import net.rim.device.api.ui.XYRect;
import net.rim.device.api.ui.component.BitmapField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;
import net.rim.device.api.ui.decor.Border;
import net.rim.device.api.ui.decor.BorderFactory;
import net.rim.device.api.util.StringProvider;
/**
* This class handles the user interface for the MediaPlayerDemo
*/
public class MediaPlayerDemoScreen extends MainScreen {
public static final int STATE_STOPPED = 84300;
public static final int STATE_PLAYING = 84301;
public static final int STATE_PAUSED = 84302;
public static final String INSTRUCTION_TEXT =
"If your device does not have dedicated media keys, you can:"
+ "\n1) Use the mute key to toggle play and pause \n2) Hold the volume keys to change tracks";
private final MediaPlayerDemoScreenManager _manager;
private Runnable _onCloseRunnable;
/**
* Constructs a new MediaPlayerDemoScreen object
*
* @param mediaPlayerDemo
* The instance of MediaPlayerDemo with which to interact
* @throws NullPointerException
* If mediaPlayerDemo==null
*/
public MediaPlayerDemoScreen(final MediaPlayerDemo mediaPlayerDemo) {
super(NO_VERTICAL_SCROLL);
if (mediaPlayerDemo == null) {
throw new NullPointerException("mediaPlayerDemo==null");
}
_manager = new MediaPlayerDemoScreenManager(mediaPlayerDemo);
_manager.getControlsField().getVolumeField().setLevel(
mediaPlayerDemo.getVolume());
add(_manager);
addMenuItem(new HelpMenuItem());
setTitle("Media Player Demo");
}
/**
* Sets a Runnable to be invoked when this screen is removed from the
* display stack. The specified Runnable is only ever invoked once and the
* reference to it is cleared after its run() method is invoked.
*
* @param runnable
* A Runnable whose run() method is invoked when this method is
* removed from the display stack. May be null.
*/
public void setOnCloseRunnable(final Runnable runnable) {
_onCloseRunnable = runnable;
}
/**
* @see Screen#close()
*/
public void close() {
final Runnable runnable = _onCloseRunnable;
if (runnable != null) {
_onCloseRunnable = null;
try {
runnable.run();
} catch (final Throwable e) {
}
}
super.close();
}
/**
* Retrieves the current track
*
* @return A PlayListEntry object for the current track
*/
public PlaylistEntry getCurrentPlaylistEntry() {
return _manager.getPlaylistField().getCurrentPlaylistEntry();
}
/**
* Retrieves the index of the current track
*
* @return The index of the current track
*/
public int getPlaylistIndex() {
return _manager.getPlaylistField().getPlaylistIndex();
}
/**
* Retrieves the size of the playlist
*
* @return The size of the playlist
*/
public int getPlaylistSize() {
return _manager.getPlaylistField().getPlaylistSize();
}
/**
* Retrieves the current volume level
*
* @return The current volume level
*/
public int getVolume() {
return _manager.getControlsField().getVolumeField().getLevel();
}
/**
* Sets the status of the mute field
*
* @param muted
* true If application should be muted, false otherwise
*/
public void setMuted(final boolean muted) {
_manager.getControlsField().getVolumeField().setMuted(muted);
}
/**
* Sets the size of the playlist that must be added to the MediaPlayerDemo
*
* @param playlist
* The PlaylistEntry objects that are to be added
*/
public void setPlaylist(final PlaylistEntry[] playlist) {
final PlaylistField field = _manager.getPlaylistField();
field.setEntries(playlist);
}
/**
* Sets the index of the current track
*
* @param index
* The index of the track to be set
*/
public void setPlaylistIndex(final int index) {
_manager.getPlaylistField().setPlaylistIndex(index);
}
/**
* Sets the current state of the player
*
* @param state
* The state that the player must be set to
*/
public void setPlayState(final int state) {
_manager.getControlsField().setPlayState(state);
}
/**
* Sets the volume level
*
* @param volume
* The new volume level
*/
public void setVolume(final int volume) {
_manager.getControlsField().getVolumeField().setLevel(volume);
}
/**
* This class handles the individual UI Buttons of the MediaPlayerDemo,
* specifically the play, pause, forward and reverse buttons.
*/
private static final class ControlField extends BitmapField {
/**
* Constructs a new ControlField object
*
* @param prefix
* The prefix of the file name of the image to be loaded
*/
public ControlField(final String prefix) {
super(loadBitmap(prefix), FOCUSABLE);
final XYEdges borderWidths = new XYEdges(2, 2, 2, 2);
final Border border =
BorderFactory.createRoundedBorder(borderWidths);
setBorder(border);
}
/**
* Indicates that an element in the field has been changed
*/
private void fireFieldChanged() {
final FieldChangeListener listener = getChangeListener();
if (listener != null) {
listener.fieldChanged(this, 0);
}
}
/**
* @see net.rim.device.api.ui.Field#keyDown(int, int)
*/
protected boolean keyDown(final int keycode, final int time) {
final int key = Keypad.key(keycode);
if (key == Keypad.KEY_ENTER) {
fireFieldChanged();
return true;
}
return super.keyDown(keycode, time);
}
/**
* @see net.rim.device.api.ui.Field#navigationClick(int, int)
*/
protected boolean navigationClick(final int status, final int time) {
fireFieldChanged();
return true;
}
/**
* @see net.rim.device.api.ui.Field#touchEvent(TouchEvent)
*/
protected boolean touchEvent(final TouchEvent message) {
switch (message.getEvent()) {
case TouchEvent.CLICK:
fireFieldChanged();
return true;
case TouchEvent.UNCLICK:
// Return true in order to consume the touch
// event and prevent the menu from opening.
return true;
}
return super.touchEvent(message);
}
/**
* Creates and returns a Bitmap object as specified by the prefix
* argument.
*
* @param prefix
* The prefix of the image file from which to create the
* Bitmap
* @return A Bitmap object containing the image as specified by the
* prefix argument
*/
private static Bitmap loadBitmap(final String prefix) {
if (prefix == null) {
throw new NullPointerException("imgName==null");
}
final String imgResourcePath = prefix + ".png";
final Bitmap bitmap = Bitmap.getBitmapResource(imgResourcePath);
return bitmap;
}
}
/**
* This class organizes the ControlField objects into a horizontal field
*/
private static final class ControlsField extends HorizontalFieldManager
implements FieldChangeListener {
private final MediaActionHandler _handler;
private final ControlField _prevButton;
private final ControlField _playButton;
private final ControlField _pauseButton;
private final ControlField _nextButton;
private final ControlField _stopButton;
private final VolumeField _volumeField;
/**
* Creates a new ControlsField object
*
* @param mediaPlayerDemo
* The instance of MediaPlayerDemo with which to interact
* @throws NullPointerException
* If mediaPlayerDemo==null
*/
public ControlsField(final MediaPlayerDemo mediaPlayerDemo) {
super(HORIZONTAL_SCROLL);
if (mediaPlayerDemo == null) {
throw new NullPointerException("mediaPlayerDemo==null");
}
_handler = mediaPlayerDemo;
_prevButton = createButton("BtnPrev");
_playButton = createButton("BtnPlay");
_pauseButton = createButton("BtnPause");
_nextButton = createButton("BtnNext");
_stopButton = createButton("BtnStop");
_volumeField =
new VolumeField(mediaPlayerDemo, mediaPlayerDemo
.getVolume(), false);
add(_prevButton);
add(_playButton);
add(_stopButton);
add(_nextButton);
add(_volumeField);
}
/**
* Creates a ControlField object
*
* @param prefix
* The prefix of the file name to be used as an image in the
* field
* @return The ControlField object containing the image specified by the
* prefix argument
*/
private ControlField createButton(final String prefix) {
final ControlField field = new ControlField(prefix);
field.setChangeListener(this);
return field;
}
/**
* @see net.rim.device.api.ui.FieldChangeListener#fieldChanged(Field,
* int)
*/
public void fieldChanged(final Field field, final int context) {
if (field == _prevButton) {
invokeMediaAction(MediaActionHandler.MEDIA_ACTION_PREV_TRACK);
} else if (field == _nextButton) {
invokeMediaAction(MediaActionHandler.MEDIA_ACTION_NEXT_TRACK);
} else if (field == _playButton) {
invokeMediaAction(MediaActionHandler.MEDIA_ACTION_PLAY);
} else if (field == _pauseButton) {
invokeMediaAction(MediaActionHandler.MEDIA_ACTION_PAUSE);
} else if (field == _stopButton) {
invokeMediaAction(MediaActionHandler.MEDIA_ACTION_STOP);
}
}
/**
* Invokes the specified media action
*
* @param action
* The media action to be invoked
*/
private void invokeMediaAction(final int action) {
_handler.mediaAction(action,
MediaPlayerDemo.MEDIA_ACTION_SOURCE_UI, null);
}
/**
* Retrieves the VolumeField object
*
* @return The VolumeField object
*/
public VolumeField getVolumeField() {
return _volumeField;
}
/**
* Sets the current state of the player in order to draw the appropriate
* UI elements.
*
* @param state
* The state that the player should be set to
*/
public void setPlayState(final int state) {
Field oldField, newField;
switch (state) {
case STATE_PLAYING:
oldField = _playButton;
newField = _pauseButton;
break;
case STATE_PAUSED:
case STATE_STOPPED:
oldField = _pauseButton;
newField = _playButton;
break;
default:
return;
}
final int index = oldField.getIndex();
if (index >= 0) {
replace(oldField, newField);
}
}
}
/**
* A menu item that displays a help screen to the user when selected
*/
private static class HelpMenuItem extends MenuItem {
/**
* Creates a new instance of HelpMenuItem
*/
public HelpMenuItem() {
super(new StringProvider("Help"), 0x230010, 0);
this.setCommand(new Command(new CommandHandler() {
/**
* @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata,
* Object)
*/
public void execute(final ReadOnlyCommandMetadata metadata,
final Object context) {
final HelpScreen screen =
new HelpScreen("Help", INSTRUCTION_TEXT);
UiApplication.getUiApplication().pushScreen(screen);
}
}));
}
}
/**
* This class organizes the UI components of the MediaPlayerDemo
*/
private static class MediaPlayerDemoScreenManager extends Manager {
private final PlaylistField _playlistField;
private final ControlsField _controlsField;
/**
* Creates a new MediaPlayerDemoScreenManager object
*
* @param mediaPlayerDemo
* The instance of MediaPlayerDemo with which to interact
* @throws NullPointerException
* If mediaPlayerDemo==null
*/
public MediaPlayerDemoScreenManager(
final MediaPlayerDemo mediaPlayerDemo) {
super(USE_ALL_HEIGHT | USE_ALL_WIDTH | NO_VERTICAL_SCROLL);
_playlistField = new PlaylistField(mediaPlayerDemo);
_controlsField = new ControlsField(mediaPlayerDemo);
add(_playlistField);
add(_controlsField);
}
/**
* Retrieves the instance of the ControlsField class
*
* @return The ControlsField object
*/
public ControlsField getControlsField() {
return _controlsField;
}
/**
* Retrieves the instance of the PlaylistField class
*
* @return The PlaylistField object
*/
public PlaylistField getPlaylistField() {
return _playlistField;
}
/**
* @see net.rim.device.api.ui.Manager#sublayout(int, int)
*/
protected void sublayout(final int width, final int height) {
final int playlistHeight =
height - _controlsField.getPreferredHeight();
setPositionChild(_playlistField, 0, 0);
layoutChild(_playlistField, width, playlistHeight);
setPositionChild(_controlsField, 0, playlistHeight);
layoutChild(_controlsField, width, _controlsField
.getPreferredHeight());
setExtent(width, height);
}
}
/**
* This class handles the UI for the current track
*/
private static class PlaylistEntryField extends Field {
private final PlaylistEntry _entry;
private final String _displayText;
private boolean _current;
/**
* Constructs a new PlaylistEntryField object
*
* @param entry
* The playlist entry for the current track
*/
public PlaylistEntryField(final PlaylistEntry entry) {
super(FOCUSABLE);
if (entry == null) {
throw new NullPointerException("entry==null");
}
_entry = entry;
_displayText = entry.getName();
_current = false;
}
/**
* Retrieves the current track
*
* @return A PlaylistEntry object for the current track
*/
public PlaylistEntry getEntry() {
return _entry;
}
/**
* @see net.rim.device.api.ui.Field#getPreferredHeight()
*/
public int getPreferredHeight() {
return getFont().getHeight();
}
/**
* @see net.rim.device.api.ui.Field#getPreferredWidth()
*/
public int getPreferredWidth() {
return Display.getWidth();
}
/**
* Sets the selected track as the current track
*
* @param current
* True if the selected track should be set as the current
* track, false otherwise
*/
public void setCurrent(final boolean current) {
if (current != _current) {
_current = current;
invalidate();
}
}
/**
* @see net.rim.device.api.ui.Field#layout(int, int)
*/
protected void layout(int width, int height) {
final Font font = getFont();
width = Math.min(width, font.getAdvance(_displayText) + height);
height = Math.min(height, font.getHeight());
setExtent(width, height);
}
/**
* @see net.rim.device.api.ui.Field#paint(Graphics)
*/
protected void paint(final Graphics graphics) {
final int height = getExtent().height;
// The start of the field is a square where the "current" indicator
// is drawn.
if (_current) {
int radius = (height - 10) / 2;
if (radius <= 0) {
radius = 1;
}
final int cx = height / 2; // X coordinate of ellipse's center
final int cy = height / 2; // Y coordinate of ellipse's center
final int px = cx + radius; // X coordinate of right most point
final int py = cy; // Y coordinate of right most point
final int qx = cx; // X coordinate of bottom most point
final int qy = cy + radius; // Y coordinate of bottom most point
graphics.fillEllipse(cx, cy, px, py, qx, qy, 0, 360);
}
graphics.drawText(_displayText, height, 0);
}
}
/**
* This class manages the playlist field and organizes the tracks that are
* put into it using a VerticalFieldManager.
*/
private static class PlaylistField extends VerticalFieldManager {
private final MediaActionHandler _handler;
/**
* Creates a new PlaylistField object
*
* @param handler
* The MediaActionHandler to notify when relevant events
* occur in this field.
*/
public PlaylistField(final MediaActionHandler handler) {
super(VERTICAL_SCROLL);
if (handler == null) {
throw new NullPointerException("handler==null");
}
_handler = handler;
}
/**
* Retrieves the current track
*
* @return A PlaylistEntry object with the current track
*/
public PlaylistEntry getCurrentPlaylistEntry() {
final int index = getPlaylistIndex();
if (index < 0) {
return null;
}
final PlaylistEntryField entryField =
(PlaylistEntryField) getField(index);
return entryField.getEntry();
}
/**
* Retrieves the index of the current track
*
* @return The index of the current track. If there is no current track,
* -1 is returned
*/
public int getPlaylistIndex() {
return ((MediaPlayerDemo) _handler).getCurrentTrackIndex();
}
/**
* Retrieves the size of the playlist
*
* @return The size of the playlist
*/
public int getPlaylistSize() {
return getFieldCount();
}
/**
* @see net.rim.device.api.ui.Manager#keyDown(int, int)
*/
protected boolean keyDown(final int keycode, final int time) {
final int key = Keypad.key(keycode);
if (key == Keypad.KEY_ENTER) {
setEntryWithFocusAsCurrent();
return true;
}
return super.keyDown(keycode, time);
}
/**
* @see net.rim.device.api.ui.Manager#navigationClick(int, int)
*/
protected boolean navigationClick(final int status, final int time) {
setEntryWithFocusAsCurrent();
return true;
}
/**
* Sets the tracks in the playlist
*
* @param entries
* The PlayListEntry objects that should be added to the
* playlist
*/
public void setEntries(final PlaylistEntry[] entries) {
deleteAll();
final int numEntries = entries == null ? 0 : entries.length;
boolean isCurrentSet = false;
for (int i = 0; i < numEntries; i++) {
final PlaylistEntry entry = entries[i];
if (entry != null) {
final PlaylistEntryField field =
new PlaylistEntryField(entry);
if (!isCurrentSet) {
field.setCurrent(true);
isCurrentSet = true;
}
add(field);
}
}
}
/**
* Sets the selected track as the current track
*/
public void setEntryWithFocusAsCurrent() {
final int index = getFieldWithFocusIndex();
if (index != getPlaylistIndex()) {
((MediaPlayerDemo) _handler).setCurrentTrackIndex(index);
_handler.mediaAction(MediaPlayerDemo.MEDIA_ACTION_CHANGE_TRACK,
MediaPlayerDemo.MEDIA_ACTION_SOURCE_UI, null);
}
}
/**
* Sets the current track
*
* @param index
* The index of the track to set
*/
public void setPlaylistIndex(int index) {
final int fieldCount = getFieldCount();
if (fieldCount == 0) {
return;
} else if (index < 0) {
index = 0;
} else if (index >= fieldCount) {
index = fieldCount - 1;
}
for (int i = 0; i < fieldCount; i++) {
final Field field = getField(i);
if (field instanceof PlaylistEntryField) {
final PlaylistEntryField entryField =
(PlaylistEntryField) field;
if (i == index) {
entryField.setCurrent(true);
} else {
entryField.setCurrent(false);
}
}
}
}
/**
* @see net.rim.device.api.ui.Manager#touchEvent(TouchEvent)
*/
protected boolean touchEvent(final TouchEvent message) {
final int event = message.getEvent();
switch (event) {
case TouchEvent.CLICK:
setEntryWithFocusAsCurrent();
return true;
case TouchEvent.UNCLICK:
// Prevents the "reduced" menu from opening
return true;
}
return super.touchEvent(message);
}
}
/**
* This class manages the volume field
*/
private static class VolumeField extends LabelField {
private final MediaPlayerDemo _mediaPlayerDemo;
private int _level;
private boolean _muted;
/**
* Constructs a new VolumeField object
*
* @param mediaPlayerDemo
* The instance of MediaPlayerDemo with which to interact
* @param level
* The volume level
* @param muted
* True if the volume should be muted, false otherwise
* @throws NullPointerException
* If mediaPlayerDemo==null.
*/
public VolumeField(final MediaPlayerDemo mediaPlayerDemo,
final int level, final boolean muted) {
super(null, FIELD_VCENTER | FOCUSABLE);
if (mediaPlayerDemo == null) {
throw new NullPointerException("mediaPlayerDemo==null");
}
_mediaPlayerDemo = mediaPlayerDemo;
_level = level;
_muted = muted;
}
/**
* Retrieves the current volume level
*
* @return The current volume level
*/
public int getLevel() {
return _level;
}
/**
* Checks whether the volume is muted
*
* @return true If application is muted, false otherwise
*/
public boolean isMuted() {
return _muted;
}
/**
* Invokes the mediaAction() method of the MediaPlayerDemo with an
* action corresponding to whether or not mute is toggled.
*/
public void dispatchMuteEvent() {
int action;
if (isMuted()) {
action = MediaActionHandler.MEDIA_ACTION_UNMUTE;
} else {
action = MediaActionHandler.MEDIA_ACTION_MUTE;
}
_mediaPlayerDemo.mediaAction(action,
MediaPlayerDemo.MEDIA_ACTION_SOURCE_UI, null);
}
/**
* @see net.rim.device.api.ui.Field#keyDown(int, int)
*/
protected boolean keyDown(final int keycode, final int time) {
final int key = Keypad.key(keycode);
if (key == Keypad.KEY_ENTER) {
dispatchMuteEvent();
return true;
}
return super.keyDown(keycode, time);
}
/**
* @see net.rim.device.api.ui.Field#navigationClick(int, int)
*/
protected boolean navigationClick(final int status, final int time) {
dispatchMuteEvent();
return true;
}
/**
* @see net.rim.device.api.ui.Field#paint(Graphics)
*/
protected void paint(final Graphics graphics) {
super.paint(graphics);
// If muted, draw an X through the volume field
if (_muted) {
final XYRect extent = getExtent();
graphics.drawLine(0, 0, extent.width, extent.height);
graphics.drawLine(0, extent.height, extent.width, 0);
}
}
/**
* Sets the volume level
*
* @param level
* The new volume level
*/
public void setLevel(final int level) {
_level = level;
setText("Vol: " + level + "%");
}
/**
* Sets the mute state
*
* @param muted
* true If application should be muted, false otherwise
*/
public void setMuted(final boolean muted) {
if (_muted != muted) {
_muted = muted;
invalidate();
}
}
/**
* @see net.rim.device.api.ui.Field#touchEvent(TouchEvent)
*/
protected boolean touchEvent(final TouchEvent message) {
final int event = message.getEvent();
switch (event) {
case TouchEvent.CLICK:
dispatchMuteEvent();
return true;
case TouchEvent.UNCLICK:
// Prevents the "reduced" menu from opening
return true;
}
return super.touchEvent(message);
}
}
}