Merge branch 'master' into develop
[pub/Android/ownCloud.git] / src / com / owncloud / android / media / MediaService.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2013 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 package com.owncloud.android.media;
19
20 import android.accounts.Account;
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.app.Service;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.media.AudioManager;
28 import android.media.MediaPlayer;
29 import android.media.MediaPlayer.OnCompletionListener;
30 import android.media.MediaPlayer.OnErrorListener;
31 import android.media.MediaPlayer.OnPreparedListener;
32 import android.net.wifi.WifiManager;
33 import android.net.wifi.WifiManager.WifiLock;
34 import android.os.IBinder;
35 import android.os.PowerManager;
36 import android.widget.Toast;
37
38 import java.io.IOException;
39
40 import com.owncloud.android.R;
41 import com.owncloud.android.datamodel.OCFile;
42 import com.owncloud.android.ui.activity.FileActivity;
43 import com.owncloud.android.ui.activity.FileDisplayActivity;
44 import com.owncloud.android.utils.Log_OC;
45
46
47 /**
48 * Service that handles media playback, both audio and video.
49 *
50 * Waits for Intents which signal the service to perform specific operations: Play, Pause,
51 * Rewind, etc.
52 *
53 * @author David A. Velasco
54 */
55 public class MediaService extends Service implements OnCompletionListener, OnPreparedListener,
56 OnErrorListener, AudioManager.OnAudioFocusChangeListener {
57
58 private static final String TAG = MediaService.class.getSimpleName();
59
60 private static final String MY_PACKAGE = MediaService.class.getPackage() != null ? MediaService.class.getPackage().getName() : "com.owncloud.android.media";
61
62 /// Intent actions that we are prepared to handle
63 public static final String ACTION_PLAY_FILE = MY_PACKAGE + ".action.PLAY_FILE";
64 public static final String ACTION_STOP_ALL = MY_PACKAGE + ".action.STOP_ALL";
65
66 /// Keys to add extras to the action
67 public static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE";
68 public static final String EXTRA_ACCOUNT = MY_PACKAGE + ".extra.ACCOUNT";
69 public static String EXTRA_START_POSITION = MY_PACKAGE + ".extra.START_POSITION";
70 public static final String EXTRA_PLAY_ON_LOAD = MY_PACKAGE + ".extra.PLAY_ON_LOAD";
71
72
73 /** Error code for specific messages - see regular error codes at {@link MediaPlayer} */
74 public static final int OC_MEDIA_ERROR = 0;
75
76 /** Time To keep the control panel visible when the user does not use it */
77 public static final int MEDIA_CONTROL_SHORT_LIFE = 4000;
78
79 /** Time To keep the control panel visible when the user does not use it */
80 public static final int MEDIA_CONTROL_PERMANENT = 0;
81
82 /** Volume to set when audio focus is lost and ducking is allowed */
83 private static final float DUCK_VOLUME = 0.1f;
84
85 /** Media player instance */
86 private MediaPlayer mPlayer = null;
87
88 /** Reference to the system AudioManager */
89 private AudioManager mAudioManager = null;
90
91
92 /** Values to indicate the state of the service */
93 enum State {
94 STOPPED,
95 PREPARING,
96 PLAYING,
97 PAUSED
98 };
99
100
101 /** Current state */
102 private State mState = State.STOPPED;
103
104 /** Possible focus values */
105 enum AudioFocus {
106 NO_FOCUS,
107 NO_FOCUS_CAN_DUCK,
108 FOCUS
109 }
110
111 /** Current focus state */
112 private AudioFocus mAudioFocus = AudioFocus.NO_FOCUS;
113
114
115 /** 'True' when the current song is streaming from the network */
116 private boolean mIsStreaming = false;
117
118 /** Wifi lock kept to prevents the device from shutting off the radio when streaming a file. */
119 private WifiLock mWifiLock;
120
121 private static final String MEDIA_WIFI_LOCK_TAG = MY_PACKAGE + ".WIFI_LOCK";
122
123 /** Notification to keep in the notification bar while a song is playing */
124 private NotificationManager mNotificationManager;
125 private Notification mNotification = null;
126
127 /** File being played */
128 private OCFile mFile;
129
130 /** Account holding the file being played */
131 private Account mAccount;
132
133 /** Flag signaling if the audio should be played immediately when the file is prepared */
134 protected boolean mPlayOnPrepared;
135
136 /** Position, in miliseconds, where the audio should be started */
137 private int mStartPosition;
138
139 /** Interface to access the service through binding */
140 private IBinder mBinder;
141
142 /** Control panel shown to the user to control the playback, to register through binding */
143 private MediaControlView mMediaController;
144
145
146
147 /**
148 * Helper method to get an error message suitable to show to users for errors occurred in media playback,
149 *
150 * @param context A context to access string resources.
151 * @param what See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
152 * @param extra See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
153 * @return Message suitable to users.
154 */
155 public static String getMessageForMediaError(Context context, int what, int extra) {
156 int messageId;
157
158 if (what == OC_MEDIA_ERROR) {
159 messageId = extra;
160
161 } else if (extra == MediaPlayer.MEDIA_ERROR_UNSUPPORTED) {
162 /* Added in API level 17
163 Bitstream is conforming to the related coding standard or file spec, but the media framework does not support the feature.
164 Constant Value: -1010 (0xfffffc0e)
165 */
166 messageId = R.string.media_err_unsupported;
167
168 } else if (extra == MediaPlayer.MEDIA_ERROR_IO) {
169 /* Added in API level 17
170 File or network related operation errors.
171 Constant Value: -1004 (0xfffffc14)
172 */
173 messageId = R.string.media_err_io;
174
175 } else if (extra == MediaPlayer.MEDIA_ERROR_MALFORMED) {
176 /* Added in API level 17
177 Bitstream is not conforming to the related coding standard or file spec.
178 Constant Value: -1007 (0xfffffc11)
179 */
180 messageId = R.string.media_err_malformed;
181
182 } else if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
183 /* Added in API level 17
184 Some operation takes too long to complete, usually more than 3-5 seconds.
185 Constant Value: -110 (0xffffff92)
186 */
187 messageId = R.string.media_err_timeout;
188
189 } else if (what == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
190 /* Added in API level 3
191 The video is streamed and its container is not valid for progressive playback i.e the video's index (e.g moov atom) is not at the start of the file.
192 Constant Value: 200 (0x000000c8)
193 */
194 messageId = R.string.media_err_invalid_progressive_playback;
195
196 } else {
197 /* MediaPlayer.MEDIA_ERROR_UNKNOWN
198 Added in API level 1
199 Unspecified media player error.
200 Constant Value: 1 (0x00000001)
201 */
202 /* MediaPlayer.MEDIA_ERROR_SERVER_DIED)
203 Added in API level 1
204 Media server died. In this case, the application must release the MediaPlayer object and instantiate a new one.
205 Constant Value: 100 (0x00000064)
206 */
207 messageId = R.string.media_err_unknown;
208 }
209 return context.getString(messageId);
210 }
211
212
213
214 /**
215 * Initialize a service instance
216 *
217 * {@inheritDoc}
218 */
219 @Override
220 public void onCreate() {
221 Log_OC.d(TAG, "Creating ownCloud media service");
222
223 mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)).
224 createWifiLock(WifiManager.WIFI_MODE_FULL, MEDIA_WIFI_LOCK_TAG);
225
226 mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
227 mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
228 mBinder = new MediaServiceBinder(this);
229 }
230
231
232 /**
233 * Entry point for Intents requesting actions, sent here via startService.
234 *
235 * {@inheritDoc}
236 */
237 @Override
238 public int onStartCommand(Intent intent, int flags, int startId) {
239 String action = intent.getAction();
240 if (action.equals(ACTION_PLAY_FILE)) {
241 processPlayFileRequest(intent);
242
243 } else if (action.equals(ACTION_STOP_ALL)) {
244 processStopRequest(true);
245 }
246
247 return START_NOT_STICKY; // don't want it to restart in case it's killed.
248 }
249
250
251 /**
252 * Processes a request to play a media file received as a parameter
253 *
254 * TODO If a new request is received when a file is being prepared, it is ignored. Is this what we want?
255 *
256 * @param intent Intent received in the request with the data to identify the file to play.
257 */
258 private void processPlayFileRequest(Intent intent) {
259 if (mState != State.PREPARING) {
260 mFile = intent.getExtras().getParcelable(EXTRA_FILE);
261 mAccount = intent.getExtras().getParcelable(EXTRA_ACCOUNT);
262 mPlayOnPrepared = intent.getExtras().getBoolean(EXTRA_PLAY_ON_LOAD, false);
263 mStartPosition = intent.getExtras().getInt(EXTRA_START_POSITION, 0);
264 tryToGetAudioFocus();
265 playMedia();
266 }
267 }
268
269
270 /**
271 * Processes a request to play a media file.
272 */
273 protected void processPlayRequest() {
274 // request audio focus
275 tryToGetAudioFocus();
276
277 // actually play the song
278 if (mState == State.STOPPED) {
279 // (re)start playback
280 playMedia();
281
282 } else if (mState == State.PAUSED) {
283 // continue playback
284 mState = State.PLAYING;
285 setUpAsForeground(String.format(getString(R.string.media_state_playing), mFile.getFileName()));
286 configAndStartMediaPlayer();
287
288 }
289 }
290
291
292 /**
293 * Makes sure the media player exists and has been reset. This will create the media player
294 * if needed. reset the existing media player if one already exists.
295 */
296 protected void createMediaPlayerIfNeeded() {
297 if (mPlayer == null) {
298 mPlayer = new MediaPlayer();
299
300 // make sure the CPU won't go to sleep while media is playing
301 mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
302
303 // the media player will notify the service when it's ready preparing, and when it's done playing
304 mPlayer.setOnPreparedListener(this);
305 mPlayer.setOnCompletionListener(this);
306 mPlayer.setOnErrorListener(this);
307
308 } else {
309 mPlayer.reset();
310 }
311 }
312
313 /**
314 * Processes a request to pause the current playback
315 */
316 protected void processPauseRequest() {
317 if (mState == State.PLAYING) {
318 mState = State.PAUSED;
319 mPlayer.pause();
320 releaseResources(false); // retain media player in pause
321 // TODO polite audio focus, instead of keep it owned; or not?
322 }
323 }
324
325
326 /**
327 * Processes a request to stop the playback.
328 *
329 * @param force When 'true', the playback is stopped no matter the value of mState
330 */
331 protected void processStopRequest(boolean force) {
332 if (mState != State.PREPARING || force) {
333 mState = State.STOPPED;
334 mFile = null;
335 mAccount = null;
336 releaseResources(true);
337 giveUpAudioFocus();
338 stopSelf(); // service is no longer necessary
339 }
340 }
341
342
343 /**
344 * Releases resources used by the service for playback. This includes the "foreground service"
345 * status and notification, the wake locks and possibly the MediaPlayer.
346 *
347 * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not
348 */
349 protected void releaseResources(boolean releaseMediaPlayer) {
350 // stop being a foreground service
351 stopForeground(true);
352
353 // stop and release the Media Player, if it's available
354 if (releaseMediaPlayer && mPlayer != null) {
355 mPlayer.reset();
356 mPlayer.release();
357 mPlayer = null;
358 }
359
360 // release the Wifi lock, if holding it
361 if (mWifiLock.isHeld()) {
362 mWifiLock.release();
363 }
364 }
365
366
367 /**
368 * Fully releases the audio focus.
369 */
370 private void giveUpAudioFocus() {
371 if (mAudioFocus == AudioFocus.FOCUS
372 && mAudioManager != null
373 && AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this)) {
374
375 mAudioFocus = AudioFocus.NO_FOCUS;
376 }
377 }
378
379
380 /**
381 * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it.
382 */
383 protected void configAndStartMediaPlayer() {
384 if (mPlayer == null) {
385 throw new IllegalStateException("mPlayer is NULL");
386 }
387
388 if (mAudioFocus == AudioFocus.NO_FOCUS) {
389 if (mPlayer.isPlaying()) {
390 mPlayer.pause(); // have to be polite; but mState is not changed, to resume when focus is received again
391 }
392
393 } else {
394 if (mAudioFocus == AudioFocus.NO_FOCUS_CAN_DUCK) {
395 mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME);
396
397 } else {
398 mPlayer.setVolume(1.0f, 1.0f); // full volume
399 }
400
401 if (!mPlayer.isPlaying()) {
402 mPlayer.start();
403 }
404 }
405 }
406
407
408 /**
409 * Requests the audio focus to the Audio Manager
410 */
411 private void tryToGetAudioFocus() {
412 if (mAudioFocus != AudioFocus.FOCUS
413 && mAudioManager != null
414 && (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.requestAudioFocus( this,
415 AudioManager.STREAM_MUSIC,
416 AudioManager.AUDIOFOCUS_GAIN))
417 ) {
418 mAudioFocus = AudioFocus.FOCUS;
419 }
420 }
421
422
423 /**
424 * Starts playing the current media file.
425 */
426 protected void playMedia() {
427 mState = State.STOPPED;
428 releaseResources(false); // release everything except MediaPlayer
429
430 try {
431 if (mFile == null) {
432 Toast.makeText(this, R.string.media_err_nothing_to_play, Toast.LENGTH_LONG).show();
433 processStopRequest(true);
434 return;
435
436 } else if (mAccount == null) {
437 Toast.makeText(this, R.string.media_err_not_in_owncloud, Toast.LENGTH_LONG).show();
438 processStopRequest(true);
439 return;
440 }
441
442 createMediaPlayerIfNeeded();
443 mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
444 String url = mFile.getStoragePath();
445 /* Streaming is not possible right now
446 if (url == null || url.length() <= 0) {
447 url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath();
448 }
449 mIsStreaming = url.startsWith("http:") || url.startsWith("https:");
450 */
451 mIsStreaming = false;
452
453 mPlayer.setDataSource(url);
454
455 mState = State.PREPARING;
456 setUpAsForeground(String.format(getString(R.string.media_state_loading), mFile.getFileName()));
457
458 // starts preparing the media player in background
459 mPlayer.prepareAsync();
460
461 // prevent the Wifi from going to sleep when streaming
462 if (mIsStreaming) {
463 mWifiLock.acquire();
464 } else if (mWifiLock.isHeld()) {
465 mWifiLock.release();
466 }
467
468 } catch (SecurityException e) {
469 Log_OC.e(TAG, "SecurityException playing " + mAccount.name + mFile.getRemotePath(), e);
470 Toast.makeText(this, String.format(getString(R.string.media_err_security_ex), mFile.getFileName()), Toast.LENGTH_LONG).show();
471 processStopRequest(true);
472
473 } catch (IOException e) {
474 Log_OC.e(TAG, "IOException playing " + mAccount.name + mFile.getRemotePath(), e);
475 Toast.makeText(this, String.format(getString(R.string.media_err_io_ex), mFile.getFileName()), Toast.LENGTH_LONG).show();
476 processStopRequest(true);
477
478 } catch (IllegalStateException e) {
479 Log_OC.e(TAG, "IllegalStateException " + mAccount.name + mFile.getRemotePath(), e);
480 Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show();
481 processStopRequest(true);
482
483 } catch (IllegalArgumentException e) {
484 Log_OC.e(TAG, "IllegalArgumentException " + mAccount.name + mFile.getRemotePath(), e);
485 Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show();
486 processStopRequest(true);
487 }
488 }
489
490
491 /** Called when media player is done playing current song. */
492 public void onCompletion(MediaPlayer player) {
493 Toast.makeText(this, String.format(getString(R.string.media_event_done, mFile.getFileName())), Toast.LENGTH_LONG).show();
494 if (mMediaController != null) {
495 // somebody is still bound to the service
496 player.seekTo(0);
497 processPauseRequest();
498 mMediaController.updatePausePlay();
499 } else {
500 // nobody is bound
501 processStopRequest(true);
502 }
503 return;
504 }
505
506
507 /**
508 * Called when media player is done preparing.
509 *
510 * Time to start.
511 */
512 public void onPrepared(MediaPlayer player) {
513 mState = State.PLAYING;
514 updateNotification(String.format(getString(R.string.media_state_playing), mFile.getFileName()));
515 if (mMediaController != null) {
516 mMediaController.setEnabled(true);
517 }
518 player.seekTo(mStartPosition);
519 configAndStartMediaPlayer();
520 if (!mPlayOnPrepared) {
521 processPauseRequest();
522 }
523
524 if (mMediaController != null) {
525 mMediaController.updatePausePlay();
526 }
527 }
528
529
530 /**
531 * Updates the status notification
532 */
533 @SuppressWarnings("deprecation")
534 private void updateNotification(String content) {
535 // TODO check if updating the Intent is really necessary
536 Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class);
537 showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile);
538 showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount);
539 showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
540 mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
541 (int)System.currentTimeMillis(),
542 showDetailsIntent,
543 PendingIntent.FLAG_UPDATE_CURRENT);
544 mNotification.when = System.currentTimeMillis();
545 //mNotification.contentView.setTextViewText(R.id.status_text, content);
546 String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name));
547 mNotification.setLatestEventInfo(getApplicationContext(), ticker, content, mNotification.contentIntent);
548 mNotificationManager.notify(R.string.media_notif_ticker, mNotification);
549 }
550
551
552 /**
553 * Configures the service as a foreground service.
554 *
555 * The system will avoid finishing the service as much as possible when resources as low.
556 *
557 * A notification must be created to keep the user aware of the existance of the service.
558 */
559 @SuppressWarnings("deprecation")
560 private void setUpAsForeground(String content) {
561 /// creates status notification
562 // TODO put a progress bar to follow the playback progress
563 mNotification = new Notification();
564 mNotification.icon = android.R.drawable.ic_media_play;
565 //mNotification.tickerText = text;
566 mNotification.when = System.currentTimeMillis();
567 mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
568 //mNotification.contentView.setTextViewText(R.id.status_text, "ownCloud Music Player"); // NULL POINTER
569 //mNotification.contentView.setTextViewText(R.id.status_text, getString(R.string.downloader_download_in_progress_content));
570
571
572 /// includes a pending intent in the notification showing the details view of the file
573 Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class);
574 showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile);
575 showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount);
576 showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
577 mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
578 (int)System.currentTimeMillis(),
579 showDetailsIntent,
580 PendingIntent.FLAG_UPDATE_CURRENT);
581
582
583 //mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification);
584 String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name));
585 mNotification.setLatestEventInfo(getApplicationContext(), ticker, content, mNotification.contentIntent);
586 startForeground(R.string.media_notif_ticker, mNotification);
587
588 }
589
590 /**
591 * Called when there's an error playing media.
592 *
593 * Warns the user about the error and resets the media player.
594 */
595 public boolean onError(MediaPlayer mp, int what, int extra) {
596 Log_OC.e(TAG, "Error in audio playback, what = " + what + ", extra = " + extra);
597
598 String message = getMessageForMediaError(this, what, extra);
599 Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
600
601 processStopRequest(true);
602 return true;
603 }
604
605 /**
606 * Called by the system when another app tries to play some sound.
607 *
608 * {@inheritDoc}
609 */
610 @Override
611 public void onAudioFocusChange(int focusChange) {
612 if (focusChange > 0) {
613 // focus gain; check AudioManager.AUDIOFOCUS_* values
614 mAudioFocus = AudioFocus.FOCUS;
615 // restart media player with new focus settings
616 if (mState == State.PLAYING)
617 configAndStartMediaPlayer();
618
619 } else if (focusChange < 0) {
620 // focus loss; check AudioManager.AUDIOFOCUS_* values
621 boolean canDuck = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK == focusChange;
622 mAudioFocus = canDuck ? AudioFocus.NO_FOCUS_CAN_DUCK : AudioFocus.NO_FOCUS;
623 // start/restart/pause media player with new focus settings
624 if (mPlayer != null && mPlayer.isPlaying())
625 configAndStartMediaPlayer();
626 }
627
628 }
629
630 /**
631 * Called when the service is finished for final clean-up.
632 *
633 * {@inheritDoc}
634 */
635 @Override
636 public void onDestroy() {
637 mState = State.STOPPED;
638 releaseResources(true);
639 giveUpAudioFocus();
640 }
641
642
643 /**
644 * Provides a binder object that clients can use to perform operations on the MediaPlayer managed by the MediaService.
645 */
646 @Override
647 public IBinder onBind(Intent arg) {
648 return mBinder;
649 }
650
651
652 /**
653 * Called when ALL the bound clients were onbound.
654 *
655 * The service is destroyed if playback stopped or paused
656 */
657 @Override
658 public boolean onUnbind(Intent intent) {
659 if (mState == State.PAUSED || mState == State.STOPPED) {
660 processStopRequest(false);
661 }
662 return false; // not accepting rebinding (default behaviour)
663 }
664
665
666 /**
667 * Accesses the current MediaPlayer instance in the service.
668 *
669 * To be handled carefully. Visibility is protected to be accessed only
670 *
671 * @return Current MediaPlayer instance handled by MediaService.
672 */
673 protected MediaPlayer getPlayer() {
674 return mPlayer;
675 }
676
677
678 /**
679 * Accesses the current OCFile loaded in the service.
680 *
681 * @return The current OCFile loaded in the service.
682 */
683 protected OCFile getCurrentFile() {
684 return mFile;
685 }
686
687
688 /**
689 * Accesses the current {@link State} of the MediaService.
690 *
691 * @return The current {@link State} of the MediaService.
692 */
693 protected State getState() {
694 return mState;
695 }
696
697
698 protected void setMediaContoller(MediaControlView mediaController) {
699 mMediaController = mediaController;
700 }
701
702 protected MediaControlView getMediaController() {
703 return mMediaController;
704 }
705
706 }