Removed unused code in MediaService
[pub/Android/ownCloud.git] / src / com / owncloud / android / media / MediaService.java
1 /* ownCloud Android client application
2 * Copyright 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 as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 package com.owncloud.android.media;
20
21 import android.accounts.Account;
22 import android.app.Notification;
23 import android.app.NotificationManager;
24 import android.app.PendingIntent;
25 import android.app.Service;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.media.AudioManager;
29 import android.media.MediaPlayer;
30 import android.media.MediaPlayer.OnCompletionListener;
31 import android.media.MediaPlayer.OnErrorListener;
32 import android.media.MediaPlayer.OnPreparedListener;
33 import android.net.wifi.WifiManager;
34 import android.net.wifi.WifiManager.WifiLock;
35 import android.os.IBinder;
36 import android.os.PowerManager;
37 import android.util.Log;
38 import android.widget.Toast;
39
40 import java.io.IOException;
41
42 import com.owncloud.android.AccountUtils;
43 import com.owncloud.android.R;
44 import com.owncloud.android.datamodel.OCFile;
45 import com.owncloud.android.ui.activity.FileDetailActivity;
46 import com.owncloud.android.ui.fragment.FileDetailFragment;
47
48 /**
49 * Service that handles media playback, both audio and video.
50 *
51 * Waits for Intents which signal the service to perform specific operations: Play, Pause,
52 * Rewind, etc.
53 *
54 * @author David A. Velasco
55 */
56 public class MediaService extends Service implements OnCompletionListener, OnPreparedListener,
57 OnErrorListener, AudioManager.OnAudioFocusChangeListener {
58
59 private static final String TAG = MediaService.class.getSimpleName();
60
61 private static final String MY_PACKAGE = MediaService.class.getPackage() != null ? MediaService.class.getPackage().getName() : "com.owncloud.android.media";
62
63 /// Intent actions that we are prepared to handle
64 public static final String ACTION_PLAY_FILE = MY_PACKAGE + ".android.media.action.PLAY_FILE";
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
70 /** Volume to set when audio focus is lost and ducking is allowed */
71 private static final float DUCK_VOLUME = 0.1f;
72
73 /** Media player instance */
74 private MediaPlayer mPlayer = null;
75
76
77 /** Reference to the system AudioManager */
78 private AudioManager mAudioManager = null;
79
80
81 /** Values to indicate the state of the service */
82 enum State {
83 STOPPED,
84 PREPARING,
85 PLAYING,
86 PAUSED
87 };
88
89
90 /** Current state */
91 private State mState = State.STOPPED;
92
93 /** Possible focus values */
94 enum AudioFocus {
95 NO_FOCUS,
96 NO_FOCUS_CAN_DUCK,
97 FOCUS
98 }
99
100 /** Current focus state */
101 private AudioFocus mAudioFocus = AudioFocus.NO_FOCUS;
102
103
104 /** 'True' when the current song is streaming from the network */
105 private boolean mIsStreaming = false;
106
107 /** Wifi lock kept to prevents the device from shutting off the radio when streaming a file. */
108 private WifiLock mWifiLock;
109
110 private static final String MEDIA_WIFI_LOCK_TAG = MY_PACKAGE + ".WIFI_LOCK";
111
112 /** Notification to keep in the notification bar while a song is playing */
113 private NotificationManager mNotificationManager;
114 private Notification mNotification = null;
115
116 /** File being played */
117 private OCFile mFile;
118
119 /** Account holding the file being played */
120 private Account mAccount;
121
122 /** Interface to access the service through binding */
123 private IBinder mBinder;
124
125
126
127 /**
128 * Initialize a service instance
129 *
130 * {@inheritDoc}
131 */
132 @Override
133 public void onCreate() {
134 Log.d(TAG, "Creating ownCloud media service");
135
136 mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)).
137 createWifiLock(WifiManager.WIFI_MODE_FULL, MEDIA_WIFI_LOCK_TAG);
138
139 mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
140 mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
141 mBinder = new MediaServiceBinder(this);
142 }
143
144
145 /**
146 * Entry point for Intents requesting actions, sent here via startService.
147 *
148 * {@inheritDoc}
149 */
150 @Override
151 public int onStartCommand(Intent intent, int flags, int startId) {
152 String action = intent.getAction();
153 if (action.equals(ACTION_PLAY_FILE)) {
154 processPlayFileRequest(intent);
155 }
156
157 return START_NOT_STICKY; // don't want it to restart in case it's killed.
158 }
159
160
161 /**
162 * Processes a request to play a media file received as a parameter
163 *
164 * TODO If a new request is received when a file is being prepared, it is ignored. Is this what we want?
165 *
166 * @param intent Intent received in the request with the data to identify the file to play.
167 */
168 private void processPlayFileRequest(Intent intent) {
169 if (mState == State.PLAYING || mState == State.PAUSED || mState == State.STOPPED) {
170 mFile = intent.getExtras().getParcelable(EXTRA_FILE);
171 mAccount = intent.getExtras().getParcelable(EXTRA_ACCOUNT);
172 tryToGetAudioFocus();
173 playMedia();
174 }
175 }
176
177
178 /**
179 * Processes a request to play a media file.
180 */
181 protected void processPlayRequest() {
182 // request audio focus
183 tryToGetAudioFocus();
184
185 // actually play the song
186 if (mState == State.STOPPED) {
187 // (re)start playback
188 playMedia();
189
190 } else if (mState == State.PAUSED) {
191 // continue playback
192 mState = State.PLAYING;
193 setUpAsForeground(String.format(getString(R.string.media_state_playing), mFile.getFileName()));
194 configAndStartMediaPlayer();
195
196 }
197 }
198
199
200 /**
201 * Makes sure the media player exists and has been reset. This will create the media player
202 * if needed, or reset the existing media player if one already exists.
203 */
204 protected void createMediaPlayerIfNeeded() {
205 if (mPlayer == null) {
206 mPlayer = new MediaPlayer();
207
208 // make sure the CPU won't go to sleep while media is playing
209 mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
210
211 // the media player will notify the service when it's ready preparing, and when it's done playing
212 mPlayer.setOnPreparedListener(this);
213 mPlayer.setOnCompletionListener(this);
214 mPlayer.setOnErrorListener(this);
215
216 } else {
217 mPlayer.reset();
218 }
219 }
220
221 /**
222 * Processes a request to pause the current playback
223 */
224 protected void processPauseRequest() {
225 if (mState == State.PLAYING) {
226 mState = State.PAUSED;
227 mPlayer.pause();
228 releaseResources(false); // retain media player in pause
229 // TODO polite audio focus, instead of keep it owned; or not?
230 }
231 }
232
233
234 /**
235 * Processes a request to stop the playback.
236 *
237 * @param force When 'true', the playback is stopped no matter the value of mState
238 */
239 protected void processStopRequest(boolean force) {
240 if (mState == State.PLAYING || mState == State.PAUSED || mState == State.STOPPED || force) {
241 mState = State.STOPPED;
242
243 mFile = null;
244 mAccount = null;
245
246 releaseResources(true);
247 giveUpAudioFocus();
248 stopSelf(); // service is no longer necessary
249 }
250 }
251
252
253 /**
254 * Releases resources used by the service for playback. This includes the "foreground service"
255 * status and notification, the wake locks and possibly the MediaPlayer.
256 *
257 * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not
258 */
259 protected void releaseResources(boolean releaseMediaPlayer) {
260 // stop being a foreground service
261 stopForeground(true);
262
263 // stop and release the Media Player, if it's available
264 if (releaseMediaPlayer && mPlayer != null) {
265 mPlayer.reset();
266 mPlayer.release();
267 mPlayer = null;
268 }
269
270 // release the Wifi lock, if holding it
271 if (mWifiLock.isHeld()) {
272 mWifiLock.release();
273 }
274 }
275
276
277 /**
278 * Fully releases the audio focus.
279 */
280 private void giveUpAudioFocus() {
281 if (mAudioFocus == AudioFocus.FOCUS
282 && mAudioManager != null
283 && AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this)) {
284
285 mAudioFocus = AudioFocus.NO_FOCUS;
286 }
287 }
288
289
290 /**
291 * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it.
292 */
293 protected void configAndStartMediaPlayer() {
294 if (mPlayer == null) {
295 throw new IllegalStateException("mPlayer is NULL");
296 }
297
298 if (mAudioFocus == AudioFocus.NO_FOCUS) {
299 if (mPlayer.isPlaying()) {
300 mPlayer.pause(); // have to be polite; but mState is not changed, to resume when focus is received again
301 }
302
303 } else {
304 if (mAudioFocus == AudioFocus.NO_FOCUS_CAN_DUCK) {
305 mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME);
306
307 } else {
308 mPlayer.setVolume(1.0f, 1.0f); // full volume
309 }
310
311 if (!mPlayer.isPlaying()) {
312 mPlayer.start();
313 }
314 }
315 }
316
317
318 /**
319 * Requests the audio focus to the Audio Manager
320 */
321 private void tryToGetAudioFocus() {
322 if (mAudioFocus != AudioFocus.FOCUS
323 && mAudioManager != null
324 && (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.requestAudioFocus( this,
325 AudioManager.STREAM_MUSIC,
326 AudioManager.AUDIOFOCUS_GAIN))
327 ) {
328 mAudioFocus = AudioFocus.FOCUS;
329 }
330 }
331
332
333 /**
334 * Starts playing the current media file.
335 */
336 protected void playMedia() {
337 mState = State.STOPPED;
338 releaseResources(false); // release everything except MediaPlayer
339
340 try {
341 if (mFile == null) {
342 Toast.makeText(this, R.string.media_err_nothing_to_play, Toast.LENGTH_LONG).show();
343 processStopRequest(true);
344 return;
345
346 } else if (mAccount == null) {
347 Toast.makeText(this, R.string.media_err_not_in_owncloud, Toast.LENGTH_LONG).show();
348 processStopRequest(true);
349 return;
350 }
351
352 createMediaPlayerIfNeeded();
353 mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
354 String url = mFile.getStoragePath();
355 if (url == null || url.length() <= 0) {
356 url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath();
357 }
358 mIsStreaming = url.startsWith("http:") || url.startsWith("https:");
359
360 mPlayer.setDataSource(url);
361
362 mState = State.PREPARING;
363 setUpAsForeground(String.format(getString(R.string.media_state_loading), mFile.getFileName()));
364
365 // starts preparing the media player in background
366 mPlayer.prepareAsync();
367
368 // prevent the Wifi from going to sleep when streaming
369 if (mIsStreaming) {
370 mWifiLock.acquire();
371 } else if (mWifiLock.isHeld()) {
372 mWifiLock.release();
373 }
374
375 } catch (SecurityException e) {
376 Log.e(TAG, "SecurityException playing " + mAccount.name + mFile.getRemotePath(), e);
377 // TODO message to the user
378
379 } catch (IOException e) {
380 Log.e(TAG, "IOException playing " + mAccount.name + mFile.getRemotePath(), e);
381 // TODO message to the user
382
383 } catch (IllegalStateException e) {
384 Log.e(TAG, "IllegalStateException " + mAccount.name + mFile.getRemotePath(), e);
385 // TODO message to the user
386
387 } catch (IllegalArgumentException e) {
388 Log.e(TAG, "IllegalArgumentException " + mAccount.name + mFile.getRemotePath(), e);
389 e.printStackTrace();
390 // TODO message to the user
391 }
392 }
393
394
395 /** Called when media player is done playing current song. */
396 public void onCompletion(MediaPlayer player) {
397 Toast.makeText(this, String.format(getString(R.string.media_event_done, mFile.getFileName())), Toast.LENGTH_LONG).show();
398 processStopRequest(true);
399 return;
400 }
401
402
403 /**
404 * Called when media player is done preparing.
405 *
406 * Time to start.
407 */
408 public void onPrepared(MediaPlayer player) {
409 mState = State.PLAYING;
410 updateNotification(String.format(getString(R.string.media_state_playing), mFile.getFileName()));
411 configAndStartMediaPlayer();
412 }
413
414
415 /**
416 * Updates the status notification
417 */
418 @SuppressWarnings("deprecation")
419 private void updateNotification(String content) {
420 // TODO check if updating the Intent is really necessary
421 Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
422 showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, mFile);
423 showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount);
424 showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
425 mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
426 (int)System.currentTimeMillis(),
427 showDetailsIntent,
428 PendingIntent.FLAG_UPDATE_CURRENT);
429 mNotification.when = System.currentTimeMillis();
430 //mNotification.contentView.setTextViewText(R.id.status_text, content);
431 String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name));
432 mNotification.setLatestEventInfo(getApplicationContext(), ticker, content, mNotification.contentIntent);
433 mNotificationManager.notify(R.string.media_notif_ticker, mNotification);
434 }
435
436
437 /**
438 * Configures the service as a foreground service.
439 *
440 * The system will avoid finishing the service as much as possible when resources as low.
441 *
442 * A notification must be created to keep the user aware of the existance of the service.
443 */
444 @SuppressWarnings("deprecation")
445 private void setUpAsForeground(String content) {
446 /// creates status notification
447 // TODO put a progress bar to follow the playback progress
448 mNotification = new Notification();
449 mNotification.icon = android.R.drawable.ic_media_play;
450 //mNotification.tickerText = text;
451 mNotification.when = System.currentTimeMillis();
452 mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
453 //mNotification.contentView.setTextViewText(R.id.status_text, "ownCloud Music Player"); // NULL POINTER
454 //mNotification.contentView.setTextViewText(R.id.status_text, getString(R.string.downloader_download_in_progress_content));
455
456
457 /// includes a pending intent in the notification showing the details view of the file
458 Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
459 showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, mFile);
460 showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount);
461 showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
462 mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
463 (int)System.currentTimeMillis(),
464 showDetailsIntent,
465 PendingIntent.FLAG_UPDATE_CURRENT);
466
467
468 //mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification);
469 String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name));
470 mNotification.setLatestEventInfo(getApplicationContext(), ticker, content, mNotification.contentIntent);
471 startForeground(R.string.media_notif_ticker, mNotification);
472
473 }
474
475 /**
476 * Called when there's an error playing media.
477 *
478 * Warns the user about the error and resets the media player.
479 */
480 public boolean onError(MediaPlayer mp, int what, int extra) {
481 // TODO FOLLOW HERE!!!!!!
482
483 Toast.makeText(getApplicationContext(), "Media player error! Resetting.",
484 Toast.LENGTH_SHORT).show();
485 Log.e(TAG, "Error: what=" + String.valueOf(what) + ", extra=" + String.valueOf(extra));
486
487 mState = State.STOPPED;
488 releaseResources(true);
489 giveUpAudioFocus();
490 return true;
491 }
492
493
494 /**
495 * Called by the system when another app tries to play some sound.
496 *
497 * {@inheritDoc}
498 */
499 @Override
500 public void onAudioFocusChange(int focusChange) {
501 if (focusChange > 0) {
502 // focus gain; check AudioManager.AUDIOFOCUS_* values
503 Toast.makeText(getApplicationContext(), "gained audio focus.", Toast.LENGTH_SHORT).show();
504 mAudioFocus = AudioFocus.FOCUS;
505
506 // restart media player with new focus settings
507 if (mState == State.PLAYING)
508 configAndStartMediaPlayer();
509
510 } else if (focusChange < 0) {
511 // focus loss; check AudioManager.AUDIOFOCUS_* values
512 boolean canDuck = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK == focusChange;
513 Toast.makeText(getApplicationContext(), "lost audio focus." + (canDuck ? "can duck" :
514 "no duck"), Toast.LENGTH_SHORT).show();
515 mAudioFocus = canDuck ? AudioFocus.NO_FOCUS_CAN_DUCK : AudioFocus.NO_FOCUS;
516
517 // start/restart/pause media player with new focus settings
518 if (mPlayer != null && mPlayer.isPlaying())
519 configAndStartMediaPlayer();
520 }
521
522 }
523
524 /**
525 * Called when the service is finished for final clean-up.
526 *
527 * {@inheritDoc}
528 */
529 @Override
530 public void onDestroy() {
531 mState = State.STOPPED;
532 releaseResources(true);
533 giveUpAudioFocus();
534 }
535
536
537 /**
538 * Provides a binder object that clients can use to perform operations on the MediaPlayer managed by the MediaService.
539 */
540 @Override
541 public IBinder onBind(Intent arg) {
542 return mBinder;
543 }
544
545
546 /**
547 * Called when ALL the bound clients were onbound.
548 *
549 * The service is destroyed if playback stopped or paused
550 */
551 @Override
552 public boolean onUnbind(Intent intent) {
553 if (mState == State.PAUSED || mState == State.STOPPED) {
554 processStopRequest(false);
555 }
556 return false; // not accepting rebinding (default behaviour)
557 }
558
559
560 /**
561 * Accesses the current MediaPlayer instance in the service.
562 *
563 * To be handled carefully. Visibility is protected to be accessed only
564 *
565 * @return Current MediaPlayer instance handled by MediaService.
566 */
567 protected MediaPlayer getPlayer() {
568 return mPlayer;
569 }
570
571
572 /**
573 * Accesses the current OCFile loaded in the service.
574 *
575 * @return The current OCFile loaded in the service.
576 */
577 protected OCFile getCurrentFile() {
578 return mFile;
579 }
580
581
582 /**
583 * Accesses the current {@link State} of the MediaService.
584 *
585 * @return The current {@link State} of the MediaService.
586 */
587 protected State getState() {
588 return mState;
589 }
590
591 }