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
; 
  27 import android
.app
.Activity
; 
  28 import android
.content
.Intent
; 
  29 import android
.os
.Bundle
; 
  30 import android
.support
.v4
.widget
.SwipeRefreshLayout
; 
  31 import android
.view
.ContextMenu
; 
  32 import android
.view
.MenuInflater
; 
  33 import android
.view
.MenuItem
; 
  34 import android
.view
.View
; 
  35 import android
.widget
.AdapterView
; 
  36 import android
.widget
.AdapterView
.AdapterContextMenuInfo
; 
  38 import com
.owncloud
.android
.R
; 
  39 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  40 import com
.owncloud
.android
.datamodel
.OCFile
; 
  41 import com
.owncloud
.android
.files
.FileMenuFilter
; 
  42 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  43 import com
.owncloud
.android
.ui
.activity
.FileDisplayActivity
; 
  44 import com
.owncloud
.android
.ui
.activity
.FolderPickerActivity
; 
  45 import com
.owncloud
.android
.ui
.activity
.OnEnforceableRefreshListener
; 
  46 import com
.owncloud
.android
.ui
.adapter
.FileListListAdapter
; 
  47 import com
.owncloud
.android
.ui
.dialog
.ConfirmationDialogFragment
; 
  48 import com
.owncloud
.android
.ui
.dialog
.RemoveFileDialogFragment
; 
  49 import com
.owncloud
.android
.ui
.dialog
.RenameFileDialogFragment
; 
  50 import com
.owncloud
.android
.ui
.preview
.PreviewImageFragment
; 
  51 import com
.owncloud
.android
.ui
.preview
.PreviewMediaFragment
; 
  52 import com
.owncloud
.android
.utils
.FileStorageUtils
; 
  55  * A Fragment that lists all files and folders in a given path. 
  57  * TODO refactorize to get rid of direct dependency on FileDisplayActivity 
  59 public class OCFileListFragment 
