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