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
.actionbarsherlock
.view
.Menu
; 
  25 import com
.owncloud
.android
.R
; 
  26 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  27 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  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
.lib
.operations
.common
.OnRemoteOperationListener
; 
  33 import com
.owncloud
.android
.lib
.operations
.common
.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
; 
  45 import com
.owncloud
.android
.utils
.Log_OC
; 
  48 import android
.accounts
.Account
; 
  49 import android
.app
.Activity
; 
  50 import android
.os
.Bundle
; 
  51 import android
.os
.Handler
; 
  52 import android
.view
.ContextMenu
; 
  53 import android
.view
.MenuInflater
; 
  54 import android
.view
.MenuItem
; 
  55 import android
.view
.View
; 
  56 import android
.widget
.AdapterView
; 
  57 import android
.widget
.AdapterView
.AdapterContextMenuInfo
; 
  60  * A Fragment that lists all files and folders in a given path. 
  62  * @author Bartek Przybylski 
  65 public class OCFileListFragment 
extends ExtendedListFragment 
implements EditNameDialogListener
, ConfirmationDialogFragmentListener 
{ 
  67     private static final String TAG 
= OCFileListFragment
.class.getSimpleName(); 
  69     private static final String MY_PACKAGE 
= OCFileListFragment
.class.getPackage() != null ? OCFileListFragment
.class.getPackage().getName() : "com.owncloud.android.ui.fragment"; 
  70     private static final String EXTRA_FILE 
= MY_PACKAGE 
+ ".extra.FILE"; 
  72     private OCFileListFragment
.ContainerActivity mContainerActivity
; 
  74     private OCFile mFile 
= null
; 
  75     private FileListListAdapter mAdapter
; 
  77     private Handler mHandler
; 
  78     private OCFile mTargetFile
; 
  84     public void onAttach(Activity activity
) { 
  85         super.onAttach(activity
); 
  86         Log_OC
.e(TAG
, "onAttach"); 
  88             mContainerActivity 
= (ContainerActivity
) activity
; 
  89         } catch (ClassCastException e
) { 
  90             throw new ClassCastException(activity
.toString() + " must implement " + OCFileListFragment
.ContainerActivity
.class.getSimpleName()); 
  99     public void onActivityCreated(Bundle savedInstanceState
) { 
 100         super.onActivityCreated(savedInstanceState
); 
 101         Log_OC
.e(TAG
, "onActivityCreated() start"); 
 102         mAdapter 
= new FileListListAdapter(getActivity(), mContainerActivity
); 
 103         if (savedInstanceState 
!= null
) { 
 104             mFile 
= savedInstanceState
.getParcelable(EXTRA_FILE
); 
 106         setListAdapter(mAdapter
); 
 108         registerForContextMenu(getListView()); 
 109         getListView().setOnCreateContextMenuListener(this);         
 111         mHandler 
= new Handler(); 
 116      * Saves the current listed folder. 
 119     public void onSaveInstanceState (Bundle outState
) { 
 120         super.onSaveInstanceState(outState
); 
 121         outState
.putParcelable(EXTRA_FILE
, mFile
); 
 126      * Call this, when the user presses the up button. 
 128      * Tries to move up the current folder one level. If the parent folder was removed from the database,  
 129      * it continues browsing up until finding an existing folders. 
 131      * return       Count of folder levels browsed up. 
 133     public int onBrowseUp() { 
 134         OCFile parentDir 
= null
; 
 138             FileDataStorageManager storageManager 
= mContainerActivity
.getStorageManager(); 
 140             String parentPath 
= null
; 
 141             if (mFile
.getParentId() != FileDataStorageManager
.ROOT_PARENT_ID
) { 
 142                 parentPath 
= new File(mFile
.getRemotePath()).getParent(); 
 143                 parentPath 
= parentPath
.endsWith(OCFile
.PATH_SEPARATOR
) ? parentPath 
: parentPath 
+ OCFile
.PATH_SEPARATOR
; 
 144                 parentDir 
= storageManager
.getFileByPath(parentPath
); 
 147                 parentDir 
= storageManager
.getFileByPath(OCFile
.ROOT_PATH
);    // never returns null; keep the path in root folder 
 149             while (parentDir 
== null
) { 
 150                 parentPath 
= new File(parentPath
).getParent(); 
 151                 parentPath 
= parentPath
.endsWith(OCFile
.PATH_SEPARATOR
) ? parentPath 
: parentPath 
+ OCFile
.PATH_SEPARATOR
; 
 152                 parentDir 
= storageManager
.getFileByPath(parentPath
); 
 154             }   // exit is granted because storageManager.getFileByPath("/") never returns null 
 159             listDirectory(mFile
); 
 161             mContainerActivity
.startSyncFolderOperation(mFile
); 
 162         }   // else - should never happen now 
 168     public void onItemClick(AdapterView
<?
> l
, View v
, int position
, long id
) { 
 169         OCFile file 
= (OCFile
) mAdapter
.getItem(position
); 
 171             if (file
.isFolder()) {  
 172                 // update state and view of this fragment 
 174                 // then, notify parent activity to let it update its state and view, and other fragments 
 175                 mContainerActivity
.onBrowsedDownTo(file
); 
 177             } else { /// Click on a file 
 178                 if (PreviewImageFragment
.canBePreviewed(file
)) { 
 179                     // preview image - it handles the download, if needed 
 180                     mContainerActivity
.startImagePreview(file
); 
 182                 } else if (file
.isDown()) { 
 183                     if (PreviewMediaFragment
.canBePreviewed(file
)) { 
 185                         mContainerActivity
.startMediaPreview(file
, 0, true
); 
 188                         mContainerActivity
.openFile(file
); 
 192                     // automatic download, preview on finish 
 193                     mContainerActivity
.startDownloadForPreview(file
); 
 199             Log_OC
.d(TAG
, "Null object in ListAdapter!!"); 
 208     public void onCreateContextMenu (ContextMenu menu
, View v
, ContextMenu
.ContextMenuInfo menuInfo
) { 
 209         super.onCreateContextMenu(menu
, v
, menuInfo
); 
 210         MenuInflater inflater 
= getActivity().getMenuInflater(); 
 211         inflater
.inflate(R
.menu
.file_actions_menu
, menu
); 
 212         AdapterContextMenuInfo info 
= (AdapterContextMenuInfo
) menuInfo
; 
 213         OCFile targetFile 
= (OCFile
) mAdapter
.getItem(info
.position
); 
 214         List
<Integer
> toHide 
= new ArrayList
<Integer
>();     
 215         List
<Integer
> toDisable 
= new ArrayList
<Integer
>();   
 217         MenuItem item 
= null
; 
 218         if (targetFile
.isFolder()) { 
 219             // contextual menu for folders 
 220             toHide
.add(R
.id
.action_open_file_with
); 
 221             toHide
.add(R
.id
.action_download_file
); 
 222             toHide
.add(R
.id
.action_cancel_download
); 
 223             toHide
.add(R
.id
.action_cancel_upload
); 
 224             toHide
.add(R
.id
.action_sync_file
); 
 225             toHide
.add(R
.id
.action_see_details
); 
 226             if (    mContainerActivity
.getFileDownloaderBinder().isDownloading(AccountUtils
.getCurrentOwnCloudAccount(getActivity()), targetFile
) || 
 227                     mContainerActivity
.getFileUploaderBinder().isUploading(AccountUtils
.getCurrentOwnCloudAccount(getActivity()), targetFile
)           ) { 
 228                 toDisable
.add(R
.id
.action_rename_file
); 
 229                 toDisable
.add(R
.id
.action_remove_file
); 
 234             // contextual menu for regular files 
 236             // new design: 'download' and 'open with' won't be available anymore in context menu 
 237             toHide
.add(R
.id
.action_download_file
); 
 238             toHide
.add(R
.id
.action_open_file_with
); 
 240             if (targetFile
.isDown()) { 
 241                 toHide
.add(R
.id
.action_cancel_download
); 
 242                 toHide
.add(R
.id
.action_cancel_upload
); 
 245                 toHide
.add(R
.id
.action_sync_file
); 
 247             if ( mContainerActivity
.getFileDownloaderBinder().isDownloading(AccountUtils
.getCurrentOwnCloudAccount(getActivity()), targetFile
)) { 
 248                 toHide
.add(R
.id
.action_cancel_upload
); 
 249                 toDisable
.add(R
.id
.action_rename_file
); 
 250                 toDisable
.add(R
.id
.action_remove_file
); 
 252             } else if ( mContainerActivity
.getFileUploaderBinder().isUploading(AccountUtils
.getCurrentOwnCloudAccount(getActivity()), targetFile
)) { 
 253                 toHide
.add(R
.id
.action_cancel_download
); 
 254                 toDisable
.add(R
.id
.action_rename_file
); 
 255                 toDisable
.add(R
.id
.action_remove_file
); 
 258                 toHide
.add(R
.id
.action_cancel_download
); 
 259                 toHide
.add(R
.id
.action_cancel_upload
); 
 263         for (int i 
: toHide
) { 
 264             item 
= menu
.findItem(i
); 
 266                 item
.setVisible(false
); 
 267                 item
.setEnabled(false
); 
 271         for (int i 
: toDisable
) { 
 272             item 
= menu
.findItem(i
); 
 274                 item
.setEnabled(false
); 
 284     public boolean onContextItemSelected (MenuItem item
) { 
 285         AdapterContextMenuInfo info 
= (AdapterContextMenuInfo
) item
.getMenuInfo();         
 286         mTargetFile 
= (OCFile
) mAdapter
.getItem(info
.position
); 
 287         switch (item
.getItemId()) { 
 288             case R
.id
.action_rename_file
: { 
 289                 String fileName 
= mTargetFile
.getFileName(); 
 290                 int extensionStart 
= mTargetFile
.isFolder() ? 
-1 : fileName
.lastIndexOf("."); 
 291                 int selectionEnd 
= (extensionStart 
>= 0) ? extensionStart 
: fileName
.length(); 
 292                 EditNameDialog dialog 
= EditNameDialog
.newInstance(getString(R
.string
.rename_dialog_title
), fileName
, 0, selectionEnd
, this); 
 293                 dialog
.show(getFragmentManager(), EditNameDialog
.TAG
); 
 296             case R
.id
.action_remove_file
: { 
 297                 int messageStringId 
= R
.string
.confirmation_remove_alert
; 
 298                 int posBtnStringId 
= R
.string
.confirmation_remove_remote
; 
 299                 int neuBtnStringId 
= -1; 
 300                 if (mTargetFile
.isFolder()) { 
 301                     messageStringId 
= R
.string
.confirmation_remove_folder_alert
; 
 302                     posBtnStringId 
= R
.string
.confirmation_remove_remote_and_local
; 
 303                     neuBtnStringId 
= R
.string
.confirmation_remove_folder_local
; 
 304                 } else if (mTargetFile
.isDown()) { 
 305                     posBtnStringId 
= R
.string
.confirmation_remove_remote_and_local
; 
 306                     neuBtnStringId 
= R
.string
.confirmation_remove_local
; 
 308                 ConfirmationDialogFragment confDialog 
= ConfirmationDialogFragment
.newInstance( 
 310                         new String
[]{mTargetFile
.getFileName()}, 
 313                         R
.string
.common_cancel
); 
 314                 confDialog
.setOnConfirmationListener(this); 
 315                 confDialog
.show(getFragmentManager(), FileDetailFragment
.FTAG_CONFIRMATION
); 
 318             case R
.id
.action_sync_file
: { 
 319                 Account account 
= AccountUtils
.getCurrentOwnCloudAccount(getSherlockActivity()); 
 320                 RemoteOperation operation 
= new SynchronizeFileOperation(mTargetFile
, null
, mContainerActivity
.getStorageManager(), account
, true
, getSherlockActivity()); 
 321                 operation
.execute(account
, getSherlockActivity(), mContainerActivity
, mHandler
, getSherlockActivity()); 
 322                 ((FileDisplayActivity
) getSherlockActivity()).showLoadingDialog(); 
 325             case R
.id
.action_cancel_download
: { 
 326                 FileDownloaderBinder downloaderBinder 
= mContainerActivity
.getFileDownloaderBinder(); 
 327                 Account account 
= AccountUtils
.getCurrentOwnCloudAccount(getActivity()); 
 328                 if (downloaderBinder 
!= null 
&& downloaderBinder
.isDownloading(account
, mTargetFile
)) { 
 329                     downloaderBinder
.cancel(account
, mTargetFile
); 
 331                     mContainerActivity
.onTransferStateChanged(mTargetFile
, false
, false
); 
 335             case R
.id
.action_cancel_upload
: { 
 336                 FileUploaderBinder uploaderBinder 
= mContainerActivity
.getFileUploaderBinder(); 
 337                 Account account 
= AccountUtils
.getCurrentOwnCloudAccount(getActivity()); 
 338                 if (uploaderBinder 
!= null 
&& uploaderBinder
.isUploading(account
, mTargetFile
)) { 
 339                     uploaderBinder
.cancel(account
, mTargetFile
); 
 341                     mContainerActivity
.onTransferStateChanged(mTargetFile
, false
, false
); 
 345             case R
.id
.action_see_details
: { 
 346                 ((FileFragment
.ContainerActivity
)getActivity()).showDetails(mTargetFile
); 
 350                 return super.onContextItemSelected(item
);  
 359     public void onPrepareOptionsMenu(Menu menu
) { 
 360         super.onPrepareOptionsMenu(menu
); 
 362         List
<Integer
> toHide 
= new ArrayList
<Integer
>(); 
 363         MenuItem item 
= null
; 
 364         for (int i 
: toHide
) { 
 365             item 
= (MenuItem
) menu
.findItem(i
); 
 367                 item
.setVisible(false
); 
 368                 item
.setEnabled(false
); 
 375      * Use this to query the {@link OCFile} that is currently 
 376      * being displayed by this fragment 
 377      * @return The currently viewed OCFile 
 379     public OCFile 
getCurrentFile(){ 
 384      * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter 
 386     public void listDirectory(){ 
 391      * Lists the given directory on the view. When the input parameter is null, 
 392      * it will either refresh the last known directory. list the root 
 393      * if there never was a directory. 
 395      * @param directory File to be listed 
 397     public void listDirectory(OCFile directory
) { 
 398         FileDataStorageManager storageManager 
= mContainerActivity
.getStorageManager(); 
 399         if (storageManager 
!= null
) { 
 401             // Check input parameters for null 
 402             if(directory 
== null
){ 
 406                     directory 
= storageManager
.getFileByPath("/"); 
 407                     if (directory 
== null
) return; // no files, wait for sync 
 412             // If that's not a directory -> List its parent 
 413             if(!directory
.isFolder()){ 
 414                 Log_OC
.w(TAG
, "You see, that is not a directory -> " + directory
.toString()); 
 415                 directory 
= storageManager
.getFileById(directory
.getParentId()); 
 418             mAdapter
.swapDirectory(directory
, storageManager
); 
 419             if (mFile 
== null 
|| !mFile
.equals(directory
)) { 
 420                 mList
.setSelectionFromTop(0, 0); 
 429      * Interface to implement by any Activity that includes some instance of FileListFragment 
 431      * @author David A. Velasco 
 433     public interface ContainerActivity 
extends TransferServiceGetter
, OnRemoteOperationListener
, FileHandler 
{ 
 436          * Callback method invoked when a the user browsed into a different folder through the list of files 
 440         public void onBrowsedDownTo(OCFile folder
); 
 442         public void startDownloadForPreview(OCFile file
); 
 444         public void startMediaPreview(OCFile file
, int i
, boolean b
); 
 446         public void startImagePreview(OCFile file
); 
 448         public void startSyncFolderOperation(OCFile folder
); 
 451          * Getter for the current DataStorageManager in the container activity 
 453         public FileDataStorageManager 
getStorageManager(); 
 457          * Callback method invoked when a the 'transfer state' of a file changes. 
 459          * This happens when a download or upload is started or ended for a file. 
 461          * This method is necessary by now to update the user interface of the double-pane layout in tablets 
 462          * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)} 
 463          * won't provide the needed response before the method where this is called finishes.  
 465          * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO) 
 467          * @param file          OCFile which state changed. 
 468          * @param downloading   Flag signaling if the file is now downloading. 
 469          * @param uploading     Flag signaling if the file is now uploading. 
 471         public void onTransferStateChanged(OCFile file
, boolean downloading
, boolean uploading
); 
 477     public void onDismiss(EditNameDialog dialog
) { 
 478         if (dialog
.getResult()) { 
 479             String newFilename 
= dialog
.getNewFilename(); 
 480             Log_OC
.d(TAG
, "name edit dialog dismissed with new name " + newFilename
); 
 481             RemoteOperation operation 
= new RenameFileOperation(mTargetFile
,  
 482                                                                 AccountUtils
.getCurrentOwnCloudAccount(getActivity()),  
 484                                                                 mContainerActivity
.getStorageManager()); 
 485             operation
.execute(AccountUtils
.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity
, mHandler
, getSherlockActivity()); 
 486             ((FileDisplayActivity
) getActivity()).showLoadingDialog(); 
 492     public void onConfirmation(String callerTag
) { 
 493         if (callerTag
.equals(FileDetailFragment
.FTAG_CONFIRMATION
)) { 
 494             if (mContainerActivity
.getStorageManager().getFileById(mTargetFile
.getFileId()) != null
) { 
 495                 RemoteOperation operation 
= new RemoveFileOperation( mTargetFile
,  
 497                                                                     mContainerActivity
.getStorageManager()); 
 498                 operation
.execute(AccountUtils
.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity
, mHandler
, getSherlockActivity()); 
 500                 ((FileDisplayActivity
) getActivity()).showLoadingDialog(); 
 506     public void onNeutral(String callerTag
) { 
 507         mContainerActivity
.getStorageManager().removeFile(mTargetFile
, false
, true
);    // TODO perform in background task / new thread 
 509         mContainerActivity
.onTransferStateChanged(mTargetFile
, false
, false
); 
 513     public void onCancel(String callerTag
) { 
 514         Log_OC
.d(TAG
, "REMOVAL CANCELED");