1 /* ownCloud Android client application 
   2  *   Copyright (C) 2011  Bartek Przybylski 
   3  *   Copyright (C) 2012-2013 ownCloud Inc. 
   5  *   This program is free software: you can redistribute it and/or modify 
   6  *   it under the terms of the GNU General Public License version 2, 
   7  *   as published by the Free Software Foundation. 
   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
.util
.ArrayList
; 
  22 import java
.util
.List
; 
  24 import com
.owncloud
.android
.Log_OC
; 
  25 import com
.owncloud
.android
.R
; 
  26 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  27 import com
.owncloud
.android
.datamodel
.DataStorageManager
; 
  28 import com
.owncloud
.android
.datamodel
.OCFile
; 
  29 import com
.owncloud
.android
.files
.FileHandler
; 
  30 import com
.owncloud
.android
.files
.services
.FileDownloader
.FileDownloaderBinder
; 
  31 import com
.owncloud
.android
.files
.services
.FileUploader
.FileUploaderBinder
; 
  32 import com
.owncloud
.android
.operations
.OnRemoteOperationListener
; 
  33 import com
.owncloud
.android
.operations
.RemoteOperation
; 
  34 import com
.owncloud
.android
.operations
.RemoveFileOperation
; 
  35 import com
.owncloud
.android
.operations
.RenameFileOperation
; 
  36 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
; 
  37 import com
.owncloud
.android
.ui
.activity
.FileDisplayActivity
; 
  38 import com
.owncloud
.android
.ui
.activity
.TransferServiceGetter
; 
  39 import com
.owncloud
.android
.ui
.adapter
.FileListListAdapter
; 
  40 import com
.owncloud
.android
.ui
.dialog
.EditNameDialog
; 
  41 import com
.owncloud
.android
.ui
.dialog
.EditNameDialog
.EditNameDialogListener
; 
  42 import com
.owncloud
.android
.ui
.fragment
.ConfirmationDialogFragment
.ConfirmationDialogFragmentListener
; 
  43 import com
.owncloud
.android
.ui
.preview
.PreviewImageFragment
; 
  44 import com
.owncloud
.android
.ui
.preview
.PreviewMediaFragment
; 
  46 import android
.accounts
.Account
; 
  47 import android
.app
.Activity
; 
  48 import android
.os
.Bundle
; 
  49 import android
.os
.Handler
; 
  50 import android
.view
.ContextMenu
; 
  51 import android
.view
.MenuInflater
; 
  52 import android
.view
.MenuItem
; 
  53 import android
.view
.View
; 
  54 import android
.widget
.AdapterView
; 
  55 import android
.widget
.AdapterView
.AdapterContextMenuInfo
; 
  58  * A Fragment that lists all files and folders in a given path. 
  60  * @author Bartek Przybylski 
  63 public class OCFileListFragment 
extends ExtendedListFragment 
implements EditNameDialogListener
, ConfirmationDialogFragmentListener 
{ 
  65     private static final String TAG 
= OCFileListFragment
.class.getSimpleName(); 
  67     private static final String MY_PACKAGE 
= OCFileListFragment
.class.getPackage() != null ? OCFileListFragment
.class.getPackage().getName() : "com.owncloud.android.ui.fragment"; 
  68     private static final String EXTRA_FILE 
= MY_PACKAGE 
+ ".extra.FILE"; 
  70     private OCFileListFragment
.ContainerActivity mContainerActivity
; 
  72     private OCFile mFile 
= null
; 
  73     private FileListListAdapter mAdapter
; 
  75     private Handler mHandler
; 
  76     private OCFile mTargetFile
; 
  82     public void onAttach(Activity activity
) { 
  83         super.onAttach(activity
); 
  84         Log_OC
.e(TAG
, "onAttach"); 
  86             mContainerActivity 
= (ContainerActivity
) activity
; 
  87         } catch (ClassCastException e
) { 
  88             throw new ClassCastException(activity
.toString() + " must implement " + OCFileListFragment
.ContainerActivity
.class.getSimpleName()); 
  97     public void onActivityCreated(Bundle savedInstanceState
) { 
  98         super.onActivityCreated(savedInstanceState
); 
  99         Log_OC
.e(TAG
, "onActivityCreated() start"); 
 100         mAdapter 
= new FileListListAdapter(getActivity(), mContainerActivity
); 
 101         if (savedInstanceState 
!= null
) { 
 102             mFile 
= savedInstanceState
.getParcelable(EXTRA_FILE
); 
 104         setListAdapter(mAdapter
); 
 106         registerForContextMenu(getListView()); 
 107         getListView().setOnCreateContextMenuListener(this);         
 109         mHandler 
= new Handler(); 
 114      * Saves the current listed folder. 
 117     public void onSaveInstanceState (Bundle outState
) { 
 118         super.onSaveInstanceState(outState
); 
 119         outState
.putParcelable(EXTRA_FILE
, mFile
); 
 124      * Call this, when the user presses the up button 
 126     public void onBrowseUp() { 
 127         OCFile parentDir 
= null
; 
 130             DataStorageManager storageManager 
= mContainerActivity
.getStorageManager(); 
 131             if (mFile
.getParentId() == 0) { 
 132                 parentDir 
= storageManager
.getFileById(1); 
 135                 parentDir 
= storageManager
.getFileById(mFile
.getParentId()); 
 142             listDirectory(mFile
); 
 144             mContainerActivity
.startSyncFolderOperation(mFile
); 
 150     public void onItemClick(AdapterView
<?
> l
, View v
, int position
, long id
) { 
 151         OCFile file 
= (OCFile
) mAdapter
.getItem(position
); 
 153             if (file
.isDirectory()) {  
 154                 // update state and view of this fragment 
 156                 // then, notify parent activity to let it update its state and view, and other fragments 
 157                 mContainerActivity
.onBrowsedDownTo(file
); 
 159             } else { /// Click on a file 
 160                 if (PreviewImageFragment
.canBePreviewed(file
)) { 
 161                     // preview image - it handles the download, if needed 
 162                     mContainerActivity
.startImagePreview(file
); 
 164                 } else if (file
.isDown()) { 
 165                     if (PreviewMediaFragment
.canBePreviewed(file
)) { 
 167                         mContainerActivity
.startMediaPreview(file
, 0, true
); 
 170                         mContainerActivity
.openFile(file
); 
 174                     // automatic download, preview on finish 
 175                     mContainerActivity
.startDownloadForPreview(file
); 
 181             Log_OC
.d(TAG
, "Null object in ListAdapter!!"); 
 190     public void onCreateContextMenu (ContextMenu menu
, View v
, ContextMenu
.ContextMenuInfo menuInfo
) { 
 191         super.onCreateContextMenu(menu
, v
, menuInfo
); 
 192         MenuInflater inflater 
= getActivity().getMenuInflater(); 
 193         inflater
.inflate(R
.menu
.file_actions_menu
, menu
); 
 194         AdapterContextMenuInfo info 
= (AdapterContextMenuInfo
) menuInfo
; 
 195         OCFile targetFile 
= (OCFile
) mAdapter
.getItem(info
.position
); 
 196         List
<Integer
> toHide 
= new ArrayList
<Integer
>();     
 197         List
<Integer
> toDisable 
= new ArrayList
<Integer
>();   
 199         MenuItem item 
= null
; 
 200         if (targetFile
.isDirectory()) { 
 201             // contextual menu for folders 
 202             toHide
.add(R
.id
.action_open_file_with
); 
 203             toHide
.add(R
.id
.action_download_file
); 
 204             toHide
.add(R
.id
.action_cancel_download
); 
 205             toHide
.add(R
.id
.action_cancel_upload
); 
 206             toHide
.add(R
.id
.action_sync_file
); 
 207             toHide
.add(R
.id
.action_see_details
); 
 208             if (    mContainerActivity
.getFileDownloaderBinder().isDownloading(AccountUtils
.getCurrentOwnCloudAccount(getActivity()), targetFile
) || 
 209                     mContainerActivity
.getFileUploaderBinder().isUploading(AccountUtils
.getCurrentOwnCloudAccount(getActivity()), targetFile
)           ) { 
 210                 toDisable
.add(R
.id
.action_rename_file
); 
 211                 toDisable
.add(R
.id
.action_remove_file
); 
 216             // contextual menu for regular files 
 218             // new design: 'download' and 'open with' won't be available anymore in context menu 
 219             toHide
.add(R
.id
.action_download_file
); 
 220             toHide
.add(R
.id
.action_open_file_with
); 
 222             if (targetFile
.isDown()) { 
 223                 toHide
.add(R
.id
.action_cancel_download
); 
 224                 toHide
.add(R
.id
.action_cancel_upload
); 
 227                 toHide
.add(R
.id
.action_sync_file
); 
 229             if ( mContainerActivity
.getFileDownloaderBinder().isDownloading(AccountUtils
.getCurrentOwnCloudAccount(getActivity()), targetFile
)) { 
 230                 toHide
.add(R
.id
.action_cancel_upload
); 
 231                 toDisable
.add(R
.id
.action_rename_file
); 
 232                 toDisable
.add(R
.id
.action_remove_file
); 
 234             } else if ( mContainerActivity
.getFileUploaderBinder().isUploading(AccountUtils
.getCurrentOwnCloudAccount(getActivity()), targetFile
)) { 
 235                 toHide
.add(R
.id
.action_cancel_download
); 
 236                 toDisable
.add(R
.id
.action_rename_file
); 
 237                 toDisable
.add(R
.id
.action_remove_file
); 
 240                 toHide
.add(R
.id
.action_cancel_download
); 
 241                 toHide
.add(R
.id
.action_cancel_upload
); 
 245         for (int i 
: toHide
) { 
 246             item 
= menu
.findItem(i
); 
 248                 item
.setVisible(false
); 
 249                 item
.setEnabled(false
); 
 253         for (int i 
: toDisable
) { 
 254             item 
= menu
.findItem(i
); 
 256                 item
.setEnabled(false
); 
 266     public boolean onContextItemSelected (MenuItem item
) { 
 267         AdapterContextMenuInfo info 
= (AdapterContextMenuInfo
) item
.getMenuInfo();         
 268         mTargetFile 
= (OCFile
) mAdapter
.getItem(info
.position
); 
 269         switch (item
.getItemId()) { 
 270             case R
.id
.action_rename_file
: { 
 271                 String fileName 
= mTargetFile
.getFileName(); 
 272                 int extensionStart 
= mTargetFile
.isDirectory() ? 
-1 : fileName
.lastIndexOf("."); 
 273                 int selectionEnd 
= (extensionStart 
>= 0) ? extensionStart 
: fileName
.length(); 
 274                 EditNameDialog dialog 
= EditNameDialog
.newInstance(getString(R
.string
.rename_dialog_title
), fileName
, 0, selectionEnd
, this); 
 275                 dialog
.show(getFragmentManager(), EditNameDialog
.TAG
); 
 278             case R
.id
.action_remove_file
: { 
 279                 int messageStringId 
= R
.string
.confirmation_remove_alert
; 
 280                 int posBtnStringId 
= R
.string
.confirmation_remove_remote
; 
 281                 int neuBtnStringId 
= -1; 
 282                 if (mTargetFile
.isDirectory()) { 
 283                     messageStringId 
= R
.string
.confirmation_remove_folder_alert
; 
 284                     posBtnStringId 
= R
.string
.confirmation_remove_remote_and_local
; 
 285                     neuBtnStringId 
= R
.string
.confirmation_remove_folder_local
; 
 286                 } else if (mTargetFile
.isDown()) { 
 287                     posBtnStringId 
= R
.string
.confirmation_remove_remote_and_local
; 
 288                     neuBtnStringId 
= R
.string
.confirmation_remove_local
; 
 290                 ConfirmationDialogFragment confDialog 
= ConfirmationDialogFragment
.newInstance( 
 292                         new String
[]{mTargetFile
.getFileName()}, 
 295                         R
.string
.common_cancel
); 
 296                 confDialog
.setOnConfirmationListener(this); 
 297                 confDialog
.show(getFragmentManager(), FileDetailFragment
.FTAG_CONFIRMATION
); 
 300             case R
.id
.action_sync_file
: { 
 301                 Account account 
= AccountUtils
.getCurrentOwnCloudAccount(getSherlockActivity()); 
 302                 RemoteOperation operation 
= new SynchronizeFileOperation(mTargetFile
, null
, mContainerActivity
.getStorageManager(), account
, true
, false
, getSherlockActivity()); 
 303                 operation
.execute(account
, getSherlockActivity(), mContainerActivity
, mHandler
, getSherlockActivity()); 
 304                 ((FileDisplayActivity
) getSherlockActivity()).showLoadingDialog(); 
 307             case R
.id
.action_cancel_download
: { 
 308                 FileDownloaderBinder downloaderBinder 
= mContainerActivity
.getFileDownloaderBinder(); 
 309                 Account account 
= AccountUtils
.getCurrentOwnCloudAccount(getActivity()); 
 310                 if (downloaderBinder 
!= null 
&& downloaderBinder
.isDownloading(account
, mTargetFile
)) { 
 311                     downloaderBinder
.cancel(account
, mTargetFile
); 
 313                     mContainerActivity
.onTransferStateChanged(mTargetFile
, false
, false
); 
 317             case R
.id
.action_cancel_upload
: { 
 318                 FileUploaderBinder uploaderBinder 
= mContainerActivity
.getFileUploaderBinder(); 
 319                 Account account 
= AccountUtils
.getCurrentOwnCloudAccount(getActivity()); 
 320                 if (uploaderBinder 
!= null 
&& uploaderBinder
.isUploading(account
, mTargetFile
)) { 
 321                     uploaderBinder
.cancel(account
, mTargetFile
); 
 323                     mContainerActivity
.onTransferStateChanged(mTargetFile
, false
, false
); 
 327             case R
.id
.action_see_details
: { 
 328                 ((FileFragment
.ContainerActivity
)getActivity()).showDetails(mTargetFile
); 
 332                 return super.onContextItemSelected(item
);  
 338      * Use this to query the {@link OCFile} that is currently 
 339      * being displayed by this fragment 
 340      * @return The currently viewed OCFile 
 342     public OCFile 
getCurrentFile(){ 
 347      * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter 
 349     public void listDirectory(){ 
 354      * Lists the given directory on the view. When the input parameter is null, 
 355      * it will either refresh the last known directory. list the root 
 356      * if there never was a directory. 
 358      * @param directory File to be listed 
 360     public void listDirectory(OCFile directory
) { 
 361         DataStorageManager storageManager 
= mContainerActivity
.getStorageManager(); 
 362         if (storageManager 
!= null
) { 
 364             // Check input parameters for null 
 365             if(directory 
== null
){ 
 369                     directory 
= storageManager
.getFileByPath("/"); 
 370                     if (directory 
== null
) return; // no files, wait for sync 
 375             // If that's not a directory -> List its parent 
 376             if(!directory
.isDirectory()){ 
 377                 Log_OC
.w(TAG
, "You see, that is not a directory -> " + directory
.toString()); 
 378                 directory 
= storageManager
.getFileById(directory
.getParentId()); 
 381             mAdapter
.swapDirectory(directory
, storageManager
); 
 382             if (mFile 
== null 
|| !mFile
.equals(directory
)) { 
 383                 mList
.setSelectionFromTop(0, 0); 
 392      * Interface to implement by any Activity that includes some instance of FileListFragment 
 394      * @author David A. Velasco 
 396     public interface ContainerActivity 
extends TransferServiceGetter
, OnRemoteOperationListener
, FileHandler 
{ 
 399          * Callback method invoked when a the user browsed into a different folder through the list of files 
 403         public void onBrowsedDownTo(OCFile folder
); 
 405         public void startDownloadForPreview(OCFile file
); 
 407         public void startMediaPreview(OCFile file
, int i
, boolean b
); 
 409         public void startImagePreview(OCFile file
); 
 411         public void startSyncFolderOperation(OCFile folder
); 
 414          * Getter for the current DataStorageManager in the container activity 
 416         public DataStorageManager 
getStorageManager(); 
 420          * Callback method invoked when a the 'transfer state' of a file changes. 
 422          * This happens when a download or upload is started or ended for a file. 
 424          * This method is necessary by now to update the user interface of the double-pane layout in tablets 
 425          * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)} 
 426          * won't provide the needed response before the method where this is called finishes.  
 428          * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO) 
 430          * @param file          OCFile which state changed. 
 431          * @param downloading   Flag signaling if the file is now downloading. 
 432          * @param uploading     Flag signaling if the file is now uploading. 
 434         public void onTransferStateChanged(OCFile file
, boolean downloading
, boolean uploading
); 
 440     public void onDismiss(EditNameDialog dialog
) { 
 441         if (dialog
.getResult()) { 
 442             String newFilename 
= dialog
.getNewFilename(); 
 443             Log_OC
.d(TAG
, "name edit dialog dismissed with new name " + newFilename
); 
 444             RemoteOperation operation 
= new RenameFileOperation(mTargetFile
,  
 445                                                                 AccountUtils
.getCurrentOwnCloudAccount(getActivity()),  
 447                                                                 mContainerActivity
.getStorageManager()); 
 448             operation
.execute(AccountUtils
.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity
, mHandler
, getSherlockActivity()); 
 449             ((FileDisplayActivity
) getActivity()).showLoadingDialog(); 
 455     public void onConfirmation(String callerTag
) { 
 456         if (callerTag
.equals(FileDetailFragment
.FTAG_CONFIRMATION
)) { 
 457             if (mContainerActivity
.getStorageManager().getFileById(mTargetFile
.getFileId()) != null
) { 
 458                 RemoteOperation operation 
= new RemoveFileOperation( mTargetFile
,  
 460                                                                     mContainerActivity
.getStorageManager()); 
 461                 operation
.execute(AccountUtils
.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity
, mHandler
, getSherlockActivity()); 
 463                 ((FileDisplayActivity
) getActivity()).showLoadingDialog(); 
 469     public void onNeutral(String callerTag
) { 
 471         if (mTargetFile
.isDirectory()) { 
 472             // TODO run in a secondary thread? 
 473             mContainerActivity
.getStorageManager().removeDirectory(mTargetFile
, false
, true
); 
 475         } else if (mTargetFile
.isDown() && (f 
= new File(mTargetFile
.getStoragePath())).exists()) { 
 477             mTargetFile
.setStoragePath(null
); 
 478             mContainerActivity
.getStorageManager().saveFile(mTargetFile
); 
 481         mContainerActivity
.onTransferStateChanged(mTargetFile
, false
, false
); 
 485     public void onCancel(String callerTag
) { 
 486         Log_OC
.d(TAG
, "REMOVAL CANCELED");