/* * 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); } } }