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 as published by 
   6  *   the Free Software Foundation, either version 2 of the License, or 
   7  *   (at your option) any later version. 
   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. 
  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/>. 
  18 package com
.owncloud
.android
.ui
.fragment
; 
  21 import java
.lang
.ref
.WeakReference
; 
  22 import java
.sql
.PreparedStatement
; 
  23 import java
.util
.ArrayList
; 
  24 import java
.util
.List
; 
  26 import org
.apache
.commons
.httpclient
.methods
.GetMethod
; 
  27 import org
.apache
.commons
.httpclient
.methods
.PostMethod
; 
  28 import org
.apache
.commons
.httpclient
.methods
.StringRequestEntity
; 
  29 import org
.apache
.commons
.httpclient
.params
.HttpConnectionManagerParams
; 
  30 import org
.apache
.http
.HttpStatus
; 
  31 import org
.apache
.http
.NameValuePair
; 
  32 import org
.apache
.http
.client
.utils
.URLEncodedUtils
; 
  33 import org
.apache
.http
.entity
.FileEntity
; 
  34 import org
.apache
.http
.message
.BasicNameValuePair
; 
  35 import org
.apache
.http
.protocol
.HTTP
; 
  36 import org
.apache
.jackrabbit
.webdav
.client
.methods
.PropFindMethod
; 
  37 import org
.json
.JSONObject
; 
  39 import android
.accounts
.Account
; 
  40 import android
.accounts
.AccountManager
; 
  41 import android
.annotation
.SuppressLint
; 
  42 import android
.app
.Activity
; 
  43 import android
.app
.AlertDialog
; 
  44 import android
.content
.ActivityNotFoundException
; 
  45 import android
.content
.BroadcastReceiver
; 
  46 import android
.content
.ComponentName
; 
  47 import android
.content
.Context
; 
  48 import android
.content
.DialogInterface
; 
  49 import android
.content
.Intent
; 
  50 import android
.content
.IntentFilter
; 
  51 import android
.content
.ServiceConnection
; 
  52 import android
.graphics
.Bitmap
; 
  53 import android
.graphics
.BitmapFactory
; 
  54 import android
.graphics
.BitmapFactory
.Options
; 
  55 import android
.graphics
.Point
; 
  56 import android
.media
.MediaPlayer
; 
  57 import android
.media
.MediaPlayer
.OnCompletionListener
; 
  58 import android
.media
.MediaPlayer
.OnErrorListener
; 
  59 import android
.media
.MediaPlayer
.OnPreparedListener
; 
  60 import android
.net
.Uri
; 
  61 import android
.os
.AsyncTask
; 
  62 import android
.os
.Bundle
; 
  63 import android
.os
.Handler
; 
  64 import android
.os
.IBinder
; 
  65 import android
.support
.v4
.app
.DialogFragment
; 
  66 import android
.support
.v4
.app
.FragmentTransaction
; 
  67 import android
.util
.Log
; 
  68 import android
.view
.Display
; 
  69 import android
.view
.LayoutInflater
; 
  70 import android
.view
.MotionEvent
; 
  71 import android
.view
.View
; 
  72 import android
.view
.View
.OnClickListener
; 
  73 import android
.view
.View
.OnTouchListener
; 
  74 import android
.view
.ViewGroup
; 
  75 import android
.webkit
.MimeTypeMap
; 
  76 import android
.widget
.Button
; 
  77 import android
.widget
.CheckBox
; 
  78 import android
.widget
.ImageView
; 
  79 import android
.widget
.MediaController
; 
  80 import android
.widget
.TextView
; 
  81 import android
.widget
.Toast
; 
  82 import android
.widget
.VideoView
; 
  83 import android
.widget
.AdapterView
.AdapterContextMenuInfo
; 
  85 import com
.actionbarsherlock
.app
.SherlockFragment
; 
  86 import com
.actionbarsherlock
.view
.Menu
; 
  87 import com
.actionbarsherlock
.view
.MenuInflater
; 
  88 import com
.actionbarsherlock
.view
.MenuItem
; 
  89 import com
.owncloud
.android
.AccountUtils
; 
  90 import com
.owncloud
.android
.DisplayUtils
; 
  91 import com
.owncloud
.android
.authenticator
.AccountAuthenticator
; 
  92 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  93 import com
.owncloud
.android
.datamodel
.OCFile
; 
  94 import com
.owncloud
.android
.files
.services
.FileDownloader
; 
  95 import com
.owncloud
.android
.files
.services
.FileObserverService
; 
  96 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  97 import com
.owncloud
.android
.files
.services
.FileDownloader
.FileDownloaderBinder
; 
  98 import com
.owncloud
.android
.files
.services
.FileUploader
.FileUploaderBinder
; 
  99 import com
.owncloud
.android
.media
.MediaService
; 
 100 import com
.owncloud
.android
.media
.MediaServiceBinder
; 
 101 import com
.owncloud
.android
.network
.OwnCloudClientUtils
; 
 102 import com
.owncloud
.android
.operations
.OnRemoteOperationListener
; 
 103 import com
.owncloud
.android
.operations
.RemoteOperation
; 
 104 import com
.owncloud
.android
.operations
.RemoteOperationResult
; 
 105 import com
.owncloud
.android
.operations
.RemoteOperationResult
.ResultCode
; 
 106 import com
