2  *   ownCloud Android client application 
   4  *   @author Bartek Przybylski 
   6  *   @author David A. Velasco 
   7  *   Copyright (C) 2011  Bartek Przybylski 
   8  *   Copyright (C) 2015 ownCloud Inc. 
  10  *   This program is free software: you can redistribute it and/or modify 
  11  *   it under the terms of the GNU General Public License version 2, 
  12  *   as published by the Free Software Foundation. 
  14  *   This program is distributed in the hope that it will be useful, 
  15  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  16  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  17  *   GNU General Public License for more details. 
  19  *   You should have received a copy of the GNU General Public License 
  20  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  23 package com
.owncloud
.android
.ui
.fragment
; 
  25 import android
.app
.Activity
; 
  26 import android
.content
.Intent
; 
  27 import android
.os
.Bundle
; 
  28 import android
.support
.v4
.widget
.SwipeRefreshLayout
; 
  29 import android
.view
.ContextMenu
; 
  30 import android
.view
.Menu
; 
  31 import android
.view
.MenuInflater
; 
  32 import android
.view
.MenuItem
; 
  33 import android
.view
.View
; 
  34 import android
.widget
.AdapterView
; 
  35 import android
.widget
.AdapterView
.AdapterContextMenuInfo
; 
  36 import android
.widget
.PopupMenu
; 
  38 import com
.owncloud
.android
.R
; 
  39 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  40 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  41 import com
.owncloud
.android
.datamodel
.OCFile
; 
  42 import com
.owncloud
.android
.files
.FileMenuFilter
; 
  43 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  44 import com
.owncloud
.android
.lib
.resources
.status
.OwnCloudVersion
; 
  45 import com
.owncloud
.android
.ui
.activity
.FileActivity
; 
  46 import com
.owncloud
.android
.ui
.activity
.FileDisplayActivity
; 
  47 import com
.owncloud
.android
.ui
.activity
.FolderPickerActivity
; 
  48 import com
.owncloud
.android
.ui
.activity
.OnEnforceableRefreshListener
; 
  49 import com
.owncloud
.android
.ui
.adapter
.FileListListAdapter
; 
  50 import com
.owncloud
.android
.ui
.dialog
.ConfirmationDialogFragment
; 
  51 import com
.owncloud
.android
.ui
.dialog
.FileActionsDialogFragment
; 
  52 import com
.owncloud
.android
.ui
.dialog
.RemoveFileDialogFragment
; 
  53 import com
.owncloud
.android
.ui
.dialog
.RenameFileDialogFragment
; 
  54 import com
.owncloud
.android
.ui
.preview
.PreviewImageFragment
; 
  55 import com
.owncloud
.android
.ui
.preview
.PreviewMediaFragment
; 
  56 import com
.owncloud
.android
.utils
.FileStorageUtils
; 
  57 import com
.owncloud
.android
.ui
.preview
.PreviewTextFragment
; 
  62  * A Fragment that lists all files and folders in a given path. 
  64  * TODO refactor to get rid of direct dependency on FileDisplayActivity 
  66 public class OCFileListFragment 
