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