.owncloud
.android
.operations
.RemoveFileOperation
; 
 107 import com
.owncloud
.android
.operations
.RenameFileOperation
; 
 108 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
; 
 109 import com
.owncloud
.android
.ui
.activity
.ConflictsResolveActivity
; 
 110 import com
.owncloud
.android
.ui
.activity
.FileDetailActivity
; 
 111 import com
.owncloud
.android
.ui
.activity
.FileDisplayActivity
; 
 112 import com
.owncloud
.android
.ui
.OnSwipeTouchListener
; 
 113 import com
.owncloud
.android
.ui
.activity
.TransferServiceGetter
; 
 114 import com
.owncloud
.android
.ui
.activity
.PreviewVideoActivity
; 
 115 import com
.owncloud
.android
.ui
.dialog
.EditNameDialog
; 
 116 import com
.owncloud
.android
.ui
.dialog
.EditNameDialog
.EditNameDialogListener
; 
 117 import com
.owncloud
.android
.utils
.OwnCloudVersion
; 
 119 import com
.owncloud
.android
.R
; 
 120 import eu
.alefzero
.webdav
.WebdavClient
; 
 121 import eu
.alefzero
.webdav
.WebdavUtils
; 
 124  * This fragment shows a preview of a downloaded file. 
 126  * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}. 
 128  * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too. 
 130  * @author David A. Velasco 
 132 public class FilePreviewFragment 
extends SherlockFragment 
implements 
 133         /*OnClickListener,*/ OnTouchListener 
, FileFragment
,   
 134         ConfirmationDialogFragment
