1 /* ownCloud Android client application 
   2  *   Copyright (C) 2011  Bartek Przybylski 
   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. 
   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
.io
.IOException
; 
  22 import java
.util
.ArrayList
; 
  23 import java
.util
.List
; 
  25 import org
.apache
.commons
.httpclient
.HttpException
; 
  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
.message
.BasicNameValuePair
; 
  34 import org
.apache
.http
.protocol
.HTTP
; 
  35 import org
.apache
.jackrabbit
.webdav
.client
.methods
.DavMethodBase
; 
  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
.content
.ActivityNotFoundException
; 
  44 import android
.content
.BroadcastReceiver
; 
  45 import android
.content
.Context
; 
  46 import android
.content
.Intent
; 
  47 import android
.content
.IntentFilter
; 
  48 import android
.content
.res
.Resources
.NotFoundException
; 
  49 import android
.graphics
.Bitmap
; 
  50 import android
.graphics
.BitmapFactory
; 
  51 import android
.graphics
.BitmapFactory
.Options
; 
  52 import android
.graphics
.Point
; 
  53 import android
.net
.Uri
; 
  54 import android
.os
.AsyncTask
; 
  55 import android
.os
.Bundle
; 
  56 import android
.os
.Handler
; 
  57 import android
.support
.v4
.app
.FragmentTransaction
; 
  58 import android
.util
.Log
; 
  59 import android
.view
.Display
; 
  60 import android
.view
.LayoutInflater
; 
  61 import android
.view
.View
; 
  62 import android
.view
.View
.OnClickListener
; 
  63 import android
.view
.ViewGroup
; 
  64 import android
.view
.WindowManager
.LayoutParams
; 
  65 import android
.webkit
.MimeTypeMap
; 
  66 import android
.widget
.Button
; 
  67 import android
.widget
.CheckBox
; 
  68 import android
.widget
.ImageView
; 
  69 import android
.widget
.TextView
; 
  70 import android
.widget
.Toast
; 
  72 import com
.actionbarsherlock
.app
.SherlockDialogFragment
; 
  73 import com
.actionbarsherlock
.app
.SherlockFragment
; 
  74 import com
.owncloud
.android
.AccountUtils
; 
  75 import com
.owncloud
.android
.DisplayUtils
; 
  76 import com
.owncloud
.android
.authenticator
.AccountAuthenticator
; 
  77 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  78 import com
.owncloud
.android
.datamodel
.OCFile
; 
  79 import com
.owncloud
.android
.files
.services
.FileDownloader
; 
  80 import com
.owncloud
.android
.files
.services
.FileObserverService
; 
  81 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  82 import com
.owncloud
.android
.files
.services
.FileDownloader
.FileDownloaderBinder
; 
  83 import com
.owncloud
.android
.files
.services
.FileUploader
.FileUploaderBinder
; 
  84 import com
.owncloud
.android
.network
.OwnCloudClientUtils
; 
  85 import com
.owncloud
.android
.operations
.OnRemoteOperationListener
; 
  86 import com
.owncloud
.android
.operations
.RemoteOperation
; 
  87 import com
.owncloud
.android
.operations
.RemoteOperationResult
; 
  88 import com
.owncloud
.android
.operations
.RemoveFileOperation
; 
  89 import com
.owncloud
.android
.ui
.activity
.FileDetailActivity
; 
  90 import com
.owncloud
.android
.ui
.activity
.FileDisplayActivity
; 
  91 import com
.owncloud
.android
.ui
.activity
.TransferServiceGetter
; 
  92 import com
.owncloud
.android
.utils
.OwnCloudVersion
; 
  94 import com
.owncloud
.android
.R
; 
  95 import eu
.alefzero
.webdav
.WebdavClient
; 
  96 import eu
.alefzero
.webdav
.WebdavUtils
; 
  99  * This Fragment is used to display the details about a file. 
 101  * @author Bartek Przybylski 
 104 public class FileDetailFragment 
extends SherlockFragment 
implements 
 105         OnClickListener
, ConfirmationDialogFragment
.ConfirmationDialogFragmentListener
, OnRemoteOperationListener 
{ 
 107     public static final String EXTRA_FILE 
= "FILE"; 
 108     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
 110     private FileDetailFragment
.ContainerActivity mContainerActivity
; 
 114     private OCFile mFile
; 
 115     private Account mAccount
; 
 116     private ImageView mPreview
; 
 118     private DownloadFinishReceiver mDownloadFinishReceiver
; 
 119     private UploadFinishReceiver mUploadFinishReceiver
; 
 121     private Handler mHandler
; 
 122     private RemoteOperation mLastRemoteOperation
; 
 124     private static final String TAG 
= "FileDetailFragment"; 
 125     public static final String FTAG 
= "FileDetails";  
 126     public static final String FTAG_CONFIRMATION 
= "REMOVE_CONFIRMATION_FRAGMENT"; 
 130      * Creates an empty details fragment. 
 132      * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically.  
 134     public FileDetailFragment() { 
 137         mLayout 
= R
.layout
.file_details_empty
; 
 142      * Creates a details fragment. 
 144      * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before). 
 146      * @param fileToDetail      An {@link OCFile} to show in the fragment 
 147      * @param ocAccount         An ownCloud account; needed to start downloads 
 149     public FileDetailFragment(OCFile fileToDetail
, Account ocAccount
) { 
 150         mFile 
= fileToDetail
; 
 151         mAccount 
= ocAccount
; 
 152         mLayout 
= R
.layout
.file_details_empty
; 
 154         if(fileToDetail 
!= null 
&& ocAccount 
!= null
) { 
 155             mLayout 
= R
.layout
.file_details_fragment
; 
 161     public void onCreate(Bundle savedInstanceState
) { 
 162         super.onCreate(savedInstanceState
); 
 163         mHandler 
= new Handler(); 
 168     public View 
onCreateView(LayoutInflater inflater
, ViewGroup container
, 
 169             Bundle savedInstanceState
) { 
 170         super.onCreateView(inflater
, container
, savedInstanceState
); 
 172         if (savedInstanceState 
!= null
) { 
 173             mFile 
= savedInstanceState
.getParcelable(FileDetailFragment
.EXTRA_FILE
); 
 174             mAccount 
= savedInstanceState
.getParcelable(FileDetailFragment
.EXTRA_ACCOUNT
); 
 178         view 
= inflater
.inflate(mLayout
, container
, false
); 
 181         if (mLayout 
== R
.layout
.file_details_fragment
) { 
 182             mView
.findViewById(R
.id
.fdKeepInSync
).setOnClickListener(this); 
 183             mView
.findViewById(R
.id
.fdRenameBtn
).setOnClickListener(this); 
 184             mView
.findViewById(R
.id
.fdDownloadBtn
).setOnClickListener(this); 
 185             mView
.findViewById(R
.id
.fdOpenBtn
).setOnClickListener(this); 
 186             mView
.findViewById(R
.id
.fdRemoveBtn
).setOnClickListener(this); 
 187             //mView.findViewById(R.id.fdShareBtn).setOnClickListener(this); 
 188             mPreview 
= (ImageView
)mView
.findViewById(R
.id
.fdPreview
); 
 200     public void onAttach(Activity activity
) { 
 201         super.onAttach(activity
); 
 203             mContainerActivity 
= (ContainerActivity
) activity
; 
 204         } catch (ClassCastException e
) { 
 205             throw new ClassCastException(activity
.toString() + " must implement " + FileDetailFragment
.ContainerActivity
.class.getCanonicalName()); 
 211     public void onSaveInstanceState(Bundle outState
) { 
 212         Log
.i(getClass().toString(), "onSaveInstanceState() start"); 
 213         super.onSaveInstanceState(outState
); 
 214         outState
.putParcelable(FileDetailFragment
.EXTRA_FILE
, mFile
); 
 215         outState
.putParcelable(FileDetailFragment
.EXTRA_ACCOUNT
, mAccount
); 
 216         Log
.i(getClass().toString(), "onSaveInstanceState() end"); 
 221     public void onResume() { 
 224         mDownloadFinishReceiver 
= new DownloadFinishReceiver(); 
 225         IntentFilter filter 
= new IntentFilter( 
 226                 FileDownloader
.DOWNLOAD_FINISH_MESSAGE
); 
 227         getActivity().registerReceiver(mDownloadFinishReceiver
, filter
); 
 229         mUploadFinishReceiver 
= new UploadFinishReceiver(); 
 230         filter 
= new IntentFilter(FileUploader
.UPLOAD_FINISH_MESSAGE
); 
 231         getActivity().registerReceiver(mUploadFinishReceiver
, filter
); 
 233         mPreview 
= (ImageView
)mView
.findViewById(R
.id
.fdPreview
); 
 237     public void onPause() { 
 240         getActivity().unregisterReceiver(mDownloadFinishReceiver
); 
 241         mDownloadFinishReceiver 
= null
; 
 243         getActivity().unregisterReceiver(mUploadFinishReceiver
); 
 244         mUploadFinishReceiver 
= null
; 
 246         if (mPreview 
!= null
) { 
 252     public View 
getView() { 
 253         return super.getView() == null ? mView 
: super.getView(); 
 259     public void onClick(View v
) { 
 261             case R
.id
.fdDownloadBtn
: { 
 262                 //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath())) { 
 263                 FileDownloaderBinder downloaderBinder 
= mContainerActivity
.getFileDownloaderBinder(); 
 264                 FileUploaderBinder uploaderBinder 
= mContainerActivity
.getFileUploaderBinder(); 
 265                 if (downloaderBinder 
!= null 
&& downloaderBinder
.isDownloading(mAccount
, mFile
)) { 
 266                     downloaderBinder
.cancel(mAccount
, mFile
); 
 267                     if (mFile
.isDown()) { 
 270                         setButtonsForRemote(); 
 273                 } else if (uploaderBinder 
!= null 
&& uploaderBinder
.isUploading(mAccount
, mFile
)) { 
 274                     uploaderBinder
.cancel(mAccount
, mFile
); 
 275                     if (!mFile
.fileExists()) { 
 276                         // TODO make something better 
 277                         if (getActivity() instanceof FileDisplayActivity
) { 
 279                             FragmentTransaction transaction 
= getActivity().getSupportFragmentManager().beginTransaction(); 
 280                             transaction
.replace(R
.id
.file_details_container
, new FileDetailFragment(null
, null
), FTAG
); // empty FileDetailFragment 
 281                             transaction
.commit(); 
 282                             mContainerActivity
.onFileStateChanged(); 
 284                             getActivity().finish(); 
 287                     } else if (mFile
.isDown()) { 
 290                         setButtonsForRemote(); 
 294                     Intent i 
= new Intent(getActivity(), FileDownloader
.class); 
 295                     i
.putExtra(FileDownloader
.EXTRA_ACCOUNT
, mAccount
); 
 296                     i
.putExtra(FileDownloader
.EXTRA_FILE
, mFile
); 
 297                     /*i.putExtra(FileDownloader.EXTRA_REMOTE_PATH, mFile.getRemotePath()); 
 298                     i.putExtra(FileDownloader.EXTRA_FILE_PATH, mFile.getRemotePath()); 
 299                     i.putExtra(FileDownloader.EXTRA_FILE_SIZE, mFile.getFileLength());*/ 
 302                     setButtonsForTransferring(); 
 304                     getActivity().startService(i
); 
 305                     mContainerActivity
