Browse Source

Add new activity for selecting the destination folder when moving files, including new List Fragment and new Adapter for listing only the folders

jabarros 10 years ago
parent
commit
dc984ec5bf

+ 2 - 3
AndroidManifest.xml

@@ -186,9 +186,8 @@
         <service android:name=".services.observer.FileObserverService"/>
         
         <activity 
-            android:name=".ui.activity.CopyToClipboardActivity" 
-           	android:label="@string/copy_link"
-           	android:icon="@drawable/copy_link" />
+			android:name=".ui.activity.MoveActivity"
+			android:label="@string/app_name"/>
         
     </application>
 

+ 36 - 0
res/layout/files_move.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background_color"
+    android:orientation="vertical" >
+
+	<FrameLayout 
+		android:layout_width="match_parent"
+		android:layout_height="0dip"
+        android:layout_weight="1"
+		android:id="@+id/fragment_container" />
+	
+	<LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="horizontal" >
+
+        <Button
+            android:id="@+id/move_files_btn_cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/common_cancel" />
+
+		<Button
+		    android:id="@+id/move_files_btn_choose"
+		    android:layout_width="wrap_content"
+		    android:layout_height="wrap_content"
+		    android:layout_weight="1"
+		    android:text="@string/move_choose_button_text" />
+
+	</LinearLayout>
+
+ </LinearLayout>

+ 2 - 0
res/values/strings.xml

@@ -287,5 +287,7 @@
 	<string name="auth_redirect_non_secure_connection_title">Secure connection is redirected through a non secure route.</string>
 
 	<string name="actionbar_move">Move</string>
+	<string name="file_list_empty_moving">Nothing in here. You can add a folder!</string>
+	<string name="move_choose_button_text">Choose</string>
 
 </resources>

+ 606 - 0
src/com/owncloud/android/ui/activity/MoveActivity.java

