1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012-2013 ownCloud Inc.  
   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. 
   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. 
  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/>. 
  17 package com
.owncloud
.android
.ui
.preview
; 
  19 import android
.accounts
.Account
; 
  20 import android
.app
.Activity
; 
  21 import android
.app
.AlertDialog
; 
  22 import android
.content
.ComponentName
; 
  23 import android
.content
.Context
; 
  24 import android
.content
.DialogInterface
; 
  25 import android
.content
.Intent
; 
  26 import android
.content
.ServiceConnection
; 
  27 import android
.content
.res
.Configuration
; 
  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
.os
.Build
; 
  33 import android
.os
.Bundle
; 
  34 import android
.os
.IBinder
; 
  35 import android
.view
.LayoutInflater
; 
  36 import android
.view
.MotionEvent
; 
  37 import android
.view
.View
; 
  38 import android
.view
.View
.OnTouchListener
; 
  39 import android
.view
.ViewGroup
; 
  40 import android
.widget
.ImageView
; 
  41 import android
.widget
.Toast
; 
  42 import android
.widget
.VideoView
; 
  44 import com
.actionbarsherlock
.view
.Menu
; 
  45 import com
.actionbarsherlock
.view
.MenuInflater
; 
  46 import com
.actionbarsherlock
.view
.MenuItem
; 
  47 import com
.owncloud
.android
.R
; 
  48 import com
.owncloud
.android
.datamodel
.OCFile
; 
  49 import com
.owncloud
.android
.files
.FileMenuFilter
; 
  50 import com
.owncloud
.android
.media
.MediaControlView
; 
  51 import com
.owncloud
.android
.media
.MediaService
; 
  52 import com
.owncloud
.android
.media
.MediaServiceBinder
; 
  53 import com
.owncloud
.android
.ui
.activity
.FileActivity
; 
  54 import com
.owncloud
.android
.ui
.dialog
.ConfirmationDialogFragment
; 
  55 import com
.owncloud
.android
.ui
.dialog
.RemoveFileDialogFragment
; 
  56 import com
.owncloud
.android
.ui
.fragment
.FileFragment
; 
  57 import com
.owncloud
.android
.utils
.Log_OC
; 
  61  * This fragment shows a preview of a downloaded media file (audio or video). 
  63  * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}. 
  65  * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too. 
  67  * @author David A. Velasco 
  69 public class PreviewMediaFragment 
extends FileFragment 
implements 
  72     public static final String EXTRA_FILE 
= "FILE"; 
  73     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
  74     private static final String EXTRA_PLAY_POSITION 
= "PLAY_POSITION"; 
  75     private static final String EXTRA_PLAYING 
= "PLAYING"; 
  78     private Account mAccount
; 
  79     private ImageView mImagePreview
; 
  80     private VideoView mVideoPreview
; 
  81     private int mSavedPlaybackPosition
; 
  83     private MediaServiceBinder mMediaServiceBinder 
= null
; 
  84     private MediaControlView mMediaController 
= null
; 
  85     private MediaServiceConnection mMediaServiceConnection 
= null
; 
  86     private VideoHelper mVideoHelper
; 
  87     private boolean mAutoplay
; 
  88     public boolean mPrepared
; 
  90     private static final String TAG 