.ConfirmationDialogFragmentListener
, OnRemoteOperationListener 
/*, EditNameDialogListener*/ { 
 136     public static final String EXTRA_FILE 
= "FILE"; 
 137     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
 138     private static final String EXTRA_PLAY_POSITION 
= "PLAY_POSITION"; 
 141     private OCFile mFile
; 
 142     private Account mAccount
; 
 143     private FileDataStorageManager mStorageManager
; 
 144     private ImageView mImagePreview
; 
 145     public Bitmap mBitmap 
= null
; 
 146     private VideoView mVideoPreview
; 
 147     private int mSavedPlaybackPosition
; 
 149     //private DownloadFinishReceiver mDownloadFinishReceiver; 
 150     //private UploadFinishReceiver mUploadFinishReceiver; 
 152     private Handler mHandler
; 
 153     private RemoteOperation mLastRemoteOperation
; 
 155     private MediaServiceBinder mMediaServiceBinder 
= null
; 
 156     private MediaController mMediaController 
= null
; 
 157     private MediaServiceConnection mMediaServiceConnection 
= null
; 
 158     private VideoHelper mVideoHelper
; 
 160     private static final String TAG 
= FilePreviewFragment
.class.getSimpleName(); 
 164      * Creates a fragment to preview a file. 
 166      * When 'fileToDetail' or 'ocAccount' are null 
 168      * @param fileToDetail      An {@link OCFile} to preview in the fragment 
 169      * @param ocAccount         An ownCloud account; needed to start downloads 
 171     public FilePreviewFragment(OCFile fileToDetail
, Account ocAccount
) { 
 172         mFile 
= fileToDetail
; 
 173         mAccount 
= ocAccount
; 
 174         mSavedPlaybackPosition 
= 0; 
 175         mStorageManager 
= null
; // we need a context to init this; the container activity is not available yet at this moment  
 180      *  Creates an empty fragment for previews. 
 182      *  MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside). 
 184      *  DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction  
 186     public FilePreviewFragment() { 
 189         mSavedPlaybackPosition 
= 0; 
 190         mStorageManager 
= null
; 
 198     public void onCreate(Bundle savedInstanceState
) { 
 199         super.onCreate(savedInstanceState
); 
 200         mHandler 
= new Handler(); 
 201         setHasOptionsMenu(true
); 
 209     public View 
onCreateView(LayoutInflater inflater
, ViewGroup container
, 
 210             Bundle savedInstanceState
) { 
 211         super.onCreateView(inflater
, container
, savedInstanceState
); 
 213         mView 
= inflater
.inflate(R
.layout
.file_preview
, container
, false
); 
 215         //mView.findViewById(R.id.fdKeepInSync).setOnClickListener(this); 
 216         //mView.findViewById(R.id.fdRenameBtn).setOnClickListener(this); 
 217         //mView.findViewById(R.id.fdDownloadBtn).setOnClickListener(this); 
 218         //mView.findViewById(R.id.fdOpenBtn).setOnClickListener(this); 
 219         //mView.findViewById(R.id.fdRemoveBtn).setOnClickListener(this); 
 220         mImagePreview 
= (ImageView
)mView
.findViewById(R
.id
.image_preview
); 
 221         mImagePreview
.setOnTouchListener(this); 
 222         mVideoPreview 
= (VideoView
)mView
.findViewById(R
.id
.video_preview
); 
 224         //updateFileDetails(false); 
 233     public void onAttach(Activity activity
) { 
 234         super.onAttach(activity
); 
 235         if (!(activity 
instanceof FileFragment
.ContainerActivity
)) 
 236             throw new ClassCastException(activity
.toString() + " must implement " + FileFragment
.ContainerActivity
.class.getSimpleName()); 
 244     public void onActivityCreated(Bundle savedInstanceState
) { 
 245         super.onActivityCreated(savedInstanceState
); 
 247         mStorageManager 
= new FileDataStorageManager(mAccount
, getActivity().getApplicationContext().getContentResolver()); 
 248         if (savedInstanceState 
!= null
) { 
 249             mFile 
= savedInstanceState
.getParcelable(FilePreviewFragment
.EXTRA_FILE
); 
 250             mAccount 
= savedInstanceState
.getParcelable(FilePreviewFragment
.EXTRA_ACCOUNT
); 
 251             mSavedPlaybackPosition 
= savedInstanceState
.getInt(FilePreviewFragment
.EXTRA_PLAY_POSITION
); 
 255             throw new IllegalStateException("Instanced with a NULL OCFile"); 
 257         if (mAccount 
== null
) { 
 258             throw new IllegalStateException("Instanced with a NULL ownCloud Account"); 
 260         if (!mFile
.isDown()) { 
 261             throw new IllegalStateException("There is no local file to preview"); 
 263         if (mFile
.isVideo()) { 
 264             mVideoPreview
.setVisibility(View
.VISIBLE
); 
 265             mImagePreview
.setVisibility(View
.GONE
); 
 269             mVideoPreview
.setVisibility(View
.GONE
); 
 270             mImagePreview
.setVisibility(View
.VISIBLE
); 
 280     public void onSaveInstanceState(Bundle outState
) { 
 281         super.onSaveInstanceState(outState
); 
 283         outState
.putParcelable(FilePreviewFragment
.EXTRA_FILE
, mFile
); 
 284         outState
.putParcelable(FilePreviewFragment
.EXTRA_ACCOUNT
, mAccount
); 
 286         if (mVideoPreview
.isPlaying()) { 
 287             outState
.putInt(FilePreviewFragment
.EXTRA_PLAY_POSITION 
, mVideoPreview
.getCurrentPosition()); 
 293     public void onStart() { 
 297            if (mFile
.isAudio()) { 
 300            } else if (mFile
.isImage()) { 
 301                BitmapLoader bl 
= new BitmapLoader(mImagePreview
); 
 302                bl
.execute(new String
[]{mFile
.getStoragePath()}); 
 304            } else if (mFile
.isVideo()) { 
 315     public void onCreateOptionsMenu(Menu menu
, MenuInflater inflater
) { 
 316         super.onCreateOptionsMenu(menu
, inflater
); 
 318         inflater
.inflate(R
.menu
.file_actions_menu
, menu
); 
 319         List
<Integer
> toHide 
= new ArrayList
<Integer
>();     
 321         MenuItem item 
= null
; 
 322         toHide
.add(R
.id
.action_cancel_download
); 
 323         toHide
.add(R
.id
.action_cancel_upload
); 
 324         toHide
.add(R
.id
.action_download_file
); 
 325         toHide
.add(R
.id
.action_rename_file
);    // by now 
 327         for (int i 
: toHide
) { 
 328             item 
= menu
.findItem(i
); 
 330                 item
.setVisible(false
); 
 331                 item
.setEnabled(false
); 
 342     public boolean onOptionsItemSelected(MenuItem item
) { 
 343         switch (item
.getItemId()) { 
 344             case R
.id
.action_open_file_with
: { 
 348             case R
.id
.action_remove_file
: { 
 352             case R
.id
.action_see_details
: { 
 358             case R.id.action_toggle_keep_in_sync: { 
 359                 CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync); 
 360                 mFile.setKeepInSync(cb.isChecked()); 
 361                 mStorageManager.saveFile(mFile); 
 363                 /// register the OCFile instance in the observer service to monitor local updates; 
 364                 /// if necessary, the file is download  
 365                 Intent intent = new Intent(getActivity().getApplicationContext(), 
 366                                            FileObserverService.class); 
 367                 intent.putExtra(FileObserverService.KEY_FILE_CMD, 
 369                                    FileObserverService.CMD_ADD_OBSERVED_FILE: 
 370                                    FileObserverService.CMD_DEL_OBSERVED_FILE)); 
 371                 intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, mFile); 
 372                 intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount); 
 373                 Log.e(TAG, "starting observer service"); 
 374                 getActivity().startService(intent); 
 376                 if (mFile.keepInSync()) { 
 377                     onClick(getView().findViewById(R.id.fdDownloadBtn));    // force an immediate synchronization 
 387     private void seeDetails() { 
 389         ((FileFragment
.ContainerActivity
)getActivity()).showFragmentWithDetails(mFile
);         
 393     private void prepareVideo() { 
 394         // create helper to get more control on the playback 
 395         mVideoHelper 
= new VideoHelper(); 
 396         mVideoPreview
.setOnPreparedListener(mVideoHelper
); 
 397         mVideoPreview
.setOnCompletionListener(mVideoHelper
); 
 398         mVideoPreview
.setOnErrorListener(mVideoHelper
); 
 401     private void playVideo() { 
 402         // load the video file in the video player ; when done, VideoHelper#onPrepared() will be called 
 403         mVideoPreview
.setVideoPath(mFile
.getStoragePath());  
 405         // create and prepare control panel for the user 
 406         mMediaController 
= new MediaController(getActivity()); 
 407         mMediaController
.setMediaPlayer(mVideoPreview
); 
 408         mMediaController
.setAnchorView(mVideoPreview
); 
 409         mVideoPreview
.setMediaController(mMediaController
); 
 413     private class VideoHelper 
implements OnCompletionListener
, OnPreparedListener
, OnErrorListener 
{ 
 416          * Called when the file is ready to be played. 
 418          * Just starts the playback. 
 420          * @param   mp    {@link MediaPlayer} instance performing the playback. 
 423         public void onPrepared(MediaPlayer vp
) { 
 424             mVideoPreview
.seekTo(mSavedPlaybackPosition
); 
 425             mVideoPreview
.start(); 
 426             mMediaController
.show(MediaService
.MEDIA_CONTROL_SHORT_LIFE
);   
 431          * Called when the file is finished playing. 
 433          * Finishes the activity. 
 435          * @param   mp    {@link MediaPlayer} instance performing the playback. 
 438         public void onCompletion(MediaPlayer  mp
) { 
 439             // nothing, right now 
 444          * Called when an error in playback occurs. 
 446          * @param   mp      {@link MediaPlayer} instance performing the playback. 
 447          * @param   what    Type of error 
 448          * @param   extra   Extra code specific to the error 
 451         public boolean onError(MediaPlayer mp
, int what
, int extra
) { 
 452             Log
.e(TAG
, "Error in video playback, what = " + what 
+ ", extra = " + extra
); 
 454             if (mMediaController 
!= null
) { 
 455                 mMediaController
.hide(); 
 458             if (mVideoPreview
.getWindowToken() != null
) { 
 459                 String message 
= MediaService
.getMessageForMediaError(getActivity(), what
, extra
); 
 460                 new AlertDialog
.Builder(getActivity()) 
 462                         .setPositiveButton(android
.R
.string
.VideoView_error_button
, 
 463                                 new DialogInterface
.OnClickListener() { 
 464                                     public void onClick(DialogInterface dialog
, int whichButton
) { 
 466                                         VideoHelper
.this.onCompletion(null
); 
 469                         .setCancelable(false
) 
 479     public void onResume() { 
 482         mDownloadFinishReceiver = new DownloadFinishReceiver(); 
 483         IntentFilter filter = new IntentFilter( 
 484                 FileDownloader.DOWNLOAD_FINISH_MESSAGE); 
 485         getActivity().registerReceiver(mDownloadFinishReceiver, filter); 
 487         mUploadFinishReceiver = new UploadFinishReceiver(); 
 488         filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); 
 489         getActivity().registerReceiver(mUploadFinishReceiver, filter); 
 496     public void onPause() { 
 499         if (mVideoPreview.getVisibility() == View.VISIBLE) { 
 500             mSavedPlaybackPosition = mVideoPreview.getCurrentPosition(); 
 503         getActivity().unregisterReceiver(mDownloadFinishReceiver); 
 504         mDownloadFinishReceiver = null; 
 506         getActivity().unregisterReceiver(mUploadFinishReceiver); 
 507         mUploadFinishReceiver = null; 
 513     public void onStop() { 
 515         if (mMediaServiceConnection 
!= null
) { 
 516             Log
.d(TAG
, "Unbinding from MediaService ..."); 
 517             if (mMediaServiceBinder 
!= null 
&& mMediaController 
!= null
) { 
 518                 mMediaServiceBinder
.unregisterMediaController(mMediaController
); 
 520             getActivity().unbindService(mMediaServiceConnection
); 
 521             mMediaServiceConnection 
= null
; 
 522             mMediaServiceBinder 
= null
; 
 523             if (mMediaController 
!= null
) { 
 524                 mMediaController
.hide(); 
 525                 mMediaController 
= null
; 
 531     public void onDestroy() { 
 533         if (mBitmap 
!= null
) { 
 540     public View getView() { 
 541         return super.getView() == null ? mView : super.getView(); 
 547     public void onClick(View v) { 
 549             case R.id.fdDownloadBtn: { 
 550                 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); 
 551                 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); 
 552                 if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) { 
 553                     downloaderBinder.cancel(mAccount, mFile); 
 554                     if (mFile.isDown()) { 
 557                         setButtonsForRemote(); 
 560                 } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) { 
 561                     uploaderBinder.cancel(mAccount, mFile); 
 562                     if (!mFile.fileExists()) { 
 563                         // TODO make something better 
 564                         if (getActivity() instanceof FileDisplayActivity) { 
 566                             FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction(); 
 567                             transaction.replace(R.id.file_details_container, new FilePreviewFragment(null, null), FTAG); // empty FileDetailFragment 
 568                             transaction.commit(); 
 569                             mContainerActivity.onFileStateChanged(); 
 571                             getActivity().finish(); 
 574                     } else if (mFile.isDown()) { 
 577                         setButtonsForRemote(); 
 581                     mLastRemoteOperation = new SynchronizeFileOperation(mFile, null, mStorageManager, mAccount, true, false, getActivity()); 
 582                     WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); 
 583                     mLastRemoteOperation.execute(wc, this, mHandler); 
 586                     boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; 
 587                     getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); 
 588                     setButtonsForTransferring(); // disable button immediately, although the synchronization does not result in a file transference 
 593             case R.id.fdKeepInSync: { 
 594                 CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync); 
 595                 mFile.setKeepInSync(cb.isChecked()); 
 596                 mStorageManager.saveFile(mFile); 
 598                 /// register the OCFile instance in the observer service to monitor local updates; 
 599                 /// if necessary, the file is download  
 600                 Intent intent = new Intent(getActivity().getApplicationContext(), 
 601                                            FileObserverService.class); 
 602                 intent.putExtra(FileObserverService.KEY_FILE_CMD, 
 604                                    FileObserverService.CMD_ADD_OBSERVED_FILE: 
 605                                    FileObserverService.CMD_DEL_OBSERVED_FILE)); 
 606                 intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, mFile); 
 607                 intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount); 
 608                 Log.e(TAG, "starting observer service"); 
 609                 getActivity().startService(intent); 
 611                 if (mFile.keepInSync()) { 
 612                     onClick(getView().findViewById(R.id.fdDownloadBtn));    // force an immediate synchronization 
 616             case R.id.fdRenameBtn: { 
 617                 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mFile.getFileName(), this); 
 618                 dialog.show(getFragmentManager(), "nameeditdialog"); 
 621             case R.id.fdRemoveBtn: { 
 622                 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( 
 623                         R.string.confirmation_remove_alert, 
 624                         new String[]{mFile.getFileName()}, 
 625                         mFile.isDown() ? R.string.confirmation_remove_remote_and_local : R.string.confirmation_remove_remote, 
 626                         mFile.isDown() ? R.string.confirmation_remove_local : -1, 
 627                         R.string.common_cancel); 
 628                 confDialog.setOnConfirmationListener(this); 
 629                 mCurrentDialog = confDialog; 
 630                 mCurrentDialog.show(getFragmentManager(), FTAG_CONFIRMATION); 
 633             case R.id.fdOpenBtn: { 
 638                 Log.e(TAG, "Incorrect view clicked!"); 
 646     public boolean onTouch(View v
, MotionEvent event
) { 
 647         if (event
.getAction() == MotionEvent
.ACTION_DOWN
) {  
 648             if (v 
== mImagePreview 
&& 
 649                     mMediaServiceBinder 
!= null 
&& mFile
.isAudio() && mMediaServiceBinder
.isPlaying(mFile
)) { 
 650                 toggleMediaController(MediaService
.MEDIA_CONTROL_PERMANENT
); 
 653             } else if (v 
== mVideoPreview
) { 
 654                 toggleMediaController(MediaService
.MEDIA_CONTROL_SHORT_LIFE
); 
 662     private void toggleMediaController(int time
) { 
 663         if (mMediaController
.isShowing()) { 
 664             mMediaController
.hide(); 
 666             mMediaController
.show(time
); 
 671     private void playAudio() { 
 672         if (!mMediaServiceBinder
.isPlaying(mFile
)) { 
 673             Log
.d(TAG
, "starting playback of " + mFile
.getStoragePath()); 
 674             mMediaServiceBinder
.start(mAccount
, mFile
); 
 677             if (!mMediaServiceBinder
.isPlaying()) { 
 678                 mMediaServiceBinder
.start(); 
 680             if (!mMediaController
.isShowing() && isVisible()) { 
 681                 mMediaController
.show(MediaService
.MEDIA_CONTROL_PERMANENT
); 
 682                 // TODO - fix strange bug; steps to trigger : 
 683                 // 1. remove the "isVisible()" control 
 684                 // 2. start the app and preview an audio file 
 685                 // 3. exit from the app (home button, for instance) while the audio file is still being played  
 686                 // 4. go to notification bar and click on the "ownCloud music app" notification 
 693     private void bindMediaService() { 
 694         Log
.d(TAG
, "Binding to MediaService..."); 
 695         if (mMediaServiceConnection 
== null
) { 
 696             mMediaServiceConnection 
= new MediaServiceConnection(); 
 698         getActivity().bindService(  new Intent(getActivity(),  
 700                                     mMediaServiceConnection
,  
 701                                     Context
.BIND_AUTO_CREATE
); 
 702             // follow the flow in MediaServiceConnection#onServiceConnected(...) 
 705     /** Defines callbacks for service binding, passed to bindService() */ 
 706     private class MediaServiceConnection 
implements ServiceConnection 
{ 
 709         public void onServiceConnected(ComponentName component
, IBinder service
) { 
 710             if (component
.equals(new ComponentName(getActivity(), MediaService
.class))) { 
 711                 Log
.d(TAG
, "Media service connected"); 
 712                 mMediaServiceBinder 
= (MediaServiceBinder
) service
; 
 713                 if (mMediaServiceBinder 
!= null
) { 
 714                     if (mMediaController 
== null
) { 
 715                         mMediaController 
= new MediaController(getSherlockActivity()); 
 717                     prepareMediaController(); 
 718                     playAudio();    // do not wait for the touch of nobody to play audio 
 720                     Log
.d(TAG
, "Successfully bound to MediaService, MediaController ready"); 
 723                     Log
.e(TAG
, "Unexpected response from MediaService while binding"); 
 728         private void prepareMediaController() { 
 729             mMediaServiceBinder
.registerMediaController(mMediaController
); 
 730             mMediaController
.setMediaPlayer(mMediaServiceBinder
); 
 731             mMediaController
.setAnchorView(getView()); 
 732             mMediaController
.setEnabled(mMediaServiceBinder
.isInPlaybackState()); 
 736         public void onServiceDisconnected(ComponentName component
) { 
 737             if (component
.equals(new ComponentName(getActivity(), MediaService
.class))) { 
 738                 Log
.e(TAG
, "Media service suddenly disconnected"); 
 739                 if (mMediaController 
!= null
) { 
 740                     mMediaController
.hide(); 
 741                     mMediaController
.setMediaPlayer(null
); 
 742                     mMediaController 
= null
; 
 744                 mMediaServiceBinder 
= null
; 
 745                 mMediaServiceConnection 
= null
; 
 753      * Opens the previewed file with an external application. 
 755      * TODO - improve this; instead of prioritize the actions available for the MIME type in the server,  
 756      * we should get a list of available apps for MIME tpye in the server and join it with the list of  
 757      * available apps for the MIME type known from the file extension, to let the user choose 
 759     private void openFile() { 
 761         String storagePath 
= mFile
.getStoragePath(); 
 762         String encodedStoragePath 
= WebdavUtils
.encodePath(storagePath
); 
 764             Intent i 
= new Intent(Intent
.ACTION_VIEW
); 
 765             i
.setDataAndType(Uri
.parse("file://"+ encodedStoragePath
), mFile
.getMimetype()); 
 766             i
.setFlags(Intent
.FLAG_GRANT_READ_URI_PERMISSION 
| Intent
.FLAG_GRANT_WRITE_URI_PERMISSION
); 
 769         } catch (Throwable t
) { 
 770             Log
.e(TAG
, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile
.getMimetype()); 
 771             boolean toastIt 
= true
;  
 772             String mimeType 
= ""; 
 774                 Intent i 
= new Intent(Intent
.ACTION_VIEW
); 
 775                 mimeType 
= MimeTypeMap
.getSingleton().getMimeTypeFromExtension(storagePath
.substring(storagePath
.lastIndexOf('.') + 1)); 
 776                 if (mimeType 
== null 
|| !mimeType
.equals(mFile
.getMimetype())) { 
 777                     if (mimeType 
!= null
) { 
 778                         i
.setDataAndType(Uri
.parse("file://"+ encodedStoragePath
), mimeType
); 
 781                         i
.setDataAndType(Uri
.parse("file://"+ encodedStoragePath
), "*-/*"); 
 783                     i
.setFlags(Intent
.FLAG_GRANT_READ_URI_PERMISSION 
| Intent
.FLAG_GRANT_WRITE_URI_PERMISSION
); 
 788             } catch (IndexOutOfBoundsException e
) { 
 789                 Log
.e(TAG
, "Trying to find out MIME type of a file without extension: " + storagePath
); 
 791             } catch (ActivityNotFoundException e
) { 
 792                 Log
.e(TAG
, "No activity found to handle: " + storagePath 
+ " with MIME type " + mimeType 
+ " obtained from extension"); 
 794             } catch (Throwable th
) { 
 795                 Log
.e(TAG
, "Unexpected problem when opening: " + storagePath
, th
); 
 799                     Toast
.makeText(getActivity(), "There is no application to handle file " + mFile
.getFileName(), Toast
.LENGTH_SHORT
).show(); 
 808      * Starts a the removal of the previewed file. 
 810      * Shows a confirmation dialog. The action continues in {@link #onConfirmation(String)} , {@link #onNeutral(String)} or {@link #onCancel(String)}, 
 811      * depending upon the user selection in the dialog.  
 813     private void removeFile() { 
 814         ConfirmationDialogFragment confDialog 
= ConfirmationDialogFragment
.newInstance( 
 815                 R
.string
.confirmation_remove_alert
, 
 816                 new String
[]{mFile
.getFileName()}, 
 817                 R
.string
.confirmation_remove_remote_and_local
, 
 818                 R
.string
.confirmation_remove_local
, 
 819                 R
.string
.common_cancel
); 
 820         confDialog
.setOnConfirmationListener(this); 
 821         confDialog
.show(getFragmentManager(), ConfirmationDialogFragment
.FTAG_CONFIRMATION
); 
 826      * Performs the removal of the previewed file, both locally and in the server. 
 829     public void onConfirmation(String callerTag
) { 
 830         if (mStorageManager
.getFileById(mFile
.getFileId()) != null
) {   // check that the file is still there; 
 832             mLastRemoteOperation 
= new RemoveFileOperation( mFile
,      // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters 
 835             WebdavClient wc 
= OwnCloudClientUtils
.createOwnCloudClient(mAccount
, getSherlockActivity().getApplicationContext()); 
 836             mLastRemoteOperation
.execute(wc
, this, mHandler
); 
 838             boolean inDisplayActivity 
= getActivity() instanceof FileDisplayActivity
; 
 839             getActivity().showDialog((inDisplayActivity
)? FileDisplayActivity
.DIALOG_SHORT_WAIT 
: FileDetailActivity
.DIALOG_SHORT_WAIT
); 
 845      * Removes the file from local storage 
 848     public void onNeutral(String callerTag
) { 
 849         // TODO this code should be made in a secondary thread, 
 850         if (mFile
.isDown()) {   // checks it is still there 
 852             File f 
= new File(mFile
.getStoragePath()); 
 854             mFile
.setStoragePath(null
); 
 855             mStorageManager
.saveFile(mFile
); 
 861      * User cancelled the removal action. 
 864     public void onCancel(String callerTag
) { 
 865         // nothing to do here 
 872     public OCFile 
getFile(){ 
 878      * Use this method to signal this Activity that it shall update its view. 
 880      * @param file : An {@link OCFile} 
 882     public void updateFileDetails(OCFile file, Account ocAccount) { 
 884         if (ocAccount != null && (  
 885                 mStorageManager == null ||  
 886                 (mAccount != null && !mAccount.equals(ocAccount)) 
 888             mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver()); 
 890         mAccount = ocAccount; 
 891         updateFileDetails(false); 
 897      * Interface to implement by any Activity that includes some instance of FileDetailFragment 
 899      * @author David A. Velasco 
 901     public interface ContainerActivity 
extends TransferServiceGetter 
{ 
 904          * Callback method invoked when the detail fragment wants to notice its container  
 905          * activity about a relevant state the file shown by the fragment. 
 907          * Added to notify to FileDisplayActivity about the need of refresh the files list.  
 909          * Currently called when: 
 910          *  - a download is started; 
 911          *  - a rename is completed; 
 912          *  - a deletion is completed; 
 913          *  - the 'inSync' flag is changed; 
 915         public void onFileStateChanged(); 
 920     public void onDismiss(EditNameDialog dialog) { 
 921         if (dialog.getResult()) { 
 922             String newFilename = dialog.getNewFilename(); 
 923             Log.d(TAG, "name edit dialog dismissed with new name " + newFilename); 
 924             mLastRemoteOperation = new RenameFileOperation( mFile,  
 927                                                             new FileDataStorageManager(mAccount, getActivity().getContentResolver())); 
 928             WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); 
 929             mLastRemoteOperation.execute(wc, this, mHandler); 
 930             boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; 
 931             getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); 
 936     private class BitmapLoader 
extends AsyncTask
<String
, Void
, Bitmap
> { 
 939          * Weak reference to the target {@link ImageView} where the bitmap will be loaded into. 
 941          * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. 
 943         private final WeakReference
<ImageView
> mImageViewRef
; 
 949          * @param imageView     Target {@link ImageView} where the bitmap will be loaded into. 
 951         public BitmapLoader(ImageView imageView
) { 
 952             mImageViewRef 
= new WeakReference
<ImageView
>(imageView
); 
 956         @SuppressWarnings("deprecation") 
 957         @SuppressLint({ "NewApi", "NewApi", "NewApi" }) // to avoid Lint errors since Android SDK r20 
 959         protected Bitmap 
doInBackground(String
... params
) { 
 960             Bitmap result 
= null
; 
 961             if (params
.length 
!= 1) return result
; 
 962             String storagePath 
= params
[0]; 
 964                 // set desired options that will affect the size of the bitmap 
 965                 BitmapFactory
.Options options 
= new Options(); 
 966                 options
.inScaled 
= true
; 
 967                 options
.inPurgeable 
= true
; 
 968                 if (android
.os
.Build
.VERSION
.SDK_INT 
>= android
.os
.Build
.VERSION_CODES
.GINGERBREAD_MR1
) { 
 969                     options
.inPreferQualityOverSpeed 
= false
; 
 971                 if (android
.os
.Build
.VERSION
.SDK_INT 
>= android
.os
.Build
.VERSION_CODES
.HONEYCOMB
) { 
 972                     options
.inMutable 
= false
; 
 974                 // make a false load of the bitmap - just to be able to read outWidth, outHeight and outMimeType 
 975                 options
.inJustDecodeBounds 
= true
; 
 976                 BitmapFactory
.decodeFile(storagePath
, options
);    
 978                 int width 
= options
.outWidth
; 
 979                 int height 
= options
.outHeight
; 
 981                 if (width 
>= 2048 || height 
>= 2048) {   
 982                     // try to scale down the image to save memory   
 983                     scale 
= (int) Math
.ceil((Math
.ceil(Math
.max(height
, width
) / 2048.))); 
 984                     options
.inSampleSize 
= scale
; 
 986                 Display display 
= getActivity().getWindowManager().getDefaultDisplay(); 
 987                 Point size 
= new Point(); 
 989                 if (android
.os
.Build
.VERSION
.SDK_INT 
>= android
.os
.Build
.VERSION_CODES
.HONEYCOMB_MR2
) { 
 990                     display
.getSize(size
); 
 991                     screenwidth 
= size
.x
; 
 993                     screenwidth 
= display
.getWidth(); 
 996                 Log
.d(TAG
, "image width: " + width 
+ ", screen width: " + screenwidth
); 
 998                 if (width 
> screenwidth
) { 
 999                     // second try to scale down the image , this time depending upon the screen size; WTF...  
1000                     scale 
= (int) Math
.ceil((float)width 
/ screenwidth
); 
1001                     options
.inSampleSize 
= scale
; 
1004                 // really load the bitmap 
1005                 options
.inJustDecodeBounds 
= false
; // the next decodeFile call will be real 
1006                 result 
= BitmapFactory
.decodeFile(storagePath
, options
); 
1007                 Log
.e(TAG
, "loaded width: " + options
.outWidth 
+ ", loaded height: " + options
.outHeight
); 
1009             } catch (OutOfMemoryError e
) { 
1011                 Log
.e(TAG
, "Out of memory occured for file with size " + storagePath
); 
1013             } catch (NoSuchFieldError e
) { 
1015                 Log
.e(TAG
, "Error from access to unexisting field despite protection " + storagePath
); 
1017             } catch (Throwable t
) { 
1019                 Log
.e(TAG
, "Unexpected error while creating image preview " + storagePath
, t
); 
1025         protected void onPostExecute(Bitmap result
) { 
1026             if (result 
!= null 
&& mImageViewRef 
!= null
) { 
1027                 final ImageView imageView 
= mImageViewRef
.get(); 
1028                 imageView
.setImageBitmap(result
); 
1036      * Helper method to test if an {@link OCFile} can be passed to a {@link FilePreviewFragment} to be previewed. 
1038      * @param file      File to test if can be previewed. 
1039      * @return          'True' if the file can be handled by the fragment. 
1041     public static boolean canBePreviewed(OCFile file
) { 
1042         return (file 
!= null 
&& (file
.isAudio() || file
.isVideo() || file
.isImage())); 
1049     public void onRemoteOperationFinish(RemoteOperation operation
, RemoteOperationResult result
) { 
1050         if (operation
.equals(mLastRemoteOperation
)) { 
1051             if (operation 
instanceof RemoveFileOperation
) { 
1052                 onRemoveFileOperationFinish((RemoveFileOperation
)operation
, result
); 
1055             } else if (operation instanceof RenameFileOperation) { 
1056                 onRenameFileOperationFinish((RenameFileOperation)operation, result); 
1058             } else if (operation instanceof SynchronizeFileOperation) { 
1059                 onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result);*/ 
1064     private void onRemoveFileOperationFinish(RemoveFileOperation operation
, RemoteOperationResult result
) { 
1065         boolean inDisplayActivity 
= getActivity() instanceof FileDisplayActivity
; 
1066         getActivity().dismissDialog((inDisplayActivity
)? FileDisplayActivity
.DIALOG_SHORT_WAIT 
: FileDetailActivity
.DIALOG_SHORT_WAIT
); 
1068         if (result
.isSuccess()) { 
1069             Toast msg 
= Toast
.makeText(getActivity().getApplicationContext(), R
.string
.remove_success_msg
, Toast
.LENGTH_LONG
); 
1074             Toast msg 
= Toast
.makeText(getActivity(), R
.string
.remove_fail_msg
, Toast
.LENGTH_LONG
);  
1076             if (result
.isSslRecoverableException()) { 
1077                 // TODO show the SSL warning dialog 
1082     private void stopPreview(boolean stopAudio
) { 
1083         if (mMediaController 
!= null
) { 
1084             mMediaController
.hide(); 
1086         if (mFile
.isAudio() && stopAudio
) { 
1087             mMediaServiceBinder
.pause(); 
1089         } else if (mFile
.isVideo()) { 
1090             mVideoPreview
.stopPlayback(); 
1097      * Finishes the preview 
1099     private void finish() { 
1100         Activity container 
= getActivity(); 
1101         if (container 
instanceof FileDisplayActivity
) { 
1103             FragmentTransaction transaction 
= getActivity().getSupportFragmentManager().beginTransaction(); 
1104             transaction
.replace(R
.id
.file_details_container
, new FileDetailFragment(null
, null
), FileDetailFragment
.FTAG
); // empty FileDetailFragment 
1105             transaction
.commit(); 
1106             ((FileFragment
.ContainerActivity
)container
).onFileStateChanged(); 
1113     private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) { 
1114         boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; 
1115         getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); 
1117         if (result.isSuccess()) { 
1118             updateFileDetails(((RenameFileOperation)operation).getFile(), mAccount); 
1119             mContainerActivity.onFileStateChanged(); 
1122             if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) { 
1123                 Toast msg = Toast.makeText(getActivity(), R.string.rename_local_fail_msg, Toast.LENGTH_LONG);  
1125                 // TODO throw again the new rename dialog 
1127                 Toast msg = Toast.makeText(getActivity(), R.string.rename_server_fail_msg, Toast.LENGTH_LONG);  
1129                 if (result.isSslRecoverableException()) { 
1130                     // TODO show the SSL warning dialog 
1136     private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) { 
1137         boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; 
1138         getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); 
1140         if (!result.isSuccess()) { 
1141             if (result.getCode() == ResultCode.SYNC_CONFLICT) { 
1142                 Intent i = new Intent(getActivity(), ConflictsResolveActivity.class); 
1143                 i.putExtra(ConflictsResolveActivity.EXTRA_FILE, mFile); 
1144                 i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount); 
1148                 Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG);  
1152             if (mFile.isDown()) { 
1153                 setButtonsForDown(); 
1156                 setButtonsForRemote(); 
1160             if (operation.transferWasRequested()) { 
1161                 mContainerActivity.onFileStateChanged();    // this is not working; FileDownloader won't do NOTHING at all until this method finishes, so  
1162                                                             // checking the service to see if the file is downloading results in FALSE 
1164                 Toast msg = Toast.makeText(getActivity(), R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG);  
1166                 if (mFile.isDown()) { 
1167                     setButtonsForDown(); 
1170                     setButtonsForRemote();