@@ -0,0 +1,606 @@
+package com.owncloud.android.ui.activity;
+
+import java.io.IOException;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.ActionBar.OnNavigationListener;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuInflater;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.Window;
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.lib.common.OwnCloudCredentials;
+import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.services.observer.FileObserverService;
+import com.owncloud.android.syncadapter.FileSyncAdapter;
+import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
+import com.owncloud.android.ui.fragment.FileFragment;
+import com.owncloud.android.ui.fragment.MoveFileListFragment;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.Log_OC;
+
+public class MoveActivity extends HookActivity implements FileFragment.ContainerActivity, 
+    OnNavigationListener, OnClickListener{
+    
+    private ArrayAdapter<String> mDirectories;
+    
+    private SyncBroadcastReceiver mSyncBroadcastReceiver;
+
+    public static final int DIALOG_SHORT_WAIT = 0;
+    
+    public static final String ACTION_DETAILS = "com.owncloud.android.ui.activity.action.DETAILS";
+
+    private static final String TAG = MoveActivity.class.getSimpleName();
+    
+    private static final String TAG_LIST_OF_FILES = "LIST_OF_FILES";
+       
+    private boolean mSyncInProgress = false;
+
+    private Button mCancelBtn;
+    private Button mChooseBtn;
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Log_OC.d(TAG, "onCreate() start");
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+
+        super.onCreate(savedInstanceState); // this calls onAccountChanged() when ownCloud Account is valid
+
+        /// grant that FileObserverService is watching favourite files
+        if (savedInstanceState == null) {
+            Intent initObserversIntent = FileObserverService.makeInitIntent(this);
+            startService(initObserversIntent);
+        }
+
+        /// USER INTERFACE
+
+        // Inflate and set the layout view
+        setContentView(R.layout.files_move);    
+        if (savedInstanceState == null) {
+            createMinFragments();
+        }
+
+        initControls();
+
+        // Action bar setup
+        mDirectories = new CustomArrayAdapter<String>(this, R.layout.sherlock_spinner_dropdown_item);
+        getSupportActionBar().setHomeButtonEnabled(true);       // mandatory since Android ICS, according to the official documentation
+        setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/);    // always AFTER setContentView(...) ; to work around bug in its implementation
+        
+        setBackgroundText();
+
+        Log_OC.d(TAG, "onCreate() end");
+        
+    }
+
+    private void createMinFragments() {
+        MoveFileListFragment listOfFiles = new MoveFileListFragment();
+        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+        transaction.add(R.id.fragment_container, listOfFiles, TAG_LIST_OF_FILES);
+        transaction.commit();
+    }
+
+    /**
+     * Show a text message on screen view for notifying user if content is
+     * loading or folder is empty
+     */
+    private void setBackgroundText() {
+        MoveFileListFragment MoveFileListFragment = getListOfFilesFragment();
+        if (MoveFileListFragment != null) {
+            int message = R.string.file_list_loading;
+            if (!mSyncInProgress) {
+                // In case folder list is empty
+                message = R.string.file_list_empty_moving;
+            }
+            MoveFileListFragment.setMessageForEmptyList(getString(message));
+        } else {
+            Log.e(TAG, "MoveFileListFragment is null");
+        }
+    }
+
+    private MoveFileListFragment getListOfFilesFragment() {
+        Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(MoveActivity.TAG_LIST_OF_FILES);
+        if (listOfFiles != null) {
+            return (MoveFileListFragment)listOfFiles;
+        }
+        Log_OC.wtf(TAG, "Access to unexisting list of files fragment!!");
+        return null;
+    }
+
+    // Custom array adapter to override text colors
+    private class CustomArrayAdapter<T> extends ArrayAdapter<T> {
+
+        public CustomArrayAdapter(MoveActivity ctx, int view) {
+            super(ctx, view);
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View v = super.getView(position, convertView, parent);
+
+            ((TextView) v).setTextColor(getResources().getColorStateList(
+                    android.R.color.white));
+            
+            fixRoot((TextView) v );
+            return v;
+        }
+
+        public View getDropDownView(int position, View convertView,
+                ViewGroup parent) {
+            View v = super.getDropDownView(position, convertView, parent);
+
+            ((TextView) v).setTextColor(getResources().getColorStateList(
+                    android.R.color.white));
+
+            fixRoot((TextView) v );
+            return v;
+        }
+
+        private void fixRoot(TextView v) {
+            if (v.getText().equals(OCFile.PATH_SEPARATOR)) {
+                v.setText(R.string.default_display_name_for_root_folder);
+            }
+        }
+
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * Updates action bar and second fragment, if in dual pane mode.
+     */
+    @Override
+    public void onBrowsedDownTo(OCFile directory) {
+        pushDirname(directory);
+        
+        // Sync Folder
+        startSyncFolderOperation(directory);
+        
+    }
+
+    /**
+     * Shows the information of the {@link OCFile} received as a 
+     * parameter in the second fragment.
+     * 
+     * @param file          {@link OCFile} whose details will be shown
+     */
+    @Override
+    public void showDetails(OCFile file) {
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) {
+            
+    }
+
+    /**
+     * Pushes a directory to the drop down list
+     * @param directory to push
+     * @throws IllegalArgumentException If the {@link OCFile#isFolder()} returns false.
+     */
+    public void pushDirname(OCFile directory) {
+        if(!directory.isFolder()){
+            throw new IllegalArgumentException("Only directories may be pushed!");
+        }
+        mDirectories.insert(directory.getFileName(), 0);
+        setFile(directory);
+    }
+
+    public void startSyncFolderOperation(OCFile folder) {
+        long currentSyncTime = System.currentTimeMillis(); 
+        
+        mSyncInProgress = true;
+                
+        // perform folder synchronization
+        RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,  
+                                                                        currentSyncTime, 
+                                                                        false,
+                                                                        getFileOperationsHelper().isSharedSupported(),
+                                                                        getStorageManager(), 
+                                                                        getAccount(), 
+                                                                        getApplicationContext()
+                                                                      );
+        synchFolderOp.execute(getAccount(), this, null, null);
+        
+        setSupportProgressBarIndeterminateVisibility(true);
+
+        setBackgroundText();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Log_OC.e(TAG, "onResume() start");
+        
+        // refresh list of files
+        refreshListOfFilesFragment();
+
+        // Listen for sync messages
+        IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
+        syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
+        syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
+        syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
+        syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
+        mSyncBroadcastReceiver = new SyncBroadcastReceiver();
+        registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
+        
+        Log_OC.d(TAG, "onResume() end");
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        getSupportActionBar().setIcon(DisplayUtils.getSeasonalIconId());
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getSherlock().getMenuInflater();
+        inflater.inflate(R.menu.main_menu, menu);
+        menu.findItem(R.id.action_upload).setVisible(false);
+        menu.findItem(R.id.action_settings).setVisible(false);
+        menu.findItem(R.id.action_sync_account).setVisible(false);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        boolean retval = true;
+        switch (item.getItemId()) {
+        case R.id.action_create_dir: {
+            CreateFolderDialogFragment dialog = 
+                    CreateFolderDialogFragment.newInstance(getCurrentDir());
+            dialog.show(getSupportFragmentManager(), "createdirdialog");
+            break;
+        }
+        case android.R.id.home: {
+            OCFile currentDir = getCurrentDir();
+            if(currentDir != null && currentDir.getParentId() != 0) {
+                onBackPressed();
+            }
+            break;
+        }
+        default:
+            retval = super.onOptionsItemSelected(item);
+        }
+        return retval;
+    }
+
+    private OCFile getCurrentDir() {
+        OCFile file = getFile();
+        if (file != null) {
+            if (file.isFolder()) {
+                return file;
+            } else if (getStorageManager() != null) {
+                String parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName()));
+                return getStorageManager().getFileByPath(parentPath);
+            }
+        }
+        return null;
+    }
+    
+    protected void refreshListOfFilesFragment() {
+        MoveFileListFragment fileListFragment = getListOfFilesFragment();
+        if (fileListFragment != null) { 
+            fileListFragment.listDirectory();
+        }
+    }
+
+    private class SyncBroadcastReceiver extends BroadcastReceiver {
+
+        /**
+         * {@link BroadcastReceiver} to enable syncing feedback in UI
+         */
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            try {
+                String event = intent.getAction();
+                Log_OC.d(TAG, "Received broadcast " + event);
+                String accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME);
+                String synchFolderRemotePath = intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH); 
+                RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncAdapter.EXTRA_RESULT);
+                boolean sameAccount = (getAccount() != null && accountName.equals(getAccount().name) && getStorageManager() != null); 
+    
+                if (sameAccount) {
+                    
+                    if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
+                        mSyncInProgress = true;
+                        
+                    } else {
+                        OCFile currentFile = (getFile() == null) ? null : getStorageManager().getFileByPath(getFile().getRemotePath());
+                        OCFile currentDir = (getCurrentDir() == null) ? null : getStorageManager().getFileByPath(getCurrentDir().getRemotePath());
+    
+                        if (currentDir == null) {
+                            // current folder was removed from the server 
+                            Toast.makeText( MoveActivity.this, 
+                                            String.format(getString(R.string.sync_current_folder_was_removed), mDirectories.getItem(0)), 
+                                            Toast.LENGTH_LONG)
+                                .show();
+                            browseToRoot();
+                            
+                        } else {
+                            if (currentFile == null && !getFile().isFolder()) {
+                                // currently selected file was removed in the server, and now we know it
+                                currentFile = currentDir;
+                            }
+
+                            if (synchFolderRemotePath != null && currentDir.getRemotePath().equals(synchFolderRemotePath)) {
+                                MoveFileListFragment fileListFragment = getListOfFilesFragment();
+                                if (fileListFragment != null) {
+                                    fileListFragment.listDirectory(currentDir);
+                                }
+                            }
+                            setFile(currentFile);
+                        }
+                        
+                        mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
+                                
+                        if (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
+                                    equals(event) &&
+                                /// TODO refactor and make common
+                                synchResult != null && !synchResult.isSuccess() &&  
+                                (synchResult.getCode() == ResultCode.UNAUTHORIZED   || 
+                                    synchResult.isIdPRedirection()                  ||
+                                    (synchResult.isException() && synchResult.getException() 
+                                            instanceof AuthenticatorException))) {
+
+                            OwnCloudClient client = null;
+                            try {
+                                OwnCloudAccount ocAccount = 
+                                        new OwnCloudAccount(getAccount(), context);
+                                client = (OwnCloudClientManagerFactory.getDefaultSingleton().
+                                        removeClientFor(ocAccount));
+                                // TODO get rid of these exceptions
+                            } catch (AccountNotFoundException e) {
+                                e.printStackTrace();
+                            } catch (AuthenticatorException e) {
+                                e.printStackTrace();
+                            } catch (OperationCanceledException e) {
+                                e.printStackTrace();
+                            } catch (IOException e) {
+                                e.printStackTrace();
+                            }
+                            
+                            if (client != null) {
+                                OwnCloudCredentials cred = client.getCredentials();
+                                if (cred != null) {
+                                    AccountManager am = AccountManager.get(context);
+                                    if (cred.authTokenExpires()) {
+                                        am.invalidateAuthToken(
+                                                getAccount().type, 
+                                                cred.getAuthToken()
+                                        );
+                                    } else {
+                                        am.clearPassword(getAccount());
+                                    }
+                                }
+                            }
+                            
+                            requestCredentialsUpdate();
+                            
+                        }
+                    }
+                    removeStickyBroadcast(intent);
+                    Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress);
+                    setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/);
+
+                    setBackgroundText();
+                        
+                }
+                
+            } catch (RuntimeException e) {
+                // avoid app crashes after changing the serial id of RemoteOperationResult 
+                // in owncloud library with broadcast notifications pending to process
+                removeStickyBroadcast(intent);
+            }
+        }
+    }
+
+    public void browseToRoot() {
+        MoveFileListFragment listOfFiles = getListOfFilesFragment(); 
+        if (listOfFiles != null) {  // should never be null, indeed
+            while (mDirectories.getCount() > 1) {
+                popDirname();
+            }
+            OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
+            listOfFiles.listDirectory(root);
+            setFile(listOfFiles.getCurrentFile());
+            startSyncFolderOperation(root);
+        }
+    }
+
+    /**
+     * Pops a directory name from the drop down list
+     * @return True, unless the stack is empty
+     */
+    public boolean popDirname() {
+        mDirectories.remove(mDirectories.getItem(0));
+        return !mDirectories.isEmpty();
+    }
+
+    private void setNavigationListWithFolder(OCFile file) {
+        mDirectories.clear();
+        OCFile fileIt = file;
+        String parentPath;
+        while(fileIt != null && fileIt.getFileName() != OCFile.ROOT_PATH) {
+            if (fileIt.isFolder()) {
+                mDirectories.add(fileIt.getFileName());
+            }
+            // get parent from path
+            parentPath = fileIt.getRemotePath().substring(0, fileIt.getRemotePath().lastIndexOf(fileIt.getFileName()));
+            fileIt = getStorageManager().getFileByPath(parentPath);
+        }
+        mDirectories.add(OCFile.PATH_SEPARATOR);
+    }
+
+    @Override
+    public boolean onNavigationItemSelected(int itemPosition, long itemId) {
+        if (itemPosition != 0) {
+            String targetPath = "";
+            for (int i=itemPosition; i < mDirectories.getCount() - 1; i++) {
+                targetPath = mDirectories.getItem(i) + OCFile.PATH_SEPARATOR + targetPath; 
+            }
+            targetPath = OCFile.PATH_SEPARATOR + targetPath;
+            OCFile targetFolder = getStorageManager().getFileByPath(targetPath);
+            if (targetFolder != null) {
+                browseTo(targetFolder);
+            }
+
+            // the next operation triggers a new call to this method, but it's necessary to 
+            // ensure that the name exposed in the action bar is the current directory when the 
+            // user selected it in the navigation list
+            if (getSupportActionBar().getNavigationMode() == ActionBar.NAVIGATION_MODE_LIST  && itemPosition != 0)
+                getSupportActionBar().setSelectedNavigationItem(0);
+        }
+        return true;
+    }
+
+    public void browseTo(OCFile folder) {
+        if (folder == null || !folder.isFolder()) {
+            throw new IllegalArgumentException("Trying to browse to invalid folder " + folder);
+        }
+        MoveFileListFragment listOfFiles = getListOfFilesFragment();
+        if (listOfFiles != null) {
+            setNavigationListWithFolder(folder);
+            listOfFiles.listDirectory(folder);
+            setFile(listOfFiles.getCurrentFile());
+            startSyncFolderOperation(folder);
+        } else {
+            Log_OC.e(TAG, "Unexpected null when accessing list fragment");
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        MoveFileListFragment listOfFiles = getListOfFilesFragment();
+        if (listOfFiles != null) {  // should never be null, indeed
+            if (mDirectories.getCount() <= 1) {
+                finish();
+                return;
+            }
+            int levelsUp = listOfFiles.onBrowseUp();
+            for (int i=0; i < levelsUp && mDirectories.getCount() > 1 ; i++) {
+                popDirname();
+            }
+        }
+        if (listOfFiles != null) {  // should never be null, indeed
+            setFile(listOfFiles.getCurrentFile());
+        }
+    }
+
+    private void updateNavigationElementsInActionBar(OCFile chosenFile) {
+        ActionBar actionBar = getSupportActionBar();
+        if (chosenFile == null) {
+            // only list of files - set for browsing through folders
+            OCFile currentDir = getCurrentDir();
+            boolean noRoot = (currentDir != null && currentDir.getParentId() != 0);
+            actionBar.setDisplayHomeAsUpEnabled(noRoot);
+            actionBar.setDisplayShowTitleEnabled(!noRoot);
+            if (!noRoot) {
+                actionBar.setTitle(getString(R.string.default_display_name_for_root_folder));
+            }
+            actionBar.setNavigationMode(!noRoot ? ActionBar.NAVIGATION_MODE_STANDARD : ActionBar.NAVIGATION_MODE_LIST);
+            actionBar.setListNavigationCallbacks(mDirectories, this);   // assuming mDirectories is updated
+
+        } else {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setDisplayShowTitleEnabled(true);
+            actionBar.setTitle(chosenFile.getFileName());
+            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+        }
+    }
+
+    /**
+     *  Called when the ownCloud {@link Account} associated to the Activity was just updated.
+     */
+    @Override
+    protected void onAccountSet(boolean stateWasRecovered) {
+        super.onAccountSet(stateWasRecovered);
+        if (getAccount() != null) {
+            /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account
+            OCFile file = getFile();
+            // get parent from path
+            String parentPath = "";
+            if (file != null) {
+                if (file.isDown() && file.getLastSyncDateForProperties() == 0) {
+                    // upload in progress - right now, files are not inserted in the local cache until the upload is successful
+                    // get parent from path
+                    parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName()));
+                    if (getStorageManager().getFileByPath(parentPath) ==  null)
+                        file = null; // not able to know the directory where the file is uploading
+                } else {
+                    file = getStorageManager().getFileByPath(file.getRemotePath());   // currentDir = null if not in the current Account
+                }
+            }
+            if (file == null) {
+                // fall back to root folder
+                file = getStorageManager().getFileByPath(OCFile.ROOT_PATH);  // never returns null
+            }
+            setFile(file);
+            setNavigationListWithFolder(file);
+
+            if (!stateWasRecovered) {
+                Log_OC.e(TAG, "Initializing Fragments in onAccountChanged..");
+                if (file.isFolder()) {
+                    startSyncFolderOperation(file);
+                }
+            } else {
+                updateNavigationElementsInActionBar(file.isFolder() ? null : file);
+            }
+        }
+    }
+
+    /**
+     * Set controllers
+     */
+    private void initControls(){
+        mCancelBtn = (Button) findViewById(R.id.move_files_btn_cancel);
+        mCancelBtn.setOnClickListener(this);
+        mChooseBtn = (Button) findViewById(R.id.move_files_btn_choose);
+        mChooseBtn.setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mCancelBtn) {
+            finish();
+        }
+    }
+}