.onFileStateChanged();    // this is not working; it is performed before the fileDownloadService registers it as 'in progress' 
 309             case R
.id
.fdKeepInSync
: { 
 310                 CheckBox cb 
= (CheckBox
) getView().findViewById(R
.id
.fdKeepInSync
); 
 311                 mFile
.setKeepInSync(cb
.isChecked()); 
 312                 FileDataStorageManager fdsm 
= new FileDataStorageManager(mAccount
, getActivity().getApplicationContext().getContentResolver()); 
 313                 fdsm
.saveFile(mFile
); 
 314                 if (mFile
.keepInSync()) { 
 315                     onClick(getView().findViewById(R
.id
.fdDownloadBtn
)); 
 317                     mContainerActivity
.onFileStateChanged();    // put inside 'else' to not call it twice (here, and in the virtual click on fdDownloadBtn) 
 320                 Intent intent 
= new Intent(getActivity().getApplicationContext(), 
 321                                            FileObserverService
.class); 
 322                 intent
.putExtra(FileObserverService
.KEY_FILE_CMD
, 
 324                                    FileObserverService
.CMD_ADD_OBSERVED_FILE
: 
 325                                    FileObserverService
.CMD_DEL_OBSERVED_FILE
)); 
 326                 intent
.putExtra(FileObserverService
.KEY_CMD_ARG
, mFile
.getStoragePath()); 
 327                 Log
.e(TAG
, "starting observer service"); 
 328                 getActivity().startService(intent
); 
 332             case R
.id
.fdRenameBtn
: { 
 333                 EditNameFragment dialog 
= EditNameFragment
.newInstance(mFile
.getFileName()); 
 334                 dialog
.show(getFragmentManager(), "nameeditdialog"); 
 335                 dialog
.setOnDismissListener(this); 
 338             case R
.id
.fdRemoveBtn
: { 
 339                 ConfirmationDialogFragment confDialog 
= ConfirmationDialogFragment
.newInstance( 
 340                         R
.string
.confirmation_remove_alert
, 
 341                         new String
[]{mFile
.getFileName()}, 
 342                         mFile
.isDown() ? R
.string
.confirmation_remove_remote_and_local 
: R
.string
.confirmation_remove_remote
, 
 343                         mFile
.isDown() ? R
.string
.confirmation_remove_local 
: -1, 
 344                         R
.string
.common_cancel
); 
 345                 confDialog
.setOnConfirmationListener(this); 
 346                 confDialog
.show(getFragmentManager(), FTAG_CONFIRMATION
); 
 349             case R
.id
.fdOpenBtn
: { 
 350                 String storagePath 
= mFile
.getStoragePath(); 
 351                 String encodedStoragePath 
= WebdavUtils
.encodePath(storagePath
); 
 353                     Intent i 
= new Intent(Intent
.ACTION_VIEW
); 
 354                     i
.setDataAndType(Uri
.parse("file://"+ encodedStoragePath
), mFile
.getMimetype()); 
 355                     i
.setFlags(Intent
.FLAG_GRANT_READ_URI_PERMISSION 
| Intent
.FLAG_GRANT_WRITE_URI_PERMISSION
); 
 358                 } catch (Throwable t
) { 
 359                     Log
.e(TAG
, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile
.getMimetype()); 
 360                     boolean toastIt 
= true
;  
 361                     String mimeType 
= ""; 
 363                         Intent i 
= new Intent(Intent
.ACTION_VIEW
); 
 364                         mimeType 
= MimeTypeMap
.getSingleton().getMimeTypeFromExtension(storagePath
.substring(storagePath
.lastIndexOf('.') + 1)); 
 365                         if (mimeType 
!= null 
&& !mimeType
.equals(mFile
.getMimetype())) { 
 366                             i
.setDataAndType(Uri
.parse("file://"+ encodedStoragePath
), mimeType
); 
 367                             i
.setFlags(Intent
.FLAG_GRANT_READ_URI_PERMISSION 
| Intent
.FLAG_GRANT_WRITE_URI_PERMISSION
); 
 372                     } catch (IndexOutOfBoundsException e
) { 
 373                         Log
.e(TAG
, "Trying to find out MIME type of a file without extension: " + storagePath
); 
 375                     } catch (ActivityNotFoundException e
) { 
 376                         Log
.e(TAG
, "No activity found to handle: " + storagePath 
+ " with MIME type " + mimeType 
+ " obtained from extension"); 
 378                     } catch (Throwable th
) { 
 379                         Log
.e(TAG
, "Unexpected problem when opening: " + storagePath
, th
); 
 383                             Toast
.makeText(getActivity(), "There is no application to handle file " + mFile
.getFileName(), Toast
.LENGTH_SHORT
).show(); 
 391                 Log
.e(TAG
, "Incorrect view clicked!"); 
 394         /* else if (v.getId() == R.id.fdShareBtn) { 
 395             Thread t = new Thread(new ShareRunnable(mFile.getRemotePath())); 
 402     public void onConfirmation(String callerTag
) { 
 403         if (callerTag
.equals(FTAG_CONFIRMATION
)) { 
 404             FileDataStorageManager fdsm 
= new FileDataStorageManager(mAccount
, getActivity().getContentResolver()); 
 405             if (fdsm
.getFileById(mFile
.getFileId()) != null
) { 
 406                 mLastRemoteOperation 
= new RemoveFileOperation( mFile
,  
 408                                                                 new FileDataStorageManager(mAccount
, getActivity().getContentResolver())); 
 409                 WebdavClient wc 
= OwnCloudClientUtils
.createOwnCloudClient(mAccount
, getSherlockActivity().getApplicationContext()); 
 410                 mLastRemoteOperation
.execute(wc
, this, mHandler
); 
 412                 boolean inDisplayActivity 
= getActivity() instanceof FileDisplayActivity
; 
 413                 getActivity().showDialog((inDisplayActivity
)? FileDisplayActivity
.DIALOG_SHORT_WAIT 
: FileDetailActivity
.DIALOG_SHORT_WAIT
); 
 419     public void onNeutral(String callerTag
) { 
 420         FileDataStorageManager fdsm 
= new FileDataStorageManager(mAccount
, getActivity().getContentResolver()); 
 422         if (mFile
.isDown() && (f 
= new File(mFile
.getStoragePath())).exists()) { 
 424             mFile
.setStoragePath(null
); 
 425             fdsm
.saveFile(mFile
); 
 426             updateFileDetails(mFile
, mAccount
); 
 431     public void onCancel(String callerTag
) { 
 432         Log
.d(TAG
, "REMOVAL CANCELED"); 
 437      * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be replaced. 
 439      * @return  True when the fragment was created with the empty layout. 
 441     public boolean isEmpty() { 
 442         return mLayout 
== R
.layout
.file_details_empty
; 
 447      * Can be used to get the file that is currently being displayed. 
 448      * @return The file on the screen. 
 450     public OCFile 
getDisplayedFile(){ 
 455      * Use this method to signal this Activity that it shall update its view. 
 457      * @param file : An {@link OCFile} 
 459     public void updateFileDetails(OCFile file
, Account ocAccount
) { 
 461         mAccount 
= ocAccount
; 
 467      * Updates the view with all relevant details about that file. 
 469     public void updateFileDetails() { 
 471         if (mFile 
!= null 
&& mAccount 
!= null 
&& mLayout 
== R
.layout
.file_details_fragment
) { 
 474             setFilename(mFile
.getFileName()); 
 475             setFiletype(DisplayUtils
.convertMIMEtoPrettyPrint(mFile
 
 477             setFilesize(mFile
.getFileLength()); 
 478             if(ocVersionSupportsTimeCreated()){ 
 479                 setTimeCreated(mFile
.getCreationTimestamp()); 
 482             setTimeModified(mFile
.getModificationTimestamp()); 
 484             CheckBox cb 
= (CheckBox
)getView().findViewById(R
.id
.fdKeepInSync
); 
 485             cb
.setChecked(mFile
.keepInSync()); 
 487             // configure UI for depending upon local state of the file 
 488             //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath()) || FileUploader.isUploading(mAccount, mFile.getRemotePath())) { 
 489             FileDownloaderBinder downloaderBinder 
= mContainerActivity
.getFileDownloaderBinder(); 
 490             FileUploaderBinder uploaderBinder 
= mContainerActivity
.getFileUploaderBinder(); 
 491             if ((downloaderBinder 
!= null 
&& downloaderBinder
.isDownloading(mAccount
, mFile
)) || (uploaderBinder 
!= null 
&& uploaderBinder
.isUploading(mAccount
, mFile
))) { 
 492                 setButtonsForTransferring(); 
 494             } else if (mFile
.isDown()) { 
 496                 if (mFile
.getMimetype().startsWith("image/")) { 
 497                     BitmapLoader bl 
= new BitmapLoader(); 
 498                     bl
.execute(new String
[]{mFile
.getStoragePath()}); 
 504                 setButtonsForRemote(); 
 511      * Updates the filename in view 
 512      * @param filename to set 
 514     private void setFilename(String filename
) { 
 515         TextView tv 
= (TextView
) getView().findViewById(R
.id
.fdFilename
); 
 517             tv
.setText(filename
); 
 521      * Updates the MIME type in view 
 522      * @param mimetype to set 
 524     private void setFiletype(String mimetype
) { 
 525         TextView tv 
= (TextView
) getView().findViewById(R
.id
.fdType
); 
 527             tv
.setText(mimetype
); 
 531      * Updates the file size in view 
 532      * @param filesize in bytes to set 
 534     private void setFilesize(long filesize
) { 
 535         TextView tv 
= (TextView
) getView().findViewById(R
.id
.fdSize
); 
 537             tv
.setText(DisplayUtils
.bytesToHumanReadable(filesize
)); 
 541      * Updates the time that the file was created in view 
 542      * @param milliseconds Unix time to set 
 544     private void setTimeCreated(long milliseconds
){ 
 545         TextView tv 
= (TextView
) getView().findViewById(R
.id
.fdCreated
); 
 546         TextView tvLabel 
= (TextView
) getView().findViewById(R
.id
.fdCreatedLabel
); 
 548             tv
.setText(DisplayUtils
.unixTimeToHumanReadable(milliseconds
)); 
 549             tv
.setVisibility(View
.VISIBLE
); 
 550             tvLabel
.setVisibility(View
.VISIBLE
); 
 555      * Updates the time that the file was last modified 
 556      * @param milliseconds Unix time to set 
 558     private void setTimeModified(long milliseconds
){ 
 559         TextView tv 
= (TextView
) getView().findViewById(R
.id
.fdModified
); 
 561             tv
.setText(DisplayUtils
.unixTimeToHumanReadable(milliseconds
)); 
 566      * Enables or disables buttons for a file being downloaded 
 568     private void setButtonsForTransferring() { 
 570             Button downloadButton 
= (Button
) getView().findViewById(R
.id
.fdDownloadBtn
); 
 571             downloadButton
.setText(R
.string
.common_cancel
); 
 572             //downloadButton.setEnabled(false); 
 574             // let's protect the user from himself ;) 
 575             ((Button
) getView().findViewById(R
.id
.fdOpenBtn
)).setEnabled(false
); 
 576             ((Button
) getView().findViewById(R
.id
.fdRenameBtn
)).setEnabled(false
); 
 577             ((Button
) getView().findViewById(R
.id
.fdRemoveBtn
)).setEnabled(false
); 
 578             getView().findViewById(R
.id
.fdKeepInSync
).setEnabled(false
); 
 583      * Enables or disables buttons for a file locally available  
 585     private void setButtonsForDown() { 
 587             Button downloadButton 
= (Button
) getView().findViewById(R
.id
.fdDownloadBtn
); 
 588             downloadButton
.setText(R
.string
.filedetails_redownload
); 
 589             //downloadButton.setEnabled(true); 
 591             ((Button
) getView().findViewById(R
.id
.fdOpenBtn
)).setEnabled(true
); 
 592             ((Button
) getView().findViewById(R
.id
.fdRenameBtn
)).setEnabled(true
); 
 593             ((Button
) getView().findViewById(R
.id
.fdRemoveBtn
)).setEnabled(true
); 
 594             getView().findViewById(R
.id
.fdKeepInSync
).setEnabled(true
); 
 599      * Enables or disables buttons for a file not locally available  
 601     private void setButtonsForRemote() { 
 603             Button downloadButton 
= (Button
) getView().findViewById(R
.id
.fdDownloadBtn
); 
 604             downloadButton
.setText(R
.string
.filedetails_download
); 
 606             ((Button
) getView().findViewById(R
.id
.fdOpenBtn
)).setEnabled(false
); 
 607             ((Button
) getView().findViewById(R
.id
.fdRenameBtn
)).setEnabled(true
); 
 608             ((Button
) getView().findViewById(R
.id
.fdRemoveBtn
)).setEnabled(true
); 
 609             getView().findViewById(R
.id
.fdKeepInSync
).setEnabled(true
); 
 615      * In ownCloud 3.X.X and 4.X.X there is a bug that SabreDAV does not return 
 616      * the time that the file was created. There is a chance that this will 
 617      * be fixed in future versions. Use this method to check if this version of 
 618      * ownCloud has this fix. 
 619      * @return True, if ownCloud the ownCloud version is supporting creation time 
 621     private boolean ocVersionSupportsTimeCreated(){ 
 622         /*if(mAccount != null){ 
 623             AccountManager accManager = (AccountManager) getActivity().getSystemService(Context.ACCOUNT_SERVICE); 
 624             OwnCloudVersion ocVersion = new OwnCloudVersion(accManager 
 625                     .getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION)); 
 626             if(ocVersion.compareTo(new OwnCloudVersion(0x030000)) < 0) { 
 635      * Interface to implement by any Activity that includes some instance of FileDetailFragment 
 637      * @author David A. Velasco 
 639     public interface ContainerActivity 
extends TransferServiceGetter 
{ 
 642          * Callback method invoked when the detail fragment wants to notice its container  
 643          * activity about a relevant state the file shown by the fragment. 
 645          * Added to notify to FileDisplayActivity about the need of refresh the files list.  
 647          * Currently called when: 
 648          *  - a download is started; 
 649          *  - a rename is completed; 
 650          *  - a deletion is completed; 
 651          *  - the 'inSync' flag is changed; 
 653         public void onFileStateChanged(); 
 659      * Once the file download has finished -> update view 
 660      * @author Bartek Przybylski 
 662     private class DownloadFinishReceiver 
extends BroadcastReceiver 
{ 
 664         public void onReceive(Context context
, Intent intent
) { 
 665             String accountName 
= intent
.getStringExtra(FileDownloader
.ACCOUNT_NAME
); 
 667             if (!isEmpty() && accountName
.equals(mAccount
.name
)) { 
 668                 boolean downloadWasFine 
= intent
.getBooleanExtra(FileDownloader
.EXTRA_DOWNLOAD_RESULT
, false
); 
 669                 String downloadedRemotePath 
= intent
.getStringExtra(FileDownloader
.EXTRA_REMOTE_PATH
); 
 670                 if (mFile
.getRemotePath().equals(downloadedRemotePath
)) { 
 671                     if (downloadWasFine
) { 
 672                         mFile
.setStoragePath(intent
.getStringExtra(FileDownloader
.EXTRA_FILE_PATH
));    // updates the local object without accessing the database again 
 674                     updateFileDetails();    // it updates the buttons; must be called although !downloadWasFine 
 682      * Once the file upload has finished -> update view 
 684      * Being notified about the finish of an upload is necessary for the next sequence: 
 685      *   1. Upload a big file. 
 686      *   2. Force a synchronization; if it finished before the upload, the file in transfer will be included in the local database and in the file list 
 687      *      of its containing folder; the the server includes it in the PROPFIND requests although it's not fully upload.  
 688      *   3. Click the file in the list to see its details. 
 689      *   4. Wait for the upload finishes; at this moment, the details view must be refreshed to enable the action buttons. 
 691     private class UploadFinishReceiver 
extends BroadcastReceiver 
{ 
 693         public void onReceive(Context context
, Intent intent
) { 
 694             String accountName 
= intent
.getStringExtra(FileUploader
.ACCOUNT_NAME
); 
 696             if (!isEmpty() && accountName
.equals(mAccount
.name
)) { 
 697                 boolean uploadWasFine 
= intent
.getBooleanExtra(FileUploader
.EXTRA_UPLOAD_RESULT
, false
); 
 698                 String uploadRemotePath 
= intent
.getStringExtra(FileUploader
.EXTRA_REMOTE_PATH
); 
 699                 if (mFile
.getRemotePath().equals(uploadRemotePath
)) { 
 701                         FileDataStorageManager fdsm 
= new FileDataStorageManager(mAccount
, getActivity().getApplicationContext().getContentResolver()); 
 702                         mFile 
= fdsm
.getFileByPath(mFile
.getRemotePath()); 
 704                     updateFileDetails();    // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server 
 711     // this is a temporary class for sharing purposes, it need to be replaced in transfer service 
 712     @SuppressWarnings("unused") 
 713     private class ShareRunnable 
implements Runnable 
{ 
 714         private String mPath
; 
 716         public ShareRunnable(String path
) { 
 721             AccountManager am 
= AccountManager
.get(getActivity()); 
 722             Account account 
= AccountUtils
.getCurrentOwnCloudAccount(getActivity()); 
 723             OwnCloudVersion ocv 
= new OwnCloudVersion(am
.getUserData(account
, AccountAuthenticator
.KEY_OC_VERSION
)); 
 724             String url 
= am
.getUserData(account
, AccountAuthenticator
.KEY_OC_BASE_URL
) + AccountUtils
.getWebdavPath(ocv
); 
 726             Log
.d("share", "sharing for version " + ocv
.toString()); 
 728             if (ocv
.compareTo(new OwnCloudVersion(0x040000)) >= 0) { 
 729                 String APPS_PATH 
= "/apps/files_sharing/"; 
 730                 String SHARE_PATH 
= "ajax/share.php"; 
 732                 String SHARED_PATH 
= "/apps/files_sharing/get.php?token="; 
 734                 final String WEBDAV_SCRIPT 
= "webdav.php"; 
 735                 final String WEBDAV_FILES_LOCATION 
= "/files/"; 
 737                 WebdavClient wc 
= OwnCloudClientUtils
.createOwnCloudClient(account
, getActivity().getApplicationContext()); 
 738                 HttpConnectionManagerParams params 
= new HttpConnectionManagerParams(); 
 739                 params
.setMaxConnectionsPerHost(wc
.getHostConfiguration(), 5); 
 741                 //wc.getParams().setParameter("http.protocol.single-cookie-header", true); 
 742                 //wc.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); 
 744                 PostMethod post 
= new PostMethod(am
.getUserData(account
, AccountAuthenticator
.KEY_OC_BASE_URL
) + APPS_PATH 
+ SHARE_PATH
); 
 746                 post
.addRequestHeader("Content-type","application/x-www-form-urlencoded; charset=UTF-8" ); 
 747                 post
.addRequestHeader("Referer", am
.getUserData(account
, AccountAuthenticator
.KEY_OC_BASE_URL
)); 
 748                 List
<NameValuePair
> formparams 
= new ArrayList
<NameValuePair
>(); 
 749                 Log
.d("share", mPath
+""); 
 750                 formparams
.add(new BasicNameValuePair("sources",mPath
)); 
 751                 formparams
.add(new BasicNameValuePair("uid_shared_with", "public")); 
 752                 formparams
.add(new BasicNameValuePair("permissions", "0")); 
 753                 post
.setRequestEntity(new StringRequestEntity(URLEncodedUtils
.format(formparams
, HTTP
.UTF_8
))); 
 757                     PropFindMethod find 
= new PropFindMethod(url
+"/"); 
 758                     find
.addRequestHeader("Referer", am
.getUserData(account
, AccountAuthenticator
.KEY_OC_BASE_URL
)); 
 759                     Log
.d("sharer", ""+ url
+"/"); 
 761                     for (org
.apache
.commons
.httpclient
.Header a 
: find
.getRequestHeaders()) { 
 762                         Log
.d("sharer-h", a
.getName() + ":"+a
.getValue()); 
 765                     int status2 
= wc
.executeMethod(find
); 
 767                     Log
.d("sharer", "propstatus "+status2
); 
 769                     GetMethod get 
= new GetMethod(am
.getUserData(account
, AccountAuthenticator
.KEY_OC_BASE_URL
) + "/"); 
 770                     get
.addRequestHeader("Referer", am
.getUserData(account
, AccountAuthenticator
.KEY_OC_BASE_URL
)); 
 772                     status2 
= wc
.executeMethod(get
); 
 774                     Log
.d("sharer", "getstatus "+status2
); 
 775                     Log
.d("sharer", "" + get
.getResponseBodyAsString()); 
 777                     for (org
.apache
.commons
.httpclient
.Header a 
: get
.getResponseHeaders()) { 
 778                         Log
.d("sharer", a
.getName() + ":"+a
.getValue()); 
 781                     status 
= wc
.executeMethod(post
); 
 782                     for (org
.apache
.commons
.httpclient
.Header a 
: post
.getRequestHeaders()) { 
 783                         Log
.d("sharer-h", a
.getName() + ":"+a
.getValue()); 
 785                     for (org
.apache
.commons
.httpclient
.Header a 
: post
.getResponseHeaders()) { 
 786                         Log
.d("sharer", a
.getName() + ":"+a
.getValue()); 
 788                     String resp 
= post
.getResponseBodyAsString(); 
 789                     Log
.d("share", ""+post
.getURI().toString()); 
 790                     Log
.d("share", "returned status " + status
); 
 791                     Log
.d("share", " " +resp
); 
 793                     if(status 
!= HttpStatus
.SC_OK 
||resp 
== null 
|| resp
.equals("") || resp
.startsWith("false")) { 
 797                     JSONObject jsonObject 
= new JSONObject (resp
); 
 798                     String jsonStatus 
= jsonObject
.getString("status"); 
 799                     if(!jsonStatus
.equals("success")) throw new Exception("Error while sharing file status != success"); 
 801                     String token 
= jsonObject
.getString("data"); 
 802                     String uri 
= am
.getUserData(account
, AccountAuthenticator
.KEY_OC_BASE_URL
) + SHARED_PATH 
+ token
;  
 803                     Log
.d("Actions:shareFile ok", "url: " + uri
);    
 805                 } catch (Exception e
) { 
 809             } else if (ocv
.compareTo(new OwnCloudVersion(0x030000)) >= 0) { 
 815     public void onDismiss(EditNameFragment dialog
) { 
 816         if (dialog 
instanceof EditNameFragment
) { 
 817             if (((EditNameFragment
)dialog
).getResult()) { 
 818                 String newFilename 
= ((EditNameFragment
)dialog
).getNewFilename(); 
 819                 Log
.d(TAG
, "name edit dialog dismissed with new name " + newFilename
); 
 820                 if (!newFilename
.equals(mFile
.getFileName())) { 
 821                     FileDataStorageManager fdsm 
= new FileDataStorageManager(mAccount
, getActivity().getContentResolver()); 
 822                     if (fdsm
.getFileById(mFile
.getFileId()) != null
) { 
 823                         OCFile newFile 
= new OCFile(fdsm
.getFileById(mFile
.getParentId()).getRemotePath() + newFilename
); 
 824                         newFile
.setCreationTimestamp(mFile
.getCreationTimestamp()); 
 825                         newFile
.setFileId(mFile
.getFileId()); 
 826                         newFile
.setFileLength(mFile
.getFileLength()); 
 827                         newFile
.setKeepInSync(mFile
.keepInSync()); 
 828                         newFile
.setLastSyncDate(mFile
.getLastSyncDate()); 
 829                         newFile
.setMimetype(mFile
.getMimetype()); 
 830                         newFile
.setModificationTimestamp(mFile
.getModificationTimestamp()); 
 831                         newFile
.setParentId(mFile
.getParentId()); 
 832                         boolean localRenameFails 
= false
; 
 833                         if (mFile
.isDown()) { 
 834                             File f 
= new File(mFile
.getStoragePath()); 
 835                             Log
.e(TAG
, f
.getAbsolutePath()); 
 836                             localRenameFails 
= !(f
.renameTo(new File(f
.getParent() + File
.separator 
+ newFilename
))); 
 837                             Log
.e(TAG
, f
.getParent() + File
.separator 
+ newFilename
); 
 838                             newFile
.setStoragePath(f
.getParent() + File
.separator 
+ newFilename
); 
 841                         if (localRenameFails
) { 
 842                             Toast msg 
= Toast
.makeText(getActivity(), R
.string
.rename_local_fail_msg
, Toast
.LENGTH_LONG
);  
 846                             new Thread(new RenameRunnable(mFile
, newFile
, mAccount
, new Handler())).start(); 
 847                             boolean inDisplayActivity 
= getActivity() instanceof FileDisplayActivity
; 
 848                             getActivity().showDialog((inDisplayActivity
)? FileDisplayActivity
.DIALOG_SHORT_WAIT 
: FileDetailActivity
.DIALOG_SHORT_WAIT
); 
 855             Log
.e(TAG
, "Unknown dialog instance passed to onDismissDalog: " + dialog
.getClass().getCanonicalName()); 
 860     private class RenameRunnable 
implements Runnable 
{ 
 866         public RenameRunnable(OCFile oldFile
, OCFile newFile
, Account account
, Handler handler
) { 
 874             WebdavClient wc 
= OwnCloudClientUtils
.createOwnCloudClient(mAccount
, getSherlockActivity().getApplicationContext()); 
 875             AccountManager am 
= AccountManager
.get(getSherlockActivity()); 
 876             String baseUrl 
= am
.getUserData(mAccount
, AccountAuthenticator
.KEY_OC_BASE_URL
); 
 877             OwnCloudVersion ocv 
= new OwnCloudVersion(am
.getUserData(mAccount
, AccountAuthenticator
.KEY_OC_VERSION
)); 
 878             String webdav_path 
= AccountUtils
.getWebdavPath(ocv
); 
 879             Log
.d("ASD", ""+baseUrl 
+ webdav_path 
+ WebdavUtils
.encodePath(mOld
.getRemotePath())); 
 881             Log
.e("ASD", Uri
.parse(baseUrl
).getPath() == null ? 
"" : Uri
.parse(baseUrl
).getPath() + webdav_path 
+ WebdavUtils
.encodePath(mNew
.getRemotePath())); 
 882             LocalMoveMethod move 
= new LocalMoveMethod(baseUrl 
+ webdav_path 
+ WebdavUtils
.encodePath(mOld
.getRemotePath()), 
 883                                              Uri
.parse(baseUrl
).getPath() == null ? 
"" : Uri
.parse(baseUrl
).getPath() + webdav_path 
+ WebdavUtils
.encodePath(mNew
.getRemotePath())); 
 885             boolean success 
= false
; 
 887                 int status 
= wc
.executeMethod(move
); 
 888                 success 
= move
.succeeded(); 
 889                 move
.getResponseBodyAsString(); // exhaust response, although not interesting 
 890                 Log
.d(TAG
, "Move returned status: " + status
); 
 892             } catch (HttpException e
) { 
 893                 Log
.e(TAG
, "HTTP Exception renaming file " + mOld
.getRemotePath() + " to " + mNew
.getRemotePath(), e
); 
 895             } catch (IOException e
) { 
 896                 Log
.e(TAG
, "I/O Exception renaming file " + mOld
.getRemotePath() + " to " + mNew
.getRemotePath(), e
); 
 898             } catch (Exception e
) { 
 899                 Log
.e(TAG
, "Unexpected exception renaming file " + mOld
.getRemotePath() + " to " + mNew
.getRemotePath(), e
); 
 902                move
.releaseConnection(); 
 906                 FileDataStorageManager fdsm 
= new FileDataStorageManager(mAccount
, getActivity().getContentResolver()); 
 907                 fdsm
.removeFile(mOld
, false
); 
 910                 mHandler
.post(new Runnable() { 
 913                         boolean inDisplayActivity 
= getActivity() instanceof FileDisplayActivity
; 
 914                         getActivity().dismissDialog((inDisplayActivity
)? FileDisplayActivity
.DIALOG_SHORT_WAIT 
: FileDetailActivity
.DIALOG_SHORT_WAIT
); 
 915                         updateFileDetails(mFile
, mAccount
); 
 916                         mContainerActivity
.onFileStateChanged(); 
 921                 mHandler
.post(new Runnable() { 
 924                         // undo the local rename 
 926                             File f 
= new File(mNew
.getStoragePath()); 
 927                             if (!f
.renameTo(new File(mOld
.getStoragePath()))) { 
 928                                 // the local rename undoing failed; last chance: save the new local storage path in the old file 
 929                                 mFile
.setStoragePath(mNew
.getStoragePath()); 
 930                                 FileDataStorageManager fdsm 
= new FileDataStorageManager(mAccount
, getActivity().getContentResolver()); 
 931                                 fdsm
.saveFile(mFile
); 
 934                         boolean inDisplayActivity 
= getActivity() instanceof FileDisplayActivity
; 
 935                         getActivity().dismissDialog((inDisplayActivity
)? FileDisplayActivity
.DIALOG_SHORT_WAIT 
: FileDetailActivity
.DIALOG_SHORT_WAIT
); 
 937                             Toast msg 
= Toast
.makeText(getActivity(), R
.string
.rename_server_fail_msg
, Toast
.LENGTH_LONG
);  
 940                         } catch (NotFoundException e
) { 
 947         private class LocalMoveMethod 
extends DavMethodBase 
{ 
 949             public LocalMoveMethod(String uri
, String dest
) { 
 951                 addRequestHeader(new org
.apache
.commons
.httpclient
.Header("Destination", dest
)); 
 955             public String 
getName() { 
 960             protected boolean isSuccess(int status
) { 
 961                 return status 
== 201 || status 
== 204; 
 967     private static class EditNameFragment 
extends SherlockDialogFragment 
implements OnClickListener 
{ 
 969         private String mNewFilename
; 
 970         private boolean mResult
; 
 971         private FileDetailFragment mListener
; 
 973         static public EditNameFragment 
newInstance(String filename
) { 
 974             EditNameFragment f 
= new EditNameFragment(); 
 975             Bundle args 
= new Bundle(); 
 976             args
.putString("filename", filename
); 
 977             f
.setArguments(args
); 
 982         public View 
onCreateView(LayoutInflater inflater
, ViewGroup container
, Bundle savedInstanceState
) { 
 983             View v 
= inflater
.inflate(R
.layout
.edit_box_dialog
, container
, false
); 
 985             String currentName 
= getArguments().getString("filename"); 
 986             if (currentName 
== null
) 
 989             ((Button
)v
.findViewById(R
.id
.cancel
)).setOnClickListener(this); 
 990             ((Button
)v
.findViewById(R
.id
.ok
)).setOnClickListener(this); 
 991             ((TextView
)v
.findViewById(R
.id
.user_input
)).setText(currentName
); 
 992             ((TextView
)v
.findViewById(R
.id
.user_input
)).requestFocus(); 
 993             getDialog().getWindow().setSoftInputMode(LayoutParams
.SOFT_INPUT_STATE_VISIBLE
); 
1000         public void onClick(View view
) { 
1001             switch (view
.getId()) { 
1003                     mNewFilename 
= ((TextView
)getView().findViewById(R
.id
.user_input
)).getText().toString(); 
1006                 case R
.id
.cancel
: { // fallthought 
1008                     mListener
.onDismiss(this); 
1013         void setOnDismissListener(FileDetailFragment listener
) { 
1014             mListener 
= listener
; 
1017         public String 
getNewFilename() { 
1018             return mNewFilename
; 
1021         // true if user click ok 
1022         public boolean getResult() { 
1028     class BitmapLoader 
extends AsyncTask
<String
, Void
, Bitmap
> { 
1029         @SuppressLint({ "NewApi", "NewApi", "NewApi" }) // to avoid Lint errors since Android SDK r20 
1031         protected Bitmap 
doInBackground(String
... params
) { 
1032             Bitmap result 
= null
; 
1033             if (params
.length 
!= 1) return result
; 
1034             String storagePath 
= params
[0]; 
1037                 BitmapFactory
.Options options 
= new Options(); 
1038                 options
.inScaled 
= true
; 
1039                 options
.inPurgeable 
= true
; 
1040                 options
.inJustDecodeBounds 
= true
; 
1041                 if (android
.os
.Build
.VERSION
.SDK_INT 
>= android
.os
.Build
.VERSION_CODES
.GINGERBREAD_MR1
) { 
1042                     options
.inPreferQualityOverSpeed 
= false
; 
1044                 if (android
.os
.Build
.VERSION
.SDK_INT 
>= android
.os
.Build
.VERSION_CODES
.HONEYCOMB
) { 
1045                     options
.inMutable 
= false
; 
1048                 result 
= BitmapFactory
.decodeFile(storagePath
, options
); 
1049                 options
.inJustDecodeBounds 
= false
; 
1051                 int width 
= options
.outWidth
; 
1052                 int height 
= options
.outHeight
; 
1054                 if (width 
>= 2048 || height 
>= 2048) { 
1055                     scale 
= (int) Math
.ceil((Math
.ceil(Math
.max(height
, width
) / 2048.))); 
1056                     options
.inSampleSize 
= scale
; 
1058                 Display display 
= getActivity().getWindowManager().getDefaultDisplay(); 
1059                 Point size 
= new Point(); 
1061                 if (android
.os
.Build
.VERSION
.SDK_INT 
>= android
.os
.Build
.VERSION_CODES
.HONEYCOMB_MR2
) { 
1062                     display
.getSize(size
); 
1063                     screenwidth 
= size
.x
; 
1065                     screenwidth 
= display
.getWidth(); 
1068                 Log
.e("ASD", "W " + width 
+ " SW " + screenwidth
); 
1070                 if (width 
> screenwidth
) { 
1071                     scale 
= (int) Math
.ceil((float)width 
/ screenwidth
); 
1072                     options
.inSampleSize 
= scale
; 
1075                 result 
= BitmapFactory
.decodeFile(storagePath
, options
); 
1077                 Log
.e("ASD", "W " + options
.outWidth 
+ " SW " + options
.outHeight
); 
1079             } catch (OutOfMemoryError e
) { 
1081                 Log
.e(TAG
, "Out of memory occured for file with size " + storagePath
); 
1083             } catch (NoSuchFieldError e
) { 
1085                 Log
.e(TAG
, "Error from access to unexisting field despite protection " + storagePath
); 
1087             } catch (Throwable t
) { 
1089                 Log
.e(TAG
, "Unexpected error while creating image preview " + storagePath
, t
); 
1094         protected void onPostExecute(Bitmap result
) { 
1095             if (result 
!= null 
&& mPreview 
!= null
) { 
1096                 mPreview
.setImageBitmap(result
); 
1106     public void onRemoteOperationFinish(RemoteOperation operation
, RemoteOperationResult result
) { 
1107         if (operation
.equals(mLastRemoteOperation
)) { 
1108             if (operation 
instanceof RemoveFileOperation
) { 
1109                 boolean inDisplayActivity 
= getActivity() instanceof FileDisplayActivity
; 
1110                 getActivity().dismissDialog((inDisplayActivity
)? FileDisplayActivity
.DIALOG_SHORT_WAIT 
: FileDetailActivity
.DIALOG_SHORT_WAIT
); 
1111                 if (result
.isSuccess()) { 
1112                     Toast msg 
= Toast
.makeText(getActivity().getApplicationContext(), R
.string
.remove_success_msg
, Toast
.LENGTH_LONG
); 
1114                     if (inDisplayActivity
) { 
1116                         FragmentTransaction transaction 
= getActivity().getSupportFragmentManager().beginTransaction(); 
1117                         transaction
.replace(R
.id
.file_details_container
, new FileDetailFragment(null
, null
)); // empty FileDetailFragment 
1118                         transaction
.commit(); 
1119                         mContainerActivity
.onFileStateChanged(); 
1121                         getActivity().finish(); 
1125                     Toast msg 
= Toast
.makeText(getActivity(), R
.string
.remove_fail_msg
, Toast
.LENGTH_LONG
);  
1127                     if (result
.isSslRecoverableException()) {