= PreviewMediaFragment
.class.getSimpleName(); 
  94      * Creates a fragment to preview a file. 
  96      * When 'fileToDetail' or 'ocAccount' are null 
  98      * @param fileToDetail      An {@link OCFile} to preview in the fragment 
  99      * @param ocAccount         An ownCloud account; needed to start downloads 
 101     public PreviewMediaFragment( 
 104             int startPlaybackPosition
,  
 108         mAccount 
= ocAccount
; 
 109         mSavedPlaybackPosition 
= startPlaybackPosition
; 
 110         mAutoplay 
= autoplay
; 
 115      *  Creates an empty fragment for previews. 
 117      *  MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically  
 118      *  (for instance, when the device is turned a aside). 
 120      *  DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful  
 123     public PreviewMediaFragment() { 
 126         mSavedPlaybackPosition 
= 0; 
 135     public void onCreate(Bundle savedInstanceState
) { 
 136         super.onCreate(savedInstanceState
); 
 137         setHasOptionsMenu(true
); 
 145     public View 
onCreateView(LayoutInflater inflater
, ViewGroup container
, 
 146             Bundle savedInstanceState
) { 
 147         super.onCreateView(inflater
, container
, savedInstanceState
); 
 148         Log_OC
.e(TAG
, "onCreateView"); 
 151         mView 
= inflater
.inflate(R
.layout
.file_preview
, container
, false
); 
 153         mImagePreview 
= (ImageView
)mView
.findViewById(R
.id
.image_preview
); 
 154         mVideoPreview 
= (VideoView
)mView
.findViewById(R
.id
.video_preview
); 
 155         mVideoPreview
.setOnTouchListener(this); 
 157         mMediaController 
= (MediaControlView
)mView
.findViewById(R
.id
.media_controller
); 
 167     public void onActivityCreated(Bundle savedInstanceState
) { 
 168         super.onActivityCreated(savedInstanceState
); 
 169         Log_OC
.e(TAG
, "onActivityCreated"); 
 171         OCFile file 
= getFile(); 
 172         if (savedInstanceState 
== null
) { 
 174                 throw new IllegalStateException("Instanced with a NULL OCFile"); 
 176             if (mAccount 
== null
) { 
 177                 throw new IllegalStateException("Instanced with a NULL ownCloud Account"); 
 179             if (!file
.isDown()) { 
 180                 throw new IllegalStateException("There is no local file to preview"); 
 184             file 
= (OCFile
)savedInstanceState
.getParcelable(PreviewMediaFragment
.EXTRA_FILE
); 
 186             mAccount 
= savedInstanceState
.getParcelable(PreviewMediaFragment
.EXTRA_ACCOUNT
); 
 187             mSavedPlaybackPosition 
=  
 188                     savedInstanceState
.getInt(PreviewMediaFragment
.EXTRA_PLAY_POSITION
); 
 189             mAutoplay 
= savedInstanceState
.getBoolean(PreviewMediaFragment
.EXTRA_PLAYING
); 
 192         if (file 
!= null 
&& file
.isDown()) { 
 193             if (file
.isVideo()) { 
 194                 mVideoPreview
.setVisibility(View
.VISIBLE
); 
 195                 mImagePreview
.setVisibility(View
.GONE
); 
 199                 mVideoPreview
.setVisibility(View
.GONE
); 
 200                 mImagePreview
.setVisibility(View
.VISIBLE
); 
 211     public void onSaveInstanceState(Bundle outState
) { 
 212         super.onSaveInstanceState(outState
); 
 213         Log_OC
.e(TAG
, "onSaveInstanceState"); 
 215         outState
.putParcelable(PreviewMediaFragment
.EXTRA_FILE
, getFile()); 
 216         outState
.putParcelable(PreviewMediaFragment
.EXTRA_ACCOUNT
, mAccount
); 
 218         if (getFile().isVideo()) { 
 219             mSavedPlaybackPosition 
= mVideoPreview
.getCurrentPosition(); 
 220             mAutoplay 
= mVideoPreview
.isPlaying(); 
 221             outState
.putInt(PreviewMediaFragment
.EXTRA_PLAY_POSITION 
, mSavedPlaybackPosition
); 
 222             outState
.putBoolean(PreviewMediaFragment
.EXTRA_PLAYING 
, mAutoplay
); 
 225                     PreviewMediaFragment
.EXTRA_PLAY_POSITION 
,  
 226                     mMediaServiceBinder
.getCurrentPosition()); 
 228                     PreviewMediaFragment
.EXTRA_PLAYING 
, mMediaServiceBinder
.isPlaying()); 
 234     public void onStart() { 
 236         Log_OC
.e(TAG
, "onStart"); 
 238         OCFile file 
= getFile(); 
 239         if (file 
!= null 
&& file
.isDown()) { 
 240            if (file
.isAudio()) { 
 243            } else if (file
.isVideo()) { 
 251     private void stopAudio() { 
 252         Intent i 
= new Intent(getSherlockActivity(), MediaService
.class); 
 253         i
.setAction(MediaService
.ACTION_STOP_ALL
); 
 254         getSherlockActivity().startService(i
); 
 262     public void onCreateOptionsMenu(Menu menu
, MenuInflater inflater
) { 
 263         super.onCreateOptionsMenu(menu
, inflater
); 
 264         inflater
.inflate(R
.menu
.file_actions_menu
, menu
); 
 272     public void onPrepareOptionsMenu(Menu menu
) { 
 273         super.onPrepareOptionsMenu(menu
); 
 275         if (mContainerActivity
.getStorageManager() != null
) { 
 276             FileMenuFilter mf 
= new FileMenuFilter( 
 278                 mContainerActivity
.getStorageManager().getAccount(), 
 280                 getSherlockActivity() 
 285         // additional restriction for this fragment  
 286         // TODO allow renaming in PreviewImageFragment 
 287         MenuItem item 
= menu
.findItem(R
.id
.action_rename_file
); 
 289             item
.setVisible(false
); 
 290             item
.setEnabled(false
); 
 293         // additional restriction for this fragment 
 294         item 
= menu
.findItem(R
.id
.action_move
); 
 296             item
.setVisible(false
); 
 297             item
.setEnabled(false
); 
 306     public boolean onOptionsItemSelected(MenuItem item
) { 
 307         switch (item
.getItemId()) { 
 308             case R
.id
.action_share_file
: { 
 310                 mContainerActivity
.getFileOperationsHelper().shareFileWithLink(getFile()); 
 313             case R
.id
.action_unshare_file
: { 
 315                 mContainerActivity
.getFileOperationsHelper().unshareFileWithLink(getFile()); 
 318             case R
.id
.action_open_file_with
: { 
 322             case R
.id
.action_remove_file
: { 
 323                 RemoveFileDialogFragment dialog 
= RemoveFileDialogFragment
.newInstance(getFile()); 
 324                 dialog
.show(getFragmentManager(), ConfirmationDialogFragment
.FTAG_CONFIRMATION
); 
 327             case R
.id
.action_see_details
: { 
 331             case R
.id
.action_send_file
: { 
 335             case R
.id
.action_sync_file
: { 
 336                 mContainerActivity
.getFileOperationsHelper().syncFile(getFile()); 
 348      * Update the file of the fragment with file value 
 351     public void updateFile(OCFile file
){ 
 355     private void sendFile() { 
 357         mContainerActivity
.getFileOperationsHelper().sendDownloadedFile(getFile()); 
 361     private void seeDetails() { 
 363         mContainerActivity
.showDetails(getFile());         
 367     private void prepareVideo() { 
 368         // create helper to get more control on the playback 
 369         mVideoHelper 
= new VideoHelper(); 
 370         mVideoPreview
.setOnPreparedListener(mVideoHelper
); 
 371         mVideoPreview
.setOnCompletionListener(mVideoHelper
); 
 372         mVideoPreview
.setOnErrorListener(mVideoHelper
); 
 375     private void playVideo() { 
 376         // create and prepare control panel for the user 
 377         mMediaController
.setMediaPlayer(mVideoPreview
); 
 379         // load the video file in the video player ;  
 380         // when done, VideoHelper#onPrepared() will be called 
 381         mVideoPreview
.setVideoPath(getFile().getStoragePath());  
 385     private class VideoHelper 
implements OnCompletionListener
, OnPreparedListener
, OnErrorListener 
{ 
 388          * Called when the file is ready to be played. 
 390          * Just starts the playback. 
 392          * @param   mp    {@link MediaPlayer} instance performing the playback. 
 395         public void onPrepared(MediaPlayer vp
) { 
 396             Log_OC
.e(TAG
, "onPrepared"); 
 397             mVideoPreview
.seekTo(mSavedPlaybackPosition
); 
 399                 mVideoPreview
.start(); 
 401             mMediaController
.setEnabled(true
); 
 402             mMediaController
.updatePausePlay(); 
 408          * Called when the file is finished playing. 
 410          * Finishes the activity. 
 412          * @param   mp    {@link MediaPlayer} instance performing the playback. 
 415         public void onCompletion(MediaPlayer  mp
) { 
 416             Log_OC
.e(TAG
, "completed"); 
 418                 mVideoPreview
.seekTo(0); 
 419                 // next lines are necessary to work around undesired video loops 
 420                 if (Build
.VERSION
.SDK_INT 
== Build
.VERSION_CODES
.GINGERBREAD
) { 
 421                     mVideoPreview
.pause();    
 423                 } else if (Build
.VERSION
.SDK_INT 
== Build
.VERSION_CODES
.GINGERBREAD_MR1
) { 
 424                     // mVideePreview.pause() is not enough 
 426                     mMediaController
.setEnabled(false
); 
 427                     mVideoPreview
.stopPlayback(); 
 429                     mSavedPlaybackPosition 
= 0; 
 430                     mVideoPreview
.setVideoPath(getFile().getStoragePath()); 
 432             } // else : called from onError() 
 433             mMediaController
.updatePausePlay(); 
 438          * Called when an error in playback occurs. 
 440          * @param   mp      {@link MediaPlayer} instance performing the playback. 
 441          * @param   what    Type of error 
 442          * @param   extra   Extra code specific to the error 
 445         public boolean onError(MediaPlayer mp
, int what
, int extra
) { 
 446             if (mVideoPreview
.getWindowToken() != null
) { 
 447                 String message 
= MediaService
.getMessageForMediaError( 
 448                         getSherlockActivity(), what
, extra
); 
 449                 new AlertDialog
.Builder(getSherlockActivity()) 
 451                         .setPositiveButton(android
.R
.string
.VideoView_error_button
, 
 452                                 new DialogInterface
.OnClickListener() { 
 453                                     public void onClick(DialogInterface dialog
, int whichButton
) { 
 455                                         VideoHelper
.this.onCompletion(null
); 
 458                         .setCancelable(false
) 
 468     public void onPause() { 
 469         Log_OC
.e(TAG
, "onPause"); 
 474     public void onResume() { 
 476         Log_OC
.e(TAG
, "onResume"); 
 480     public void onDestroy() { 
 481         Log_OC
.e(TAG
, "onDestroy"); 
 486     public void onStop() { 
 487         Log_OC
.e(TAG
, "onStop"); 
 490         if (mMediaServiceConnection 
!= null
) { 
 491             Log_OC
.d(TAG
, "Unbinding from MediaService ..."); 
 492             if (mMediaServiceBinder 
!= null 
&& mMediaController 
!= null
) { 
 493                 mMediaServiceBinder
.unregisterMediaController(mMediaController
); 
 495             getSherlockActivity().unbindService(mMediaServiceConnection
); 
 496             mMediaServiceConnection 
= null
; 
 497             mMediaServiceBinder 
= null
; 
 504     public boolean onTouch(View v
, MotionEvent event
) { 
 505         if (event
.getAction() == MotionEvent
.ACTION_DOWN 
&& v 
== mVideoPreview
) { 
 506             startFullScreenVideo(); 
 513     private void startFullScreenVideo() { 
 514         Intent i 
= new Intent(getSherlockActivity(), PreviewVideoActivity
.class); 
 515         i
.putExtra(FileActivity
.EXTRA_ACCOUNT
, mAccount
); 
 516         i
.putExtra(FileActivity
.EXTRA_FILE
, getFile()); 
 517         i
.putExtra(PreviewVideoActivity
.EXTRA_AUTOPLAY
, mVideoPreview
.isPlaying()); 
 518         mVideoPreview
.pause(); 
 519         i
.putExtra(PreviewVideoActivity
.EXTRA_START_POSITION
, mVideoPreview
.getCurrentPosition()); 
 520         startActivityForResult(i
, 0); 
 524     public void onConfigurationChanged (Configuration newConfig
) { 
 525         Log_OC
.e(TAG
, "onConfigurationChanged " + this); 
 529     public void onActivityResult (int requestCode
, int resultCode
, Intent data
) { 
 530         Log_OC
.e(TAG
, "onActivityResult " + this); 
 531         super.onActivityResult(requestCode
, resultCode
, data
); 
 532         if (resultCode 
== Activity
.RESULT_OK
) { 
 533             mSavedPlaybackPosition 
= data
.getExtras().getInt( 
 534                     PreviewVideoActivity
.EXTRA_START_POSITION
); 
 535             mAutoplay 
= data
.getExtras().getBoolean(PreviewVideoActivity
.EXTRA_AUTOPLAY
);  
 540     private void playAudio() { 
 541         OCFile file 
= getFile(); 
 542         if (!mMediaServiceBinder
.isPlaying(file
)) { 
 543             Log_OC
.d(TAG
, "starting playback of " + file
.getStoragePath()); 
 544             mMediaServiceBinder
.start(mAccount
, file
, mAutoplay
, mSavedPlaybackPosition
); 
 547             if (!mMediaServiceBinder
.isPlaying() && mAutoplay
) { 
 548                 mMediaServiceBinder
.start(); 
 549                 mMediaController
.updatePausePlay(); 
 555     private void bindMediaService() { 
 556         Log_OC
.d(TAG
, "Binding to MediaService..."); 
 557         if (mMediaServiceConnection 
== null
) { 
 558             mMediaServiceConnection 
= new MediaServiceConnection(); 
 560         getSherlockActivity().bindService(  new Intent(getSherlockActivity(),  
 562                                     mMediaServiceConnection
,  
 563                                     Context
.BIND_AUTO_CREATE
); 
 564             // follow the flow in MediaServiceConnection#onServiceConnected(...) 
 567     /** Defines callbacks for service binding, passed to bindService() */ 
 568     private class MediaServiceConnection 
implements ServiceConnection 
{ 
 571         public void onServiceConnected(ComponentName component
, IBinder service
) { 
 572             if (getSherlockActivity() != null
) { 
 573                 if (component
.equals( 
 574                         new ComponentName(getSherlockActivity(), MediaService
.class))) { 
 575                     Log_OC
.d(TAG
, "Media service connected"); 
 576                     mMediaServiceBinder 
= (MediaServiceBinder
) service
; 
 577                     if (mMediaServiceBinder 
!= null
) { 
 578                         prepareMediaController(); 
 579                         playAudio();    // do not wait for the touch of nobody to play audio 
 581                         Log_OC
.d(TAG
, "Successfully bound to MediaService, MediaController ready"); 
 584                         Log_OC
.e(TAG
, "Unexpected response from MediaService while binding"); 
 590         private void prepareMediaController() { 
 591             mMediaServiceBinder
.registerMediaController(mMediaController
); 
 592             if (mMediaController 
!= null
) { 
 593                 mMediaController
.setMediaPlayer(mMediaServiceBinder
); 
 594                 mMediaController
.setEnabled(true
); 
 595                 mMediaController
.updatePausePlay(); 
 600         public void onServiceDisconnected(ComponentName component
) { 
 601             if (component
.equals(new ComponentName(getSherlockActivity(), MediaService
.class))) { 
 602                 Log_OC
.e(TAG
, "Media service suddenly disconnected"); 
 603                 if (mMediaController 
!= null
) { 
 604                     mMediaController
.setMediaPlayer(null
); 
 607                             getSherlockActivity(),  
 608                             "No media controller to release when disconnected from media service",  
 609                             Toast
.LENGTH_SHORT
).show(); 
 611                 mMediaServiceBinder 
= null
; 
 612                 mMediaServiceConnection 
= null
; 
 620      * Opens the previewed file with an external application. 
 622     private void openFile() { 
 624         mContainerActivity
.getFileOperationsHelper().openFile(getFile()); 
 629      * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} 
 632      * @param file      File to test if can be previewed. 
 633      * @return          'True' if the file can be handled by the fragment. 
 635     public static boolean canBePreviewed(OCFile file
) { 
 636         return (file 
!= null 
&& (file
.isAudio() || file
.isVideo())); 
 640     public void stopPreview(boolean stopAudio
) { 
 641         OCFile file 
= getFile(); 
 642         if (file
.isAudio() && stopAudio
) { 
 643             mMediaServiceBinder
.pause(); 
 645         } else if (file
.isVideo()) { 
 646             mVideoPreview
.stopPlayback(); 
 653      * Finishes the preview 
 655     private void finish() { 
 656         getSherlockActivity().onBackPressed(); 
 660     public int getPosition() { 
 662             mSavedPlaybackPosition 
= mVideoPreview
.getCurrentPosition(); 
 664         Log_OC
.e(TAG
, "getting position: " + mSavedPlaybackPosition
); 
 665         return mSavedPlaybackPosition
; 
 668     public boolean isPlaying() { 
 670             mAutoplay 
= mVideoPreview
.isPlaying();