+ 233 - 0
src/com/owncloud/android/ui/adapter/FolderListListAdapter.java

@@ -0,0 +1,233 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2011  Bartek Przybylski
+ *   Copyright (C) 2012-2014 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.adapter;
+
+import java.util.Vector;
+
+import android.accounts.Account;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
+import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.ui.activity.ComponentsGetter;
+import com.owncloud.android.utils.DisplayUtils;
+
+
+/**
+ * This Adapter populates a ListView with all the folders in an ownCloud instance.
+ */
+public class FolderListListAdapter extends BaseAdapter implements ListAdapter {
+    private final static String PERMISSION_SHARED_WITH_ME = "S";
+
+    private Context mContext;
+    private OCFile mFile = null;
+    private Vector<OCFile> mFolders = null;
+
+    private FileDataStorageManager mStorageManager;
+    private Account mAccount;
+    private ComponentsGetter mTransferServiceGetter;
+    
+    public FolderListListAdapter(Context context, ComponentsGetter transferServiceGetter) {
+        mContext = context;
+        mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
+        mTransferServiceGetter = transferServiceGetter;
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        return true;
+    }
+
+    @Override
+    public int getCount() {
+        return mFolders != null ? mFolders.size() : 0;
+    }
+
+    @Override
+    public Object getItem(int position) {
+        if (mFolders == null || mFolders.size() <= position)
+            return null;
+        return mFolders.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        if (mFolders == null || mFolders.size() <= position)
+            return 0;
+        return mFolders.get(position).getFileId();
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return 0;
+    }
+
+    @SuppressLint("InflateParams")
+	@Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View view = convertView;
+        if (view == null) {
+            LayoutInflater inflator = (LayoutInflater) mContext
+                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            view = inflator.inflate(R.layout.list_item, null);
+        }
+
+        if (mFolders != null && mFolders.size() > position) {
+            OCFile file = mFolders.get(position);
+            TextView fileName = (TextView) view.findViewById(R.id.Filename);
+            String name = file.getFileName();
+
+            fileName.setText(name);
+            ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1);
+            ImageView sharedIconV = (ImageView) view.findViewById(R.id.sharedIcon);
+            ImageView sharedWithMeIconV = (ImageView) view.findViewById(R.id.sharedWithMeIcon);
+            sharedWithMeIconV.setVisibility(View.GONE);
+
+            ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2);
+            localStateView.bringToFront();
+            FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder();
+            FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();
+
+            if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {
+                localStateView.setImageResource(R.drawable.downloading_file_indicator);
+                localStateView.setVisibility(View.VISIBLE);
+            } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) {
+                localStateView.setImageResource(R.drawable.uploading_file_indicator);
+                localStateView.setVisibility(View.VISIBLE);
+            } else if (file.isDown()) {
+                localStateView.setImageResource(R.drawable.local_file_indicator);
+                localStateView.setVisibility(View.VISIBLE);
+            } else {
+                localStateView.setVisibility(View.INVISIBLE);
+            }
+            
+            TextView fileSizeV = (TextView) view.findViewById(R.id.file_size);
+            TextView lastModV = (TextView) view.findViewById(R.id.last_mod);
+            ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox);
+            
+                
+            fileSizeV.setVisibility(View.INVISIBLE);
+            lastModV.setVisibility(View.VISIBLE);
+            lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp()));
+            checkBoxV.setVisibility(View.GONE);
+            view.findViewById(R.id.imageView3).setVisibility(View.GONE);
+
+            if (checkIfFileIsSharedWithMe(file)) {
+                fileIcon.setImageResource(R.drawable.shared_with_me_folder);
+                sharedWithMeIconV.setVisibility(View.VISIBLE);
+            } else {
+                fileIcon.setImageResource(DisplayUtils.getResourceId(file.getMimetype(), file.getFileName()));
+            }
+
+            // If folder is sharedByLink, icon folder must be changed to
+            // folder-public one
+            if (file.isShareByLink()) {
+                fileIcon.setImageResource(R.drawable.folder_public);
+            }
+
+            if (file.isShareByLink()) {
+                sharedIconV.setVisibility(View.VISIBLE);
+            } else {
+                sharedIconV.setVisibility(View.GONE);
+            }
+        } 
+
+        return view;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return 1;
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return (mFolders == null || mFolders.isEmpty());
+    }
+
+    /**
+     * Change the adapted directory for a new one
+     * @param directory                 New file to adapt. Can be NULL, meaning "no content to adapt".
+     * @param updatedStorageManager     Optional updated storage manager; used to replace mStorageManager if is different (and not NULL)
+     */
+    public void swapDirectory(OCFile directory, FileDataStorageManager updatedStorageManager) {
+        mFile = directory;
+        if (updatedStorageManager != null && updatedStorageManager != mStorageManager) {
+            mStorageManager = updatedStorageManager;
+            mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
+        }
+        if (mStorageManager != null) {
+            // Only take into account the folders for after being listed
+            mFolders = getFolders(mStorageManager.getFolderContent(mFile));
+        } else {
+            mFolders = null;
+        }
+        notifyDataSetChanged();
+    }
+    
+    /**
+     * Filter for getting only the folders
+     * @param files
+     * @return Vector<OCFile>
+     */
+    public Vector<OCFile> getFolders(Vector<OCFile> files) {
+        Vector<OCFile> ret = new Vector<OCFile>(); 
+        OCFile current = null; 
+        for (int i=0; i<files.size(); i++) {
+            current = files.get(i);
+            if (current.isFolder()) {
+                ret.add(current);
+            }
+        }
+        return ret;
+    }
+    
+    /**
+     * Check if parent folder does not include 'S' permission and if file/folder
+     * is shared with me
+     * 
+     * @param file: OCFile
+     * @return boolean: True if it is shared with me and false if it is not
+     */
+    private boolean checkIfFileIsSharedWithMe(OCFile file) {
+        return (mFile.getPermissions() != null && !mFile.getPermissions().contains(PERMISSION_SHARED_WITH_ME)
+                && file.getPermissions() != null && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME));
+    }
+}