extends ExtendedListFragment 
implements FileActionsDialogFragment
.FileActionsDialogFragmentListener 
{ 
  68     private static final String TAG 
= OCFileListFragment
.class.getSimpleName(); 
  70     private static final String MY_PACKAGE 
= OCFileListFragment
.class.getPackage() != null ?
 
  71             OCFileListFragment
.class.getPackage().getName() : "com.owncloud.android.ui.fragment"; 
  73     public final static String ARG_JUST_FOLDERS 
= MY_PACKAGE 
+ ".JUST_FOLDERS"; 
  74     public final static String ARG_ALLOW_CONTEXTUAL_ACTIONS 
= MY_PACKAGE 
+ ".ALLOW_CONTEXTUAL"; 
  76     private static final String KEY_FILE 
= MY_PACKAGE 
+ ".extra.FILE"; 
  78     private FileFragment
.ContainerActivity mContainerActivity
; 
  80     private OCFile mFile 
= null
; 
  81     private FileListListAdapter mAdapter
; 
  82     private boolean mJustFolders
; 
  84     private OCFile mTargetFile
; 
  92     public void onAttach(Activity activity
) { 
  93         super.onAttach(activity
); 
  94         Log_OC
.e(TAG
, "onAttach"); 
  96             mContainerActivity 
= (FileFragment
.ContainerActivity
) activity
; 
  98         } catch (ClassCastException e
) { 
  99             throw new ClassCastException(activity
.toString() + " must implement " + 
 100                     FileFragment
.ContainerActivity
.class.getSimpleName()); 
 103             setOnRefreshListener((OnEnforceableRefreshListener
) activity
); 
 105         } catch (ClassCastException e
) { 
 106             throw new ClassCastException(activity
.toString() + " must implement " + 
 107                     SwipeRefreshLayout
.OnRefreshListener
.class.getSimpleName()); 
 113     public void onDetach() { 
 114         setOnRefreshListener(null
); 
 115         mContainerActivity 
= null
; 
 123     public void onActivityCreated(Bundle savedInstanceState
) { 
 124         super.onActivityCreated(savedInstanceState
); 
 125         Log_OC
.e(TAG
, "onActivityCreated() start"); 
 127         if (savedInstanceState 
!= null
) { 
 128             mFile 
= savedInstanceState
.getParcelable(KEY_FILE
); 
 132             setFooterEnabled(false
); 
 134             setFooterEnabled(true
); 
 137         Bundle args 
= getArguments(); 
 138         mJustFolders 
= (args 
== null
) ? false 
: args
.getBoolean(ARG_JUST_FOLDERS
, false
); 
 139         mAdapter 
= new FileListListAdapter( 
 144         setListAdapter(mAdapter
); 
 146         registerLongClickListener(); 
 149     private void registerLongClickListener() { 
 150         getListView().setOnItemLongClickListener(new AdapterView
.OnItemLongClickListener() { 
 151             public boolean onItemLongClick(AdapterView
<?
> arg0
, View v
, 
 152                                            int index
, long arg3
) { 
 153                 showFileAction(index
); 
 160     private void showFileAction(int fileIndex
) { 
 161         Bundle args 
= getArguments(); 
 162         PopupMenu pm 
= new PopupMenu(getActivity(),null
); 
 163         Menu menu 
= pm
.getMenu(); 
 165         boolean allowContextualActions 
= 
 166                 (args 
== null
) ? true 
: args
.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS
, true
); 
 168         if (allowContextualActions
) { 
 169             MenuInflater inflater 
= getActivity().getMenuInflater(); 
 171             inflater
.inflate(R
.menu
.file_actions_menu
, menu
); 
 172             OCFile targetFile 
= (OCFile
) mAdapter
.getItem(fileIndex
); 
 174             if (mContainerActivity
.getStorageManager() != null
) { 
 175                 FileMenuFilter mf 
= new FileMenuFilter( 
 177                         mContainerActivity
.getStorageManager().getAccount(), 
 184             /// TODO break this direct dependency on FileDisplayActivity... if possible 
 185             MenuItem item 
= menu
.findItem(R
.id
.action_open_file_with
); 
 186             FileFragment frag 
= ((FileDisplayActivity
)getActivity()).getSecondFragment(); 
 187             if (frag 
!= null 
&& frag 
instanceof FileDetailFragment 
&& 
 188                     frag
.getFile().getFileId() == targetFile
.getFileId()) { 
 189                 item 
= menu
.findItem(R
.id
.action_see_details
); 
 191                     item
.setVisible(false
); 
 192                     item
.setEnabled(false
); 
 196             FileActionsDialogFragment dialog 
= FileActionsDialogFragment
.newInstance(menu
, fileIndex
, targetFile
.getFileName()); 
 197             dialog
.setTargetFragment(this, 0); 
 198             dialog
.show(getFragmentManager(), FileActionsDialogFragment
.FTAG_FILE_ACTIONS
); 
 203      * Saves the current listed folder. 
 206     public void onSaveInstanceState(Bundle outState
) { 
 207         super.onSaveInstanceState(outState
); 
 208         outState
.putParcelable(KEY_FILE
, mFile
); 
 212      * Call this, when the user presses the up button. 
 214      * Tries to move up the current folder one level. If the parent folder was removed from the 
 215      * database, it continues browsing up until finding an existing folders. 
 217      * return       Count of folder levels browsed up. 
 219     public int onBrowseUp() { 
 220         OCFile parentDir 
= null
; 
 224             FileDataStorageManager storageManager 
= mContainerActivity
.getStorageManager(); 
 226             String parentPath 
= null
; 
 227             if (mFile
.getParentId() != FileDataStorageManager
.ROOT_PARENT_ID
) { 
 228                 parentPath 
= new File(mFile
.getRemotePath()).getParent(); 
 229                 parentPath 
= parentPath
.endsWith(OCFile
.PATH_SEPARATOR
) ? parentPath 
: 
 230                         parentPath 
+ OCFile
.PATH_SEPARATOR
; 
 231                 parentDir 
= storageManager
.getFileByPath(parentPath
); 
 234                 parentDir 
= storageManager
.getFileByPath(OCFile
.ROOT_PATH
); 
 236             while (parentDir 
== null
) { 
 237                 parentPath 
= new File(parentPath
).getParent(); 
 238                 parentPath 
= parentPath
.endsWith(OCFile
.PATH_SEPARATOR
) ? parentPath 
: 
 239                         parentPath 
+ OCFile
.PATH_SEPARATOR
; 
 240                 parentDir 
= storageManager
.getFileByPath(parentPath
); 
 242             }   // exit is granted because storageManager.getFileByPath("/") never returns null 
 245             // TODO Enable when "On Device" is recovered ? 
 246             listDirectory(mFile 
/*, MainApp.getOnlyOnDevice()*/); 
 250             // restore index and top position 
 251             restoreIndexAndTopPosition(); 
 253         }   // else - should never happen now 
 259     public void onItemClick(AdapterView
<?
> l
, View v
, int position
, long id
) { 
 260         OCFile file 
= (OCFile
) mAdapter
.getItem(position
); 
 262             if (file
.isFolder()) { 
 263                 // update state and view of this fragment 
 264                 // TODO Enable when "On Device" is recovered ? 
 265                 listDirectory(file
/*, MainApp.getOnlyOnDevice()*/); 
 266                 // then, notify parent activity to let it update its state and view 
 267                 mContainerActivity
.onBrowsedDownTo(file
); 
 268                 // save index and top position 
 269                 saveIndexAndTopPosition(position
); 
 271             } else { /// Click on a file 
 272                 if (PreviewImageFragment
.canBePreviewed(file
)) { 
 273                     // preview image - it handles the download, if needed 
 274                     ((FileDisplayActivity
)mContainerActivity
).startImagePreview(file
); 
 275                 } else if (PreviewTextFragment
.canBePreviewed(file
)){ 
 276                     ((FileDisplayActivity
)mContainerActivity
).startTextPreview(file
); 
 277                 } else if (file
.isDown()) { 
 278                     if (PreviewMediaFragment
.canBePreviewed(file
)) { 
 280                         ((FileDisplayActivity
) mContainerActivity
).startMediaPreview(file
, 0, true
); 
 282                         mContainerActivity
.getFileOperationsHelper().openFile(file
); 
 286                     // automatic download, preview on finish 
 287                     ((FileDisplayActivity
) mContainerActivity
).startDownloadForPreview(file
); 
 293             Log_OC
.d(TAG
, "Null object in ListAdapter!!"); 
 302     public void onCreateContextMenu( 
 303             ContextMenu menu
, View v
, ContextMenu
.ContextMenuInfo menuInfo
) { 
 304         Bundle args 
= getArguments(); 
 305         boolean allowContextualActions 
= 
 306                 (args 
== null
) ? true 
: args
.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS
, true
); 
 307         if (allowContextualActions
) { 
 308             MenuInflater inflater 
= getActivity().getMenuInflater(); 
 309             inflater
.inflate(R
.menu
.file_actions_menu
, menu
); 
 310             AdapterContextMenuInfo info 
= (AdapterContextMenuInfo
) menuInfo
; 
 311             OCFile targetFile 
= (OCFile
) mAdapter
.getItem(info
.position
); 
 313             if (mContainerActivity
.getStorageManager() != null
) { 
 314                 FileMenuFilter mf 
= new FileMenuFilter( 
 316                     mContainerActivity
.getStorageManager().getAccount(), 
 323             /// TODO break this direct dependency on FileDisplayActivity... if possible 
 324             MenuItem item 
= menu
.findItem(R
.id
.action_open_file_with
); 
 325             FileFragment frag 
= ((FileDisplayActivity
)getActivity()).getSecondFragment(); 
 326             if (frag 
!= null 
&& frag 
instanceof FileDetailFragment 
&&  
 327                     frag
.getFile().getFileId() == targetFile
.getFileId()) { 
 328                 item 
= menu
.findItem(R
.id
.action_see_details
); 
 330                     item
.setVisible(false
); 
 331                     item
.setEnabled(false
); 
 341     public boolean onFileActionChosen(int menuId
, int filePosition
) { 
 342         mTargetFile 
= (OCFile
) mAdapter
.getItem(filePosition
); 
 344             case R
.id
.action_share_file
: { 
 345                 mContainerActivity
.getFileOperationsHelper().shareFileWithLink(mTargetFile
); 
 348             case R
.id
.action_open_file_with
: { 
 349                 mContainerActivity
.getFileOperationsHelper().openFile(mTargetFile
); 
 352             case R
.id
.action_unshare_file
: { 
 353                 mContainerActivity
.getFileOperationsHelper().unshareFileWithLink(mTargetFile
); 
 356             case R
.id
.action_rename_file
: { 
 357                 RenameFileDialogFragment dialog 
= RenameFileDialogFragment
.newInstance(mTargetFile
); 
 358                 dialog
.show(getFragmentManager(), FileDetailFragment
.FTAG_RENAME_FILE
); 
 361             case R
.id
.action_remove_file
: { 
 362                 RemoveFileDialogFragment dialog 
= RemoveFileDialogFragment
.newInstance(mTargetFile
); 
 363                 dialog
.show(getFragmentManager(), ConfirmationDialogFragment
.FTAG_CONFIRMATION
); 
 366             case R
.id
.action_download_file
: 
 367             case R
.id
.action_sync_file
: { 
 368                 mContainerActivity
.getFileOperationsHelper().syncFile(mTargetFile
); 
 371             case R
.id
.action_cancel_download
: 
 372             case R
.id
.action_cancel_upload
: { 
 373                 ((FileDisplayActivity
) mContainerActivity
).cancelTransference(mTargetFile
); 
 376             case R
.id
.action_see_details
: { 
 377                 mContainerActivity
.showDetails(mTargetFile
); 
 380             case R
.id
.action_send_file
: { 
 382                 if (!mTargetFile
.isDown()) {  // Download the file 
 383                     Log_OC
.d(TAG
, mTargetFile
.getRemotePath() + " : File must be downloaded"); 
 384                     ((FileDisplayActivity
) mContainerActivity
).startDownloadForSending(mTargetFile
); 
 387                     mContainerActivity
.getFileOperationsHelper().sendDownloadedFile(mTargetFile
); 
 391             case R
.id
.action_move
: { 
 392                 Intent action 
= new Intent(getActivity(), FolderPickerActivity
.class); 
 394                 // Pass mTargetFile that contains info of selected file/folder 
 395                 action
.putExtra(FolderPickerActivity
.EXTRA_FILE
, mTargetFile
); 
 396                 getActivity().startActivityForResult(action
, FileDisplayActivity
.ACTION_MOVE_FILES
); 
 399             case R
.id
.action_favorite_file
: { 
 400                 mContainerActivity
.getFileOperationsHelper().toggleFavorite(mTargetFile
, true
); 
 403             case R
.id
.action_unfavorite_file
: { 
 404                 mContainerActivity
.getFileOperationsHelper().toggleFavorite(mTargetFile
, false
); 
 407             case R
.id
.action_copy
: 
 408                 Intent action 
= new Intent(getActivity(), FolderPickerActivity
.class); 
 410                 // Pass mTargetFile that contains info of selected file/folder 
 411                 action
.putExtra(FolderPickerActivity
.EXTRA_FILE
, mTargetFile
); 
 412                 getActivity().startActivityForResult(action
, FileDisplayActivity
.ACTION_COPY_FILES
); 
 423     public boolean onContextItemSelected (MenuItem item
) { 
 424         AdapterContextMenuInfo info 
= (AdapterContextMenuInfo
) item
.getMenuInfo(); 
 425         boolean matched 
= onFileActionChosen(item
.getItemId(), ((AdapterContextMenuInfo
) item
.getMenuInfo()).position
); 
 427             return super.onContextItemSelected(item
); 
 435      * Use this to query the {@link OCFile} that is currently 
 436      * being displayed by this fragment 
 438      * @return The currently viewed OCFile 
 440     public OCFile 
getCurrentFile() { 
 445      * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter 
 447     public void listDirectory(/*boolean onlyOnDevice*/){ 
 449         // TODO Enable when "On Device" is recovered ? 
 450         // listDirectory(null, onlyOnDevice); 
 453     public void refreshDirectory(){ 
 454         // TODO Enable when "On Device" is recovered ? 
 455         listDirectory(getCurrentFile()/*, MainApp.getOnlyOnDevice()*/); 
 459      * Lists the given directory on the view. When the input parameter is null, 
 460      * it will either refresh the last known directory. list the root 
 461      * if there never was a directory. 
 463      * @param directory File to be listed 
 465     public void listDirectory(OCFile directory
/*, boolean onlyOnDevice*/) { 
 466         FileDataStorageManager storageManager 
= mContainerActivity
.getStorageManager(); 
 467         if (storageManager 
!= null
) { 
 469             // Check input parameters for null 
 470             if (directory 
== null
) { 
 474                     directory 
= storageManager
.getFileByPath("/"); 
 475                     if (directory 
== null
) return; // no files, wait for sync 
 480             // If that's not a directory -> List its parent 
 481             if (!directory
.isFolder()) { 
 482                 Log_OC
.w(TAG
, "You see, that is not a directory -> " + directory
.toString()); 
 483                 directory 
= storageManager
.getFileById(directory
.getParentId()); 
 486             // TODO Enable when "On Device" is recovered ? 
 487             mAdapter
.swapDirectory(directory
, storageManager
/*, onlyOnDevice*/); 
 488             if (mFile 
== null 
|| !mFile
.equals(directory
)) { 
 489                 mCurrentListView
.setSelection(0); 
 498     private void updateLayout() { 
 500             int filesCount 
= 0, foldersCount 
= 0, imagesCount 
= 0; 
 501             int count 
= mAdapter
.getCount(); 
 503             for (int i
=0; i 
< count 
; i
++) { 
 504                 file 
= (OCFile
) mAdapter
.getItem(i
); 
 505                 if (file
.isFolder()) { 
 508                     if (!file
.isHidden()) { 
 511                         if (file
.isImage()) { 
 518             setFooterText(generateFooterText(filesCount
, foldersCount
)); 
 520             // decide grid vs list view 
 521             OwnCloudVersion version 
= AccountUtils
.getServerVersion( 
 522                     ((FileActivity
)mContainerActivity
).getAccount()); 
 523             if (version 
!= null 
&& version
.supportsRemoteThumbnails() && 
 524                 imagesCount 
> 0 && imagesCount 
== filesCount
) { 
 526                 registerLongClickListener(); 
 533     private String 
generateFooterText(int filesCount
, int foldersCount
) { 
 535         if (filesCount 
<= 0) { 
 536             if (foldersCount 
<= 0) { 
 539             } else if (foldersCount 
== 1) { 
 540                 output 
= getResources().getString(R
.string
.file_list__footer__folder
); 
 542             } else { // foldersCount > 1 
 543                 output 
= getResources().getString(R
.string
.file_list__footer__folders
, foldersCount
); 
 546         } else if (filesCount 
== 1) { 
 547             if (foldersCount 
<= 0) { 
 548                 output 
= getResources().getString(R
.string
.file_list__footer__file
); 
 550             } else if (foldersCount 
== 1) { 
 551                 output 
= getResources().getString(R
.string
.file_list__footer__file_and_folder
); 
 553             } else { // foldersCount > 1 
 554                 output 
= getResources().getString(R
.string
.file_list__footer__file_and_folders
, foldersCount
); 
 556         } else {    // filesCount > 1 
 557             if (foldersCount 
<= 0) { 
 558                 output 
= getResources().getString(R
.string
.file_list__footer__files
, filesCount
); 
 560             } else if (foldersCount 
== 1) { 
 561                 output 
= getResources().getString(R
.string
.file_list__footer__files_and_folder
, filesCount
); 
 563             } else { // foldersCount > 1 
 564                 output 
= getResources().getString( 
 565                         R
.string
.file_list__footer__files_and_folders
, filesCount
, foldersCount
 
 573     public void sortByName(boolean descending
) { 
 574         mAdapter
.setSortOrder(FileStorageUtils
.SORT_NAME
, descending
); 
 577     public void sortByDate(boolean descending
) { 
 578         mAdapter
.setSortOrder(FileStorageUtils
.SORT_DATE
, descending
); 
 581     public void sortBySize(boolean descending
) { 
 582         mAdapter
.setSortOrder(FileStorageUtils
.SORT_SIZE
, descending
);