extends ExtendedListFragment 
{ 
  61     private static final String TAG 
= OCFileListFragment
.class.getSimpleName(); 
  63     private static final String MY_PACKAGE 
= OCFileListFragment
.class.getPackage() != null ?
 
  64             OCFileListFragment
.class.getPackage().getName() : "com.owncloud.android.ui.fragment"; 
  66     public final static String ARG_JUST_FOLDERS 
= MY_PACKAGE 
+ ".JUST_FOLDERS"; 
  67     public final static String ARG_ALLOW_CONTEXTUAL_ACTIONS 
= MY_PACKAGE 
+ ".ALLOW_CONTEXTUAL"; 
  69     private static final String KEY_FILE 
= MY_PACKAGE 
+ ".extra.FILE"; 
  71     private final static Double THUMBNAIL_THRESHOLD 
= 0.5; 
  73     private FileFragment
.ContainerActivity mContainerActivity
; 
  75     private OCFile mFile 
= null
; 
  76     private FileListListAdapter mAdapter
; 
  77     private boolean mJustFolders
; 
  79     private OCFile mTargetFile
; 
  86     public void onAttach(Activity activity
) { 
  87         super.onAttach(activity
); 
  88         Log_OC
.e(TAG
, "onAttach"); 
  90             mContainerActivity 
= (FileFragment
.ContainerActivity
) activity
; 
  92         } catch (ClassCastException e
) { 
  93             throw new ClassCastException(activity
.toString() + " must implement " +  
  94                     FileFragment
.ContainerActivity
.class.getSimpleName()); 
  97             setOnRefreshListener((OnEnforceableRefreshListener
) activity
); 
  99         } catch (ClassCastException e
) { 
 100             throw new ClassCastException(activity
.toString() + " must implement " +  
 101                     SwipeRefreshLayout
.OnRefreshListener
.class.getSimpleName()); 
 107     public void onDetach() { 
 108         setOnRefreshListener(null
); 
 109         mContainerActivity 
= null
; 
 117     public void onActivityCreated(Bundle savedInstanceState
) { 
 118         super.onActivityCreated(savedInstanceState
); 
 119         Log_OC
.e(TAG
, "onActivityCreated() start"); 
 121         if (savedInstanceState 
!= null
) { 
 122             mFile 
= savedInstanceState
.getParcelable(KEY_FILE
); 
 126             setFooterEnabled(false
); 
 128             setFooterEnabled(true
); 
 131         Bundle args 
= getArguments(); 
 132         mJustFolders 
= (args 
== null
) ? false 
: args
.getBoolean(ARG_JUST_FOLDERS
, false
); 
 133         mAdapter 
= new FileListListAdapter( 
 135                 getSherlockActivity(), 
 138         setListAdapter(mAdapter
); 
 140         registerForContextMenu(); 
 144      * Saves the current listed folder. 
 147     public void onSaveInstanceState (Bundle outState
) { 
 148         super.onSaveInstanceState(outState
); 
 149         outState
.putParcelable(KEY_FILE
, mFile
); 
 153      * Call this, when the user presses the up button. 
 155      * Tries to move up the current folder one level. If the parent folder was removed from the  
 156      * database, it continues browsing up until finding an existing folders. 
 158      * return       Count of folder levels browsed up. 
 160     public int onBrowseUp() { 
 161         OCFile parentDir 
= null
; 
 165             FileDataStorageManager storageManager 
= mContainerActivity
.getStorageManager(); 
 167             String parentPath 
= null
; 
 168             if (mFile
.getParentId() != FileDataStorageManager
.ROOT_PARENT_ID
) { 
 169                 parentPath 
= new File(mFile
.getRemotePath()).getParent(); 
 170                 parentPath 
= parentPath
.endsWith(OCFile
.PATH_SEPARATOR
) ? parentPath 
:  
 171                         parentPath 
+ OCFile
.PATH_SEPARATOR
; 
 172                 parentDir 
= storageManager
.getFileByPath(parentPath
); 
 175                 parentDir 
= storageManager
.getFileByPath(OCFile
.ROOT_PATH
); 
 177             while (parentDir 
== null
) { 
 178                 parentPath 
= new File(parentPath
).getParent(); 
 179                 parentPath 
= parentPath
.endsWith(OCFile
.PATH_SEPARATOR
) ? parentPath 
:  
 180                         parentPath 
+ OCFile
.PATH_SEPARATOR
; 
 181                 parentDir 
= storageManager
.getFileByPath(parentPath
); 
 183             }   // exit is granted because storageManager.getFileByPath("/") never returns null 
 186             listDirectory(mFile
); 
 190             // restore index and top position 
 191             restoreIndexAndTopPosition(); 
 193         }   // else - should never happen now 
 199     public void onItemClick(AdapterView
<?
> l
, View v
, int position
, long id
) { 
 200         OCFile file 
= (OCFile
) mAdapter
.getItem(position
); 
 202             if (file
.isFolder()) {  
 203                 // update state and view of this fragment 
 205                 // then, notify parent activity to let it update its state and view 
 206                 mContainerActivity
.onBrowsedDownTo(file
); 
 207                 // save index and top position 
 208                 saveIndexAndTopPosition(position
); 
 210             } else { /// Click on a file 
 211                 if (PreviewImageFragment
.canBePreviewed(file
)) { 
 212                     // preview image - it handles the download, if needed 
 213                     ((FileDisplayActivity
)mContainerActivity
).startImagePreview(file
); 
 215                 } else if (file
.isDown()) { 
 216                     if (PreviewMediaFragment
.canBePreviewed(file
)) { 
 218                         ((FileDisplayActivity
)mContainerActivity
).startMediaPreview(file
, 0, true
); 
 220                         mContainerActivity
.getFileOperationsHelper().openFile(file
); 
 224                     // automatic download, preview on finish 
 225                     ((FileDisplayActivity
)mContainerActivity
).startDownloadForPreview(file
); 
 231             Log_OC
.d(TAG
, "Null object in ListAdapter!!"); 
 240     public void onCreateContextMenu ( 
 241             ContextMenu menu
, View v
, ContextMenu
.ContextMenuInfo menuInfo
) { 
 242         super.onCreateContextMenu(menu
, v
, menuInfo
); 
 243         Bundle args 
= getArguments(); 
 244         boolean allowContextualActions 
=  
 245                 (args 
== null
) ? true 
: args
.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS
, true
);  
 246         if (allowContextualActions
) { 
 247             MenuInflater inflater 
= getSherlockActivity().getMenuInflater(); 
 248             inflater
.inflate(R
.menu
.file_actions_menu
, menu
); 
 249             AdapterContextMenuInfo info 
= (AdapterContextMenuInfo
) menuInfo
; 
 250             OCFile targetFile 
= (OCFile
) mAdapter
.getItem(info
.position
); 
 252             if (mContainerActivity
.getStorageManager() != null
) { 
 253                 FileMenuFilter mf 
= new FileMenuFilter( 
 255                     mContainerActivity
.getStorageManager().getAccount(), 
 257                     getSherlockActivity() 
 262             /// TODO break this direct dependency on FileDisplayActivity... if possible 
 263             MenuItem item 
= menu
.findItem(R
.id
.action_open_file_with
); 
 264             FileFragment frag 
= ((FileDisplayActivity
)getSherlockActivity()).getSecondFragment(); 
 265             if (frag 
!= null 
&& frag 
instanceof FileDetailFragment 
&&  
 266                     frag
.getFile().getFileId() == targetFile
.getFileId()) { 
 267                 item 
= menu
.findItem(R
.id
.action_see_details
); 
 269                     item
.setVisible(false
); 
 270                     item
.setEnabled(false
); 
 281     public boolean onContextItemSelected (MenuItem item
) { 
 282         AdapterContextMenuInfo info 
= (AdapterContextMenuInfo
) item
.getMenuInfo();         
 283         mTargetFile 
= (OCFile
) mAdapter
.getItem(info
.position
); 
 284         switch (item
.getItemId()) {                 
 285             case R
.id
.action_share_file
: { 
 286                 mContainerActivity
.getFileOperationsHelper().shareFileWithLink(mTargetFile
); 
 289             case R
.id
.action_open_file_with
: { 
 290                 mContainerActivity
.getFileOperationsHelper().openFile(mTargetFile
); 
 293             case R
.id
.action_unshare_file
: { 
 294                 mContainerActivity
.getFileOperationsHelper().unshareFileWithLink(mTargetFile
); 
 297             case R
.id
.action_rename_file
: { 
 298                 RenameFileDialogFragment dialog 
= RenameFileDialogFragment
.newInstance(mTargetFile
); 
 299                 dialog
.show(getFragmentManager(), FileDetailFragment
.FTAG_RENAME_FILE
); 
 302             case R
.id
.action_remove_file
: { 
 303                 RemoveFileDialogFragment dialog 
= RemoveFileDialogFragment
.newInstance(mTargetFile
); 
 304                 dialog
.show(getFragmentManager(), ConfirmationDialogFragment
.FTAG_CONFIRMATION
); 
 307             case R
.id
.action_download_file
:  
 308             case R
.id
.action_sync_file
: { 
 309                 mContainerActivity
.getFileOperationsHelper().syncFile(mTargetFile
); 
 312             case R
.id
.action_cancel_download
: 
 313             case R
.id
.action_cancel_upload
: { 
 314                 ((FileDisplayActivity
)mContainerActivity
).cancelTransference(mTargetFile
); 
 317             case R
.id
.action_see_details
: { 
 318                 mContainerActivity
.showDetails(mTargetFile
); 
 321             case R
.id
.action_send_file
: { 
 323                 if (!mTargetFile
.isDown()) {  // Download the file 
 324                     Log_OC
.d(TAG
, mTargetFile
.getRemotePath() + " : File must be downloaded"); 
 325                     ((FileDisplayActivity
)mContainerActivity
).startDownloadForSending(mTargetFile
); 
 328                     mContainerActivity
.getFileOperationsHelper().sendDownloadedFile(mTargetFile
); 
 332             case R
.id
.action_move
: { 
 333                 Intent action 
= new Intent(getActivity(), FolderPickerActivity
.class); 
 335                 // Pass mTargetFile that contains info of selected file/folder 
 336                 action
.putExtra(FolderPickerActivity
.EXTRA_FILE
, mTargetFile
); 
 337                 getActivity().startActivityForResult(action
, FileDisplayActivity
.ACTION_MOVE_FILES
); 
 341                 return super.onContextItemSelected(item
);  
 347      * Use this to query the {@link OCFile} that is currently 
 348      * being displayed by this fragment 
 349      * @return The currently viewed OCFile 
 351     public OCFile 
getCurrentFile(){ 
 356      * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter 
 358     public void listDirectory(){ 
 363      * Lists the given directory on the view. When the input parameter is null, 
 364      * it will either refresh the last known directory. list the root 
 365      * if there never was a directory. 
 367      * @param directory File to be listed 
 369     public void listDirectory(OCFile directory
) { 
 370         FileDataStorageManager storageManager 
= mContainerActivity
.getStorageManager(); 
 371         if (storageManager 
!= null
) { 
 373             // Check input parameters for null 
 374             if(directory 
== null
){ 
 378                     directory 
= storageManager
.getFileByPath("/"); 
 379                     if (directory 
== null
) return; // no files, wait for sync 
 384             // If that's not a directory -> List its parent 
 385             if(!directory
.isFolder()){ 
 386                 Log_OC
.w(TAG
, "You see, that is not a directory -> " + directory
.toString()); 
 387                 directory 
= storageManager
.getFileById(directory
.getParentId()); 
 390             mAdapter
.swapDirectory(directory
, storageManager
); 
 391             if (mFile 
== null 
|| !mFile
.equals(directory
)) { 
 392                 mCurrentListView
.setSelection(0); 
 401     private void updateLayout() { 
 403             int filesCount 
= 0, foldersCount 
= 0, imagesCount 
= 0; 
 404             int count 
= mAdapter
.getCount(); 
 406             for (int i
=0; i 
< count 
; i
++) { 
 407                 file 
= (OCFile
) mAdapter
.getItem(i
); 
 408                 if (file
.isFolder()) { 
 418             setFooterText(generateFooterText(filesCount
, foldersCount
)); 
 420             // decide grid vs list view 
 421             if (((double)imagesCount 
/ (double)filesCount
) >= THUMBNAIL_THRESHOLD
) { 
 429     private String 
generateFooterText(int filesCount
, int foldersCount
) { 
 432             if (filesCount 
== 1) { 
 433                 output 
= output 
+ filesCount 
+ " " + getResources().getString(R
.string
.file_list_file
); 
 435                 output 
= output 
+ filesCount 
+ " " + getResources().getString(R
.string
.file_list_files
); 
 438         if (foldersCount 
> 0 && filesCount 
> 0){ 
 439             output 
= output 
+ ", "; 
 441         if (foldersCount 
== 1) { 
 442             output 
= output 
+ foldersCount 
+ " " + getResources().getString(R
.string
.file_list_folder
); 
 443         } else if (foldersCount 
> 1) { 
 444             output 
= output 
+ foldersCount 
+ " " + getResources().getString(R
.string
.file_list_folders
); 
 451     public void sortByName(boolean descending
) { 
 452         mAdapter
.setSortOrder(FileStorageUtils
.SORT_NAME
, descending
); 
 455     public void sortByDate(boolean descending
) { 
 456         mAdapter
.setSortOrder(FileStorageUtils
.SORT_DATE
, descending
); 
 459     public void sortBySize(boolean descending
) { 
 460         mAdapter
.setSortOrder(FileStorageUtils
.SORT_SIZE
, descending
);