+ 319 - 0
src/com/owncloud/android/ui/fragment/MoveFileListFragment.java

@@ -0,0 +1,319 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2011  Bartek Przybylski
+ *   Copyright (C) 2012-2014 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.fragment;
+
+import java.io.File;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.activity.MoveActivity;
+import com.owncloud.android.ui.adapter.FolderListListAdapter;
+import com.owncloud.android.utils.Log_OC;
+
+/**
+ * A Fragment that lists all folders in a given path.
+ * 
+ * TODO refactorize to get rid of direct dependency on MoveActivity
+ * 
+ */
+public class MoveFileListFragment extends ExtendedListFragment {
+    
+    private static final String TAG = MoveFileListFragment.class.getSimpleName();
+
+    private static final String MY_PACKAGE = MoveFileListFragment.class.getPackage() != null ? MoveFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment";
+    private static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE";
+
+    private static final String KEY_INDEXES = "INDEXES";
+    private static final String KEY_FIRST_POSITIONS= "FIRST_POSITIONS";
+    private static final String KEY_TOPS = "TOPS";
+    private static final String KEY_HEIGHT_CELL = "HEIGHT_CELL";
+    private static final String KEY_EMPTY_LIST_MESSAGE = "EMPTY_LIST_MESSAGE";
+    
+    private FileFragment.ContainerActivity mContainerActivity;
+   
+    private OCFile mFile = null;
+    private FolderListListAdapter mAdapter;
+
+    // Save the state of the scroll in browsing
+    private ArrayList<Integer> mIndexes;
+    private ArrayList<Integer> mFirstPositions;
+    private ArrayList<Integer> mTops;
+
+    private int mHeightCell = 0;
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        Log_OC.e(TAG, "onAttach");
+        try {
+            mContainerActivity = (FileFragment.ContainerActivity) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement " + 
+                    FileFragment.ContainerActivity.class.getSimpleName());
+        }
+    }
+
+    
+    @Override
+    public void onDetach() {
+        mContainerActivity = null;
+        super.onDetach();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        Log_OC.e(TAG, "onActivityCreated() start");
+        
+        mAdapter = new FolderListListAdapter(getSherlockActivity(), mContainerActivity); 
+                
+        if (savedInstanceState != null) {
+            mFile = savedInstanceState.getParcelable(EXTRA_FILE);
+            mIndexes = savedInstanceState.getIntegerArrayList(KEY_INDEXES);
+            mFirstPositions = savedInstanceState.getIntegerArrayList(KEY_FIRST_POSITIONS);
+            mTops = savedInstanceState.getIntegerArrayList(KEY_TOPS);
+            mHeightCell = savedInstanceState.getInt(KEY_HEIGHT_CELL);
+            setMessageForEmptyList(savedInstanceState.getString(KEY_EMPTY_LIST_MESSAGE));
+            
+        } else {
+            mIndexes = new ArrayList<Integer>();
+            mFirstPositions = new ArrayList<Integer>();
+            mTops = new ArrayList<Integer>();
+            mHeightCell = 0;
+            
+        }
+        
+        mAdapter = new FolderListListAdapter(getSherlockActivity(), mContainerActivity);
+        
+        setListAdapter(mAdapter);
+        
+        registerForContextMenu(getListView());
+        getListView().setOnCreateContextMenuListener(this);
+  }
+    
+    /**
+     * Saves the current listed folder.
+     */
+    @Override
+    public void onSaveInstanceState (Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(EXTRA_FILE, mFile);
+        outState.putIntegerArrayList(KEY_INDEXES, mIndexes);
+        outState.putIntegerArrayList(KEY_FIRST_POSITIONS, mFirstPositions);
+        outState.putIntegerArrayList(KEY_TOPS, mTops);
+        outState.putInt(KEY_HEIGHT_CELL, mHeightCell);
+        outState.putString(KEY_EMPTY_LIST_MESSAGE, getEmptyViewText());
+    }
+    
+    /**
+     * Call this, when the user presses the up button.
+     * 
+     * Tries to move up the current folder one level. If the parent folder was removed from the database, 
+     * it continues browsing up until finding an existing folders.
+     * 
+     * return       Count of folder levels browsed up.
+     */
+    public int onBrowseUp() {
+        OCFile parentDir = null;
+        int moveCount = 0;
+        
+        if(mFile != null){
+            FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
+            
+            String parentPath = null;
+            if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
+                parentPath = new File(mFile.getRemotePath()).getParent();
+                parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
+                parentDir = storageManager.getFileByPath(parentPath);
+                moveCount++;
+            } else {
+                parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH);    // never returns null; keep the path in root folder
+            }
+            while (parentDir == null) {
+                parentPath = new File(parentPath).getParent();
+                parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
+                parentDir = storageManager.getFileByPath(parentPath);
+                moveCount++;
+            }   // exit is granted because storageManager.getFileByPath("/") never returns null
+            mFile = parentDir;           
+        }
+        
+        if (mFile != null) {
+            listDirectory(mFile);
+
+            ((MoveActivity)mContainerActivity).startSyncFolderOperation(mFile);
+            
+            // restore index and top position
+            restoreIndexAndTopPosition();
+            
+        }   // else - should never happen now
+   
+        return moveCount;
+    }
+    
+    /*
+     * Restore index and position
+     */
+    private void restoreIndexAndTopPosition() {
+        if (mIndexes.size() > 0) {  
+            // needs to be checked; not every browse-up had a browse-down before 
+            
+            int index = mIndexes.remove(mIndexes.size() - 1);
+            
+            int firstPosition = mFirstPositions.remove(mFirstPositions.size() -1);
+            
+            int top = mTops.remove(mTops.size() - 1);
+            
+            mList.setSelectionFromTop(firstPosition, top);
+            
+            // Move the scroll if the selection is not visible
+            int indexPosition = mHeightCell*index;
+            int height = mList.getHeight();
+            
+            if (indexPosition > height) {
+                if (android.os.Build.VERSION.SDK_INT >= 11)
+                {
+                    mList.smoothScrollToPosition(index); 
+                }
+                else if (android.os.Build.VERSION.SDK_INT >= 8)
+                {
+                    mList.setSelectionFromTop(index, 0);
+                }
+                
+            }
+        }
+    }
+    
+    /*
+     * Save index and top position
+     */
+    private void saveIndexAndTopPosition(int index) {
+        
+        mIndexes.add(index);
+        
+        int firstPosition = mList.getFirstVisiblePosition();
+        mFirstPositions.add(firstPosition);
+        
+        View view = mList.getChildAt(0);
+        int top = (view == null) ? 0 : view.getTop() ;
+
+        mTops.add(top);
+        
+        // Save the height of a cell
+        mHeightCell = (view == null || mHeightCell != 0) ? mHeightCell : view.getHeight();
+    }
+    
+    @Override
+    public void onItemClick(AdapterView<?> l, View v, int position, long id) {
+        OCFile file = (OCFile) mAdapter.getItem(position);
+        if (file != null) {
+            if (file.isFolder()) { 
+                // update state and view of this fragment
+                listDirectory(file);
+                // then, notify parent activity to let it update its state and view, and other fragments
+                mContainerActivity.onBrowsedDownTo(file);
+                // save index and top position
+                saveIndexAndTopPosition(position);
+                
+            } 
+            
+        } else {
+            Log_OC.d(TAG, "Null object in ListAdapter!!");
+        }
+        
+    }
+
+    /**
+     * Use this to query the {@link OCFile} that is currently
+     * being displayed by this fragment
+     * @return The currently viewed OCFile
+     */
+    public OCFile getCurrentFile(){
+        return mFile;
+    }
+    
+    /**
+     * Calls {@link MoveFileListFragment#listDirectory(OCFile)} with a null parameter
+     */
+    public void listDirectory(){
+        listDirectory(null);
+    }
+    
+    /**
+     * Lists the given directory on the view. When the input parameter is null,
+     * it will either refresh the last known directory. list the root
+     * if there never was a directory.
+     * 
+     * @param directory File to be listed
+     */
+    public void listDirectory(OCFile directory) {
+        FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
+        if (storageManager != null) {
+
+            // Check input parameters for null
+            if(directory == null){
+                if(mFile != null){
+                    directory = mFile;
+                } else {
+                    directory = storageManager.getFileByPath("/");
+                    if (directory == null) return; // no files, wait for sync
+                }
+            }
+        
+        
+            // If that's not a directory -> List its parent
+            if(!directory.isFolder()){
+                Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
+                directory = storageManager.getFileById(directory.getParentId());
+            }
+
+            mAdapter.swapDirectory(directory, storageManager);
+            if (mFile == null || !mFile.equals(directory)) {
+                mList.setSelectionFromTop(0, 0);
+            }
+            mFile = directory;
+        }
+    }
+
+
+    @Override
+    public void onRefresh() {
+        super.onRefresh();
+        
+        if (mFile != null) {
+            // Refresh mFile
+            mFile = mContainerActivity.getStorageManager().getFileById(mFile.getFileId());
+
+            listDirectory(mFile);
+            
+            ((MoveActivity)mContainerActivity).startSyncFolderOperation(mFile);
+        }
+    }
+}

+ 8 - 0
src/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -21,6 +21,7 @@ import java.io.File;
 import java.util.ArrayList;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 import android.view.ContextMenu;
 import android.view.MenuInflater;
@@ -34,6 +35,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.FileMenuFilter;
 import com.owncloud.android.ui.activity.FileDisplayActivity;
+import com.owncloud.android.ui.activity.MoveActivity;
 import com.owncloud.android.ui.adapter.FileListListAdapter;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
@@ -378,6 +380,12 @@ public class OCFileListFragment extends ExtendedListFragment {
                 }
                 return true;
             }
+            case R.id.action_move: {
+                Intent i = new Intent(getActivity(), MoveActivity.class);
+                startActivity(i);
+
+                return true;
+            }
             default:
                 return super.onContextItemSelected(item); 
         }