/*
 * Copyright (C) 2016 Davide Imbriaco
 *
 * This Java file is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package it.anyplace.syncbrowser;

import android.Manifest;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.eventbus.Subscribe;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import com.nononsenseapps.filepicker.FilePickerActivity;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.w3c.dom.Text;

import java.io.File;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

import it.anyplace.sync.bep.BlockPuller;
import it.anyplace.sync.bep.BlockPusher;
import it.anyplace.sync.bep.FolderBrowser;
import it.anyplace.sync.bep.IndexBrowser;
import it.anyplace.sync.bep.IndexFinder;
import it.anyplace.sync.bep.IndexHandler;
import it.anyplace.sync.client.SyncthingClient;
import it.anyplace.sync.core.beans.DeviceInfo;
import it.anyplace.sync.core.beans.DeviceStats;
import it.anyplace.sync.core.beans.FileInfo;
import it.anyplace.sync.core.beans.FolderInfo;
import it.anyplace.sync.core.beans.FolderStats;
import it.anyplace.sync.core.beans.IndexInfo;
import it.anyplace.sync.core.configuration.ConfigurationService;
import it.anyplace.sync.core.security.KeystoreHandler;
import it.anyplace.sync.core.utils.FileInfoOrdering;
import it.anyplace.sync.core.utils.PathUtils;
import it.anyplace.syncbrowser.filepicker.MIVFilePickerActivity;

import static com.google.common.base.Objects.equal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.nullToEmpty;
import static it.anyplace.syncbrowser.utils.ViewUtils.listViews;
import static org.apache.commons.io.FileUtils.getFile;
import static org.apache.commons.lang3.StringUtils.capitalize;
import static org.apache.commons.lang3.StringUtils.isBlank;

//TODO move interface code to fragment
public class MainActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback {

    private ConfigurationService configuration;
    private SyncthingClient syncthingClient;
    private Exception statupError;

    private void closeClient() {
        if (indexBrowser != null) {
            indexBrowser.close();
            indexBrowser = null;
        }
        if (folderBrowser != null) {
            folderBrowser.close();
            folderBrowser = null;
        }
        if (syncthingClient != null) {
            syncthingClient.close();
            syncthingClient = null;
        }
    }

    private void initClient() {
        closeClient();
        try {
            configuration = ConfigurationService.newLoader()
                    .setCache(new File(getExternalCacheDir(), "cache"))
                    .setDatabase(new File(getExternalFilesDir(null), "database"))
                    .loadFrom(new File(getExternalFilesDir(null), "config.properties"));
            configuration.edit().setDeviceName(getDeviceName());
            FileUtils.cleanDirectory(configuration.getTemp());
            KeystoreHandler keystoreHandler = KeystoreHandler.newLoader().loadAndStore(configuration);
            configuration.edit().persistLater();
            Log.i("initClient", "loaded configuration = " + configuration.newWriter().dumpToString());
            Log.i("initClient", "storage space = " + configuration.getStorageInfo().dumpAvailableSpace());
            syncthingClient = new SyncthingClient(configuration);
            syncthingClient.getIndexHandler().getEventBus().register(new Object() {

                @Subscribe
                public void handleIndexRecordAquiredEvent(IndexHandler.IndexRecordAquiredEvent event) {
                    final String label = syncthingClient.getIndexHandler().getFolderInfo(event.getFolder()).getLabel();
                    final IndexInfo indexInfo = event.getIndexInfo();
                    final long count = event.getNewRecords().size();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Log.i("handleIndexRecordEvent", "trigger folder list update from index record acquired");
                            ((TextView) findViewById(R.id.main_index_progress_bar_label)).setText("index update, folder "
                                    + label + " " + ((int) (indexInfo.getCompleted() * 100)) + "% synchronized");
                            if(indexBrowser==null  || event.getAffectedPaths().contains(indexBrowser.getCurrentPath())) {
                                updateFolderListView();
                            }
                        }
                    });
                }

                @Subscribe
                public void handleRemoteIndexAquiredEvent(IndexHandler.FullIndexAquiredEvent event) {

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Log.i("handleIndexAquiredEvent", "trigger folder list update from index acquired");
                            findViewById(R.id.main_index_progress_bar).setVisibility(View.GONE);
                            updateFolderListView();
                        }
                    });
                }
            });
            //TODO listen for device events, update device list
            folderBrowser = syncthingClient.getIndexHandler().newFolderBrowser();
            statupError = null;
        } catch (Exception ex) {
            Log.e("Main", "error", ex);
            statupError = ex;
            closeClient();
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        Log.i("MainActivity", "onRequestPermissionsResult: "+Joiner.on(",").join(permissions)+" -> "+Joiner.on(",").join(Arrays.asList(grantResults)));
        Intent intent = getBaseContext().getPackageManager().getLaunchIntentForPackage( getBaseContext().getPackageName() );
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        Log.i("MainActivity","onRequestPermissionsResult: restart app for new permissions");
        finish();
        startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.i("onCreate", "BEGIN");
        super.onCreate(savedInstanceState);


        {
            Log.i("MainActivity", "check permissions BEGIN");
            List<String> requests = Lists.newArrayList();
            for (String requiredPermission : Arrays.asList(
                    Manifest.permission.INTERNET,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                int permissionCheck = ContextCompat.checkSelfPermission(this, requiredPermission);
                if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
                    Log.i("MainActivity.onCreate", "app is missing permission " + requiredPermission + ", sending request");
                    requests.add(requiredPermission);
                }
            }
            if (!requests.isEmpty()) {
                ActivityCompat.requestPermissions(MainActivity.this, requests.toArray(new String[]{}), 13);
            }
            Log.i("MainActivity", "check permissions END");
        }

        setContentView(R.layout.main_container);

        ((ListView) findViewById(R.id.main_folder_and_files_list_view)).setEmptyView(findViewById(R.id.main_list_view_empty_element));

        checkPermissions();

        ((View) findViewById(R.id.main_header_sort_order_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                toggleFileSort();
            }
        });
        ((View) findViewById(R.id.main_header_show_menu_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ((DrawerLayout) findViewById(R.id.main_drawer_layout)).openDrawer(Gravity.LEFT);
            }
        });
        ((View) findViewById(R.id.main_header_show_devices_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ((DrawerLayout) findViewById(R.id.main_drawer_layout)).openDrawer(Gravity.RIGHT);
            }
        });
        ((View) findViewById(R.id.main_menu_exit_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ((DrawerLayout) findViewById(R.id.main_drawer_layout)).closeDrawer(Gravity.LEFT);
            }
        });
        ((View) findViewById(R.id.main_menu_add_device_qrcode_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                openQrcode();
                ((DrawerLayout) findViewById(R.id.main_drawer_layout)).closeDrawer(Gravity.LEFT);
            }
        });
        ((View) findViewById(R.id.devices_list_view_add_device_here_qrcode_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                openQrcode();
            }
        });
        ((View) findViewById(R.id.main_menu_cleanup_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("clear cache and index")
                        .setMessage("clear all cache data and index data?")
                        .setIcon(android.R.drawable.ic_dialog_alert)
                        .setPositiveButton("yes", new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                cleanCacheAndIndex();
                            }
                        })
                        .setNegativeButton("no", null)
                        .show();
                ((DrawerLayout) findViewById(R.id.main_drawer_layout)).closeDrawer(Gravity.LEFT);
            }
        });
        ((View) findViewById(R.id.main_menu_update_index_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                updateIndexFromRemote();
                ((DrawerLayout) findViewById(R.id.main_drawer_layout)).closeDrawer(Gravity.LEFT);
            }
        });
        ((View) findViewById(R.id.main_list_view_upload_here_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showUploadHereDialog();
            }
        });
        ((View) findViewById(R.id.main_header_search_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                enterSearchMode();
            }
        });
        ((View) findViewById(R.id.main_search_bar_close_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                exitSearchMode();
            }
        });
        ((EditText) findViewById(R.id.main_search_bar_input_field)).addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                final String text=editable.toString();
                if(indexFinder!=null && !StringUtils.isBlank(text) && text.trim().length()>2) {
                    new AsyncTask<Void,Void,Void>(){

                        @Override
                        protected Void doInBackground(Void... voids) {
                            try {
                                Thread.sleep(750);
                            } catch (InterruptedException e) {
                            }
                            return null;
                        }
                        @Override
                        protected void onPostExecute(Void aVoid) {
                            String newText=((EditText) findViewById(R.id.main_search_bar_input_field)).getText().toString();
                            if(equal(text,newText)) {
                                findViewById(R.id.main_search_progress_bar).setVisibility(View.VISIBLE);
                                indexFinder.submitSearch(text);
                            }
                        }
                    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

                }
            }
        });
        ((DrawerLayout) findViewById(R.id.main_drawer_layout)).addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
            @Override
            public void onDrawerOpened(View drawerView) {
                if (drawerView.getId() == R.id.devices_right_drawer) {
                    updateDeviceList();
                }
            }

            @Override
            public void onDrawerStateChanged(int newState) {
                if (newState == DrawerLayout.STATE_DRAGGING) {
                    if (!((DrawerLayout) findViewById(R.id.main_drawer_layout)).isDrawerOpen(Gravity.RIGHT)) {
                         updateDeviceList();
                    }
                }
            }
        });


        new AsyncTask<Void, Void, Void>() {


            @Override
            protected void onPreExecute() {
                updateMainProgressBar(true,"loading config, starting syncthing client");
            }

            @Override
            protected Void doInBackground(Void... voidd) {
                initClient();
                return null;
            }

            @Override
            protected void onPostExecute(Void voidd) {
                updateMainProgressBar(false,null);
                if (syncthingClient == null) {
                    Toast.makeText(MainActivity.this, "error starting syncthing client: " + statupError, Toast.LENGTH_LONG).show();
                    MainActivity.this.finish();
                } else {
                    restoreBrowserFolderFromPref();
                    Date lastUpdate = getLastIndexUpdateFromPref();
                    if (lastUpdate == null || new Date().getTime() - lastUpdate.getTime() > 10 * 60 * 1000) { //trigger update if last was more than 10mins ago
                        Log.d("onCreate", "trigger index update, last was " + lastUpdate);
                        updateIndexFromRemote();
                    }
                    initDeviceList();
                }
            }
        }.execute();

        Log.i("onCreate", "app ready, scanning intent");
        Intent intent = getIntent();
        if (equal(Intent.ACTION_SEND, intent.getAction())) {
            handleSendSingle(intent);
        } else if (equal(Intent.ACTION_SEND_MULTIPLE, intent.getAction())) {
            handleSendMany(intent);
        }
        Log.i("onCreate", "END");
    }

    private boolean checkPermissions() {
        List<String> missingPermissions = Lists.newArrayList();
        for (String permission : Arrays.asList(Manifest.permission.INTERNET, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            int permissionCheck = ContextCompat.checkSelfPermission(this, permission);
            if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
                missingPermissions.add(permission);
            }
        }
        if (!missingPermissions.isEmpty()) {
            ActivityCompat.requestPermissions(this, missingPermissions.toArray(new String[]{}), 0);
            return false;
        } else {
            return true;
        }
        //TODO handle response
    }

    private void showUploadHereDialog() {
        if (indexBrowser == null) {
            Log.w("showUploadHereDialog", "unable to open dialog, null index browser");
        } else {
            Intent i = new Intent(this, MIVFilePickerActivity.class);
            String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
            Log.i("showUploadHereDialog", "path = " + path);
            i.putExtra(FilePickerActivity.EXTRA_START_PATH, path);
            startActivityForResult(i, 0);
        }
    }

    private void cleanCacheAndIndex() {
        if (syncthingClient != null) {
            syncthingClient.clearCacheAndIndex();
            recreate();
        }
    }

    private void handleSendSingle(Intent intent) {
        Uri fileUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
        if (fileUri != null) {
            handleSend(Collections.singletonList(fileUri));
        }
    }

    private void handleSendMany(Intent intent) {
        List<Uri> fileUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
        if (fileUris != null && !fileUris.isEmpty()) {
            handleSend(fileUris);
        }
    }

    private void updateButtonsVisibility() {
        if(searchModeOn){
            findViewById(R.id.main_header_sort_order_button).setVisibility(View.VISIBLE);
            findViewById(R.id.file_upload_intent_footer).setVisibility(View.GONE);
            findViewById(R.id.main_folder_and_files_list_view).setVisibility(View.GONE);
            findViewById(R.id.main_list_view_upload_here_button).setVisibility(View.GONE);
            findViewById(R.id.main_search_results_list_view).setVisibility(View.VISIBLE);
            findViewById(R.id.main_search_bar).setVisibility(View.VISIBLE);
        } else {
            findViewById(R.id.main_folder_and_files_list_view).setVisibility(View.VISIBLE);
            findViewById(R.id.main_search_results_list_view).setVisibility(View.GONE);
            findViewById(R.id.main_search_bar).setVisibility(View.GONE);
            if (isHandlingUploadIntent) {
                findViewById(R.id.main_header_search_button).setVisibility(View.GONE);
                findViewById(R.id.main_list_view_upload_here_button).setVisibility(View.GONE);
                findViewById(R.id.file_upload_intent_footer).setVisibility(View.VISIBLE);//todo set button disabled if not in folder
                if (isBrowsingFolder) {
                    ((TextView) findViewById(R.id.file_upload_intent_footer_confirm_button)).setEnabled(true);
                } else {
                    ((TextView) findViewById(R.id.file_upload_intent_footer_confirm_button)).setEnabled(false);
                }
            } else {
                findViewById(R.id.main_header_sort_order_button).setVisibility(View.VISIBLE);
                if (isBrowsingFolder) {
                    findViewById(R.id.main_list_view_upload_here_button).setVisibility(View.VISIBLE);
                } else {
                    findViewById(R.id.main_list_view_upload_here_button).setVisibility(View.GONE);
                }
                findViewById(R.id.file_upload_intent_footer).setVisibility(View.GONE);
            }
            if (isBrowsingFolder) {
                findViewById(R.id.main_header_search_button).setVisibility(View.VISIBLE);
            } else {
                findViewById(R.id.main_header_sort_order_button).setVisibility(View.GONE);
            }
        }
    }

    private IndexFinder indexFinder;
    private IndexFinder.SearchCompletedEvent searchCompletedEvent;

    private void enterSearchMode(){
        Log.i("Main","enterSearchMode");
        searchModeOn=true;
        searchCompletedEvent=null;
        indexFinder=syncthingClient.getIndexHandler().newIndexFinderBuilder().build();
        indexFinder.setOrdering(fileInfoOrdering);
        indexFinder.getEventBus().register(new Object(){
            @Subscribe
            public void handleSearchCompletedEvent(IndexFinder.SearchCompletedEvent event){
                runOnUiThread(new Runnable(){
                    public void run(){
                        String term = ((EditText) findViewById(R.id.main_search_bar_input_field)).getText().toString();
                        if(equal(event.getQuery(),term)){
                            searchCompletedEvent=event;
                            updateSearchResultListView();
                            findViewById(R.id.main_search_progress_bar).setVisibility(View.GONE);
                        }
                    }
                });
            }
        });

        ListView listView = (ListView) findViewById(R.id.main_search_results_list_view);
        listView.setEmptyView(findViewById(R.id.main_search_results_empty_element));
        ArrayAdapter adapter = createFileInfoArrayAdapter();
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                FileInfo fileInfo = (FileInfo) listView.getItemAtPosition(position);
                if(fileInfo.isDirectory()){
                    exitSearchMode();
                    showFolderListView(fileInfo.getFolder(),fileInfo.getPath());
                }else if(fileInfo.isFile()){
                    pullFile(fileInfo);
                }
            }
        });

        updateButtonsVisibility();
        updateSearchResultListView();
    }

    private void exitSearchMode(){
        Log.i("Main","exitSearchMode");
        searchModeOn=false;
        ListView listView = (ListView) findViewById(R.id.main_search_results_list_view);
        listView.setEmptyView(null);
        ((TextView)findViewById(R.id.main_search_results_empty_element)).setVisibility(View.GONE);
        listView.setAdapter(null);
        updateButtonsVisibility();
        indexFinder.close();
        indexFinder=null;
        searchCompletedEvent=null;
    }

    private void updateSearchResultListView(){
        ListView listView = (ListView) findViewById(R.id.main_search_results_list_view);
        ArrayAdapter<FileInfo> arrayAdapter=((ArrayAdapter)listView.getAdapter());
        arrayAdapter.clear();
        if(searchCompletedEvent==null || searchCompletedEvent.hasZeroResults()){
            Log.i("Main", "updateSearchResultListView, no result");
            ((TextView)findViewById(R.id.main_search_results_empty_element)).setText(R.string.no_search_result_message);
        }else if(searchCompletedEvent.hasTooManyResults()) {
            Log.i("Main", "updateSearchResultListView, too many results");
            ((TextView)findViewById(R.id.main_search_results_empty_element)).setText(R.string.too_many_search_results_message);
        }else{
            List<FileInfo> list = searchCompletedEvent.getResultList();
            Log.i("Main", "updateSearchResultListView, result count = " + list.size());
            arrayAdapter.addAll(list);
        }
        arrayAdapter.notifyDataSetChanged();
        listView.setSelection(0);
    }

    private List<Uri> filesToUpload;

    private void handleSend(List<Uri> list) {
        Log.i("Main", "handle send of files = " + list);
        isHandlingUploadIntent = true;
        filesToUpload = list;
        updateButtonsVisibility();
        ((TextView) findViewById(R.id.file_upload_intent_footer_label)).setText(Joiner.on(", ").join(Iterables.transform(list, new Function<Uri, String>() {

            @Override
            public String apply(Uri input) {
                return getContentFileName(input);
            }
        })));
        ((TextView) findViewById(R.id.file_upload_intent_footer_confirm_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (indexBrowser != null) {
                    doUpload(indexBrowser.getFolder(), indexBrowser.getCurrentPath(), filesToUpload);
                    filesToUpload = null;
                    isHandlingUploadIntent = false;
                    updateButtonsVisibility();
                } else {
                    Toast.makeText(MainActivity.this, "choose a folder for upload", Toast.LENGTH_SHORT).show();
                }
            }
        });
        ((TextView) findViewById(R.id.file_upload_intent_footer_cancel_button)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                filesToUpload = null;
                isHandlingUploadIntent = false;
                updateButtonsVisibility();
                Toast.makeText(MainActivity.this, "file upload cancelled", Toast.LENGTH_SHORT).show();
            }
        });
    }

    private String getContentFileName(Uri contentUri) {
        String fileName = new File(contentUri.getLastPathSegment()).getName();
        if (equal(contentUri.getScheme(), "content")) {
            try (Cursor cursor = MainActivity.this.getContentResolver().query(contentUri, new String[]{MediaStore.Images.Media.DATA}, null, null, null)) {
                int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                cursor.moveToFirst();
                String path = cursor.getString(column_index);
                Log.d("Main", "recovered 'content' uri real path = " + path);
                fileName = new File(Uri.parse(path).getLastPathSegment()).getName();
            }catch(Exception ex) {
                Log.w("getContentFileName", "unable to get content _data from uri = " + contentUri, ex);
            }
        }
        return fileName;
    }

    private void doUpload(final String folder, final String dir, final List<Uri> filesToUpload) {
        if (!filesToUpload.isEmpty()) {
            new AsyncTask<Void, BlockPusher.FileUploadObserver, Exception>() {
                private ProgressDialog progressDialog;
                private Thread thread;
                private boolean cancelled = false;
                private Uri fileToUpload = filesToUpload.iterator().next();
                private List<Uri> nextFilesToUpload = filesToUpload.subList(1, filesToUpload.size());
                private String fileName = getContentFileName(fileToUpload);
                private String path = PathUtils.buildPath(dir, fileName);

                @Override
                protected void onPreExecute() {
                    Log.i("doUpload", "upload of file " + fileName + " to folder " + folder + ":" + dir);
                    progressDialog = new ProgressDialog(MainActivity.this);
                    progressDialog.setMessage("uploading file " + fileName);
                    progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                    progressDialog.setCancelable(true);
                    progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                        @Override
                        public void onCancel(DialogInterface dialogInterface) {
                            cancelled = true;
                            if (thread != null) {
                                thread.interrupt();
                            }
                            Toast.makeText(MainActivity.this, "upload aborted by user", Toast.LENGTH_SHORT).show();
                        }
                    });
                    progressDialog.setIndeterminate(true);
                    progressDialog.show();
                }

                @Override
                protected Exception doInBackground(Void... voidd) {
                    try {
                        try (BlockPusher.FileUploadObserver observer = syncthingClient.pushFile(getContentResolver().openInputStream(fileToUpload), folder, path)) {
                            Log.i("doUpload", "pushing file " + fileName + " to folder " + folder + ":" + dir);
                            publishProgress(observer);
                            while (!observer.isCompleted() && !cancelled) {
                                observer.waitForProgressUpdate();
                                Log.i("Main", "upload progress = " + observer.getProgressMessage());
                                publishProgress(observer);
                            }
                            if (cancelled) {
                                return null;
                            }
                            Log.i("Main", "uploaded file = " + path);
                            return null;
                        }
                    } catch (Exception ex) {
                        if (cancelled) {
                            return null;
                        }
                        Log.e("Main", "file upload exception", ex);
                        return ex;
                    }
                }

                @Override
                protected void onProgressUpdate(BlockPusher.FileUploadObserver... observer) {
                    if (observer[0].getProgress() > 0) {
                        progressDialog.setIndeterminate(false);
                        progressDialog.setMax((int) observer[0].getDataSource().getSize());
                        progressDialog.setProgress((int) (observer[0].getProgress() * observer[0].getDataSource().getSize()));
                    }
                }

                @Override
                protected void onPostExecute(Exception res) {
                    progressDialog.dismiss();
                    if (cancelled) {
                        // do nothing
                    } else if (res != null) {
                        Toast.makeText(MainActivity.this, "error uploading file: " + res, Toast.LENGTH_LONG).show();
                    } else {
                        Log.i("doUpload", "uploaded file " + fileName + " to folder " + folder + ":" + dir);
                        Toast.makeText(MainActivity.this, "uploaded file: " + fileName, Toast.LENGTH_SHORT).show();
                        updateFolderListView();
                        if (!nextFilesToUpload.isEmpty()) {
                            doUpload(folder, dir, nextFilesToUpload);
                        }
                    }
                }
            }.execute();
        }
    }

    private FolderBrowser folderBrowser;
    private IndexBrowser indexBrowser;
    private boolean isBrowsingFolder = false, isHandlingUploadIntent = false, indexUpdateInProgress = false, searchModeOn=false;

    private final static String CURRENT_FOLDER_PREF = "CURRENT_FOLDER";

    private void saveCurrentFolder() {
        Log.d("saveCurrentFolder", "saveCurrentFolder");
        if (isBrowsingFolder) {
            getPreferences(MODE_PRIVATE).edit()
                    .putString(CURRENT_FOLDER_PREF, new Gson().toJson(Arrays.asList(indexBrowser.getFolder(), indexBrowser.getCurrentPath())))
                    .apply();
        } else {
            getPreferences(MODE_PRIVATE).edit().remove(CURRENT_FOLDER_PREF).apply();
        }
    }

    private void restoreBrowserFolderFromPref() {
        Log.d("restoreBrowserFolder...", "restoreBrowserFolderFromPref");
        String value = getPreferences(MODE_PRIVATE).getString(CURRENT_FOLDER_PREF, null);
        if (isBlank(value)) {
            showAllFoldersListView();
        } else {
            try {
                List<String> list = new Gson().fromJson(value, new TypeToken<List<String>>() {
                }.getType());
                checkArgument(list.size() == 2);
                showFolderListView(list.get(0), list.get(1));
            } catch (Exception ex) {
                Log.e("restoreBrowserFolder...", "error restoring browser folder from preferences", ex);
                showAllFoldersListView();
            }
        }
    }

    private void showAllFoldersListView() {
        Log.d("Main", "showAllFoldersListView BEGIN");
        if (indexBrowser != null) {
            indexBrowser.close();
            indexBrowser = null;
        }
        ListView listView = (ListView) findViewById(R.id.main_folder_and_files_list_view);
        List<Pair<FolderInfo, FolderStats>> list = Lists.newArrayList(folderBrowser.getFolderInfoAndStatsList());
        Collections.sort(list, Ordering.natural().onResultOf(new Function<Pair<FolderInfo, FolderStats>, String>() {
            @Override
            public String apply(Pair<FolderInfo, FolderStats> input) {
                return input.getLeft().getLabel();
            }
        }));
        Log.i("Main", "list folders = " + list + " (" + list.size() + " records");
        ArrayAdapter adapter = new ArrayAdapter<Pair<FolderInfo, FolderStats>>(this, R.layout.listview_folder, list) {
            @NonNull
            @Override
            public View getView(int position, View v, ViewGroup parent) {
                if (v == null) {
                    v = LayoutInflater.from(getContext()).inflate(R.layout.listview_folder, null);
                }
                FolderInfo folderInfo = getItem(position).getLeft();
                FolderStats folderStats = getItem(position).getRight();
                ((TextView) v.findViewById(R.id.folder_name)).setText(folderInfo.getLabel() + " (" + folderInfo.getFolder() + ")");
                ((TextView) v.findViewById(R.id.folder_lastmod_info)).setText(folderStats.getLastUpdate() == null ? "last modified: unknown" : ("last modified: " + DateUtils.getRelativeDateTimeString(MainActivity.this,folderStats.getLastUpdate().getTime(),DateUtils.MINUTE_IN_MILLIS,DateUtils.WEEK_IN_MILLIS,0)));
                ((TextView) v.findViewById(R.id.folder_content_info)).setText(folderStats.describeSize() + ", " + folderStats.getFileCount() + " files, " + folderStats.getDirCount() + " dirs");
                return v;
            }

        };
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                String folder = ((Pair<FolderInfo, FolderStats>) listView.getItemAtPosition(position)).getLeft().getFolder();
                showFolderListView(folder, null);
            }
        });
        isBrowsingFolder = false;
        updateButtonsVisibility();
        saveCurrentFolder();
        ((TextView) findViewById(R.id.main_header_folder_label)).setText(R.string.app_name);
        Log.d("Main", "showAllFoldersListView END");
    }

    private ArrayAdapter<FileInfo> createFileInfoArrayAdapter(){
        return new ArrayAdapter<FileInfo>(this, R.layout.listview_file, Lists.newArrayList()) {
            @NonNull
            @Override
            public View getView(int position, View v, ViewGroup parent) {
                if (v == null) {
                    v = LayoutInflater.from(getContext()).inflate(R.layout.listview_file, null);
                }
                FileInfo fileInfo = getItem(position);
                ((TextView) v.findViewById(R.id.file_label)).setText(fileInfo.getFileName());
                if (fileInfo.isDirectory()) {
                    ((TextView) v.findViewById(R.id.file_icon)).setText(R.string.icon_folder_o);
                    ((TextView) v.findViewById(R.id.file_size)).setVisibility(View.GONE);
                } else {
                    ((TextView) v.findViewById(R.id.file_icon)).setText(R.string.icon_file_o);
                    ((TextView) v.findViewById(R.id.file_size)).setVisibility(View.VISIBLE);
                    ((TextView) v.findViewById(R.id.file_size)).setText(FileUtils.byteCountToDisplaySize(fileInfo.getSize())
                            +" - last modified "
                            + DateUtils.getRelativeDateTimeString(MainActivity.this,fileInfo.getLastModified().getTime(),DateUtils.MINUTE_IN_MILLIS,DateUtils.WEEK_IN_MILLIS,0));
                }
                return v;
            }
        };
    }

    private void showFolderListView(String folder, @Nullable String previousPath) {
        Log.d("showFolderListView", "showFolderListView BEGIN");
        if (indexBrowser != null && equal(folder, indexBrowser.getFolder())) {
            Log.d("showFolderListView", "reuse current index browser");
            indexBrowser.navigateToNearestPath(previousPath);
        } else {
            if (indexBrowser != null) {
                indexBrowser.close();
            }
            Log.d("showFolderListView", "open new index browser");
            indexBrowser = syncthingClient.getIndexHandler()
                    .newIndexBrowserBuilder()
                    .setOrdering(fileInfoOrdering)
                    .includeParentInList(true).allowParentInRoot(true)
                    .setFolder(folder)
                    .buildToNearestPath(previousPath);
        }
        ListView listView = (ListView) findViewById(R.id.main_folder_and_files_list_view);
        ArrayAdapter adapter = createFileInfoArrayAdapter();
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                FileInfo fileInfo = (FileInfo) listView.getItemAtPosition(position);
                Log.d("showFolderListView", "navigate to path = '" + fileInfo.getPath() + "' from path = '" + indexBrowser.getCurrentPath() + "'");
                navigateToFolder(fileInfo);
            }
        });
        isBrowsingFolder = true;
        navigateToFolder(indexBrowser.getCurrentPathInfo());
        updateButtonsVisibility();
        Log.d("showFolderListView", "showFolderListView END");
    }

    private void updateMainProgressBar(boolean visible, String message){
        findViewById(R.id.main_progress_bar_container).setVisibility(visible?View.VISIBLE:View.GONE);
        ((TextView) findViewById(R.id.main_progress_bar_label)).setText(nullToEmpty(message));
    }

    private void navigateToFolder(FileInfo fileInfo) {
        Log.d("navigateToFolder", "BEGIN");
        if (indexBrowser.isRoot() && PathUtils.isParent(fileInfo.getPath())) {
            showAllFoldersListView(); //navigate back to folder list
        } else {
            if (fileInfo.isDirectory()) {
                indexBrowser.navigateTo(fileInfo);
                FileInfo newFileInfo=PathUtils.isParent(fileInfo.getPath())?indexBrowser.getCurrentPathInfo():fileInfo;
                if (!indexBrowser.isCacheReadyAfterALittleWait()) {
                    Log.d("navigateToFolder", "load folder cache bg");
                    new AsyncTask<Void, Void, Void>() {
                        @Override
                        protected void onPreExecute() {
                            updateMainProgressBar(true,"open directory: " + (indexBrowser.isRoot() ? folderBrowser.getFolderInfo(indexBrowser.getFolder()).getLabel() : indexBrowser.getCurrentPathFileName()));
                        }

                        @Override
                        protected Void doInBackground(Void... voids) {
                            indexBrowser.waitForCacheReady();
                            return null;
                        }

                        @Override
                        protected void onPostExecute(Void aVoid) {
                            Log.d("navigateToFolder", "cache ready, navigate to folder");
                            updateMainProgressBar(false,null);
                            navigateToFolder(newFileInfo);
                        }
                    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
                } else {
                    List<FileInfo> list = indexBrowser.listFiles();
                    Log.i("navigateToFolder", "list for path = '" + indexBrowser.getCurrentPath() + "' list = " + list.size() + " records");
                    Log.d("navigateToFolder", "list for path = '" + indexBrowser.getCurrentPath() + "' list = " + list);
                    checkArgument(!list.isEmpty());//list must contain at least the 'parent' path
                    ListView listView = (ListView) findViewById(R.id.main_folder_and_files_list_view);
                    ArrayAdapter adapter = (ArrayAdapter) listView.getAdapter();
                    adapter.clear();
                    adapter.addAll(list);
                    adapter.notifyDataSetChanged();
                    listView.setSelection(0);
                    saveCurrentFolder();
                    ((TextView) findViewById(R.id.main_header_folder_label)).setText(indexBrowser.isRoot()
                            ?folderBrowser.getFolderInfo(indexBrowser.getFolder()).getLabel()
                            :newFileInfo.getFileName());
                }
            } else {
                pullFile(fileInfo);
            }
        }
        Log.d("navigateToFolder", "END");
    }

    private void updateFolderListView() {
        Log.d("updateFolderListView", "BEGIN");
        if (indexBrowser == null) {
            showAllFoldersListView();
        } else {
            showFolderListView(indexBrowser.getFolder(), indexBrowser.getCurrentPath());
        }
        Log.d("updateFolderListView", "END");
    }

    private void initDeviceList() {
        ListView listView = (ListView) findViewById(R.id.devices_list_view);
        listView.setEmptyView(findViewById(R.id.devices_list_view_empty_element));
        ArrayAdapter adapter = new ArrayAdapter<DeviceStats>(this, R.layout.listview_device, Lists.newArrayList()) {
            @NonNull
            @Override
            public View getView(int position, View v, ViewGroup parent) {
                if (v == null) {
                    v = LayoutInflater.from(getContext()).inflate(R.layout.listview_device, null);
                }
                DeviceStats deviceStats = getItem(position);
                ((TextView) v.findViewById(R.id.device_name)).setText(deviceStats.getName());
                switch (deviceStats.getStatus()) {
                    case OFFLINE:
                        ((TextView) v.findViewById(R.id.device_icon)).setTextColor(getResources().getColor(R.color.device_offline));
                        break;
                    case ONLINE_INACTIVE:
                        ((TextView) v.findViewById(R.id.device_icon)).setTextColor(getResources().getColor(R.color.device_online_inactive));
                        break;
                    case ONLINE_ACTIVE:
                        ((TextView) v.findViewById(R.id.device_icon)).setTextColor(getResources().getColor(R.color.device_online_active));
                        break;
                }
                return v;
            }
        };
        listView.setAdapter(adapter);
//        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
//            @Override
//            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
//                FileInfo fileInfo= (FileInfo)listView.getItemAtPosition(position);
        //TODO show device detail
//            }
//        });
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long l) {
                String deviceId = ((DeviceStats) listView.getItemAtPosition(position)).getDeviceId();
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("remove device " + deviceId.substring(0, 7))
                        .setMessage("remove device" + deviceId.substring(0, 7) + " from list of known devices?")
                        .setIcon(android.R.drawable.ic_dialog_alert)
                        .setPositiveButton("yes", new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                configuration.edit().removePeer(deviceId).persistLater();
                                updateDeviceList();
                            }
                        })
                        .setNegativeButton("no", null)
                        .show();
                Log.d("showFolderListView", "delete device = '" + deviceId + "'");
                return false;
            }
        });
        updateDeviceList();
    }

    private final List<Comparator<FileInfo>> availableFileInfoOrderings=Collections.unmodifiableList(Arrays.asList(FileInfoOrdering.ALPHA_ASC_DIR_FIRST, FileInfoOrdering.LAST_MOD_DESC));
    private Comparator<FileInfo> fileInfoOrdering=availableFileInfoOrderings.iterator().next();
    private final Map<Comparator<FileInfo>,Integer> iconResourceForOrdering=ImmutableMap.of(FileInfoOrdering.ALPHA_ASC_DIR_FIRST,R.string.icon_sort_alpha_asc,FileInfoOrdering.LAST_MOD_DESC,R.string.icon_sort_numeric_desc);

    private void toggleFileSort(){
        fileInfoOrdering=availableFileInfoOrderings.get( (availableFileInfoOrderings.indexOf(fileInfoOrdering)+1)%availableFileInfoOrderings.size() );
        ((TextView)findViewById(R.id.main_header_sort_order_button)).setText(iconResourceForOrdering.get(fileInfoOrdering));
        if(indexBrowser!=null){
            indexBrowser.setOrdering(fileInfoOrdering);
            updateFolderListView();
        }
        if(indexFinder!=null){
            indexFinder.setOrdering(fileInfoOrdering);
            updateSearchResultListView();
        }
    }

    private void updateDeviceList() {
        //TODO fix npe when opening drawer before app has fully started (no synclient)
        List<DeviceStats> list = Lists.newArrayList(syncthingClient.getDevicesHandler().getDeviceStatsList());
        Collections.sort(list, new Comparator<DeviceStats>() {
            Function<DeviceStats.DeviceStatus,Integer> fun= Functions.forMap(ImmutableMap.of(DeviceStats.DeviceStatus.OFFLINE,3, DeviceStats.DeviceStatus.ONLINE_INACTIVE,2, DeviceStats.DeviceStatus.ONLINE_ACTIVE,1));
            @Override
            public int compare(DeviceStats a, DeviceStats b) {
                return ComparisonChain.start().compare(fun.apply(a.getStatus()),fun.apply(b.getStatus())).compare(a.getName(),b.getName()).result();
            }
        });
        ListView listView = (ListView) findViewById(R.id.devices_list_view);
        ((ArrayAdapter) listView.getAdapter()).clear();
        ((ArrayAdapter<DeviceStats>) listView.getAdapter()).addAll(list);
        ((ArrayAdapter) listView.getAdapter()).notifyDataSetChanged();
        listView.setSelection(0);
    }

    private void pullFile(final FileInfo fileInfo) {
        Log.i("pullFile", "pulling file = " + fileInfo);
        new AsyncTask<Void, BlockPuller.FileDownloadObserver, Pair<File, Exception>>() {
            private ProgressDialog progressDialog;
            private Thread thread;
            private boolean cancelled = false;

            @Override
            protected void onPreExecute() {
                progressDialog = new ProgressDialog(MainActivity.this);
                progressDialog.setMessage("downloading file " + fileInfo.getFileName());
                progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                progressDialog.setCancelable(true);
                progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialogInterface) {
                        cancelled = true;
                        if (thread != null) {
                            thread.interrupt();
                        }
                        Toast.makeText(MainActivity.this, "download aborted by user", Toast.LENGTH_SHORT).show();
                    }
                });
                progressDialog.setIndeterminate(true);
                progressDialog.show();
            }

            @Override
            protected Pair<File, Exception> doInBackground(Void... voidd) {
                try {
                    try (BlockPuller.FileDownloadObserver fileDownloadObserver = syncthingClient.pullFile(fileInfo.getFolder(), fileInfo.getPath())) {
                        publishProgress(fileDownloadObserver);
                        while (!fileDownloadObserver.isCompleted() && !cancelled) {
                            fileDownloadObserver.waitForProgressUpdate();
                            Log.i("pullFile", "download progress = " + fileDownloadObserver.getProgressMessage());
                            publishProgress(fileDownloadObserver);
                        }
                        if (cancelled) {
                            return null;
                        }
                        File outputDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
                        File outputFile = new File(outputDir, fileInfo.getFileName());
                        FileUtils.copyInputStreamToFile(fileDownloadObserver.getInputStream(), outputFile);
                        Log.i("pullFile", "downloaded file = " + fileInfo.getPath());
                        return Pair.of(outputFile, null);
                    }
                } catch (Exception ex) {
                    if (cancelled) {
                        return null;
                    }
                    Log.e("pullFile", "file download exception", ex);
                    return Pair.of(null, ex);
                }
            }

            @Override
            protected void onProgressUpdate(BlockPuller.FileDownloadObserver... fileDownloadObserver) {
                if (fileDownloadObserver[0].getProgress() > 0) {
                    progressDialog.setIndeterminate(false);
                    progressDialog.setMax((int) (long) fileInfo.getSize());
                    progressDialog.setProgress((int) (fileDownloadObserver[0].getProgress() * fileInfo.getSize()));
                }
            }

            @Override
            protected void onPostExecute(Pair<File, Exception> res) {
                progressDialog.dismiss();
                if (cancelled) {
                    // do nothing
                } else if (res.getLeft() == null) {
                    Toast.makeText(MainActivity.this, "error downloading file: " + res.getRight(), Toast.LENGTH_LONG).show();
                } else {
                    String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(FilenameUtils.getExtension(res.getLeft().getName()));
                    Intent newIntent = new Intent(Intent.ACTION_VIEW);
                    Log.i("Main", "open file = " + res.getLeft().getName() + " (" + mimeType + ")");
                    newIntent.setDataAndType(Uri.fromFile(res.getLeft()), mimeType);
                    newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    Intent chooser = Intent.createChooser(newIntent, null);
                    try {
                        startActivity(chooser);
                    } catch (ActivityNotFoundException e) {
                        Toast.makeText(MainActivity.this, "no handler found for this file: " + res.getLeft().getName() + " (" + mimeType + ")", Toast.LENGTH_LONG).show();
                    }
                }
            }
        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    private final static String LAST_INDEX_UPDATE_TS_PREF = "LAST_INDEX_UPDATE_TS";

    private
    @Nullable
    Date getLastIndexUpdateFromPref() {
        long lastUpdate = getPreferences(MODE_PRIVATE).getLong(LAST_INDEX_UPDATE_TS_PREF, -1);
        if (lastUpdate < 0) {
            return null;
        } else {
            return new Date(lastUpdate);
        }
    }

    private void updateIndexFromRemote() {
        Log.d("Main", "updateIndexFromRemote BEGIN");
        if (indexUpdateInProgress) {
            Toast.makeText(MainActivity.this, "index update already in progress", Toast.LENGTH_SHORT).show();
        } else {
            indexUpdateInProgress = true;
            new AsyncTask<Void, Void, Exception>() {
                private View indexLoadingBar = (View) findViewById(R.id.main_index_progress_bar);

                @Override
                protected void onPreExecute() {
                    indexLoadingBar.setVisibility(View.VISIBLE);
                }

                @Override
                protected Exception doInBackground(Void... voidd) {
                    try {
                        syncthingClient.waitForRemoteIndexAquired();
                        return null;
                    } catch (Exception ex) {
                        Log.e("Main", "index dump exception", ex);
                        return ex;
                    }
                }

                @Override
                protected void onPostExecute(Exception ex) {
                    indexLoadingBar.setVisibility(View.GONE);
                    if (ex != null) {
                        Toast.makeText(MainActivity.this, "error updating index: " + ex.toString(), Toast.LENGTH_LONG).show();
                    }
                    updateFolderListView();
                    indexUpdateInProgress = false;
                    getPreferences(MODE_PRIVATE).edit()
                            .putLong(LAST_INDEX_UPDATE_TS_PREF, new Date().getTime())
                            .apply();
                }
            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        }
        Log.d("Main", "updateIndexFromRemote END (running bg)");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... voids) {
                    closeClient();
                    configuration.close();
                    configuration=null;
                    return null;
                }
            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR).get();
        } catch (Exception ex) {
            Log.w("Main", "error closing client", ex);
        }
    }

    public void openQrcode() {
        IntentIntegrator integrator = new IntentIntegrator(MainActivity.this);
        integrator.initiateScan();
    }


    /**
     * Receives value of scanned QR code and sets it as device ID.
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
        if (scanResult != null) {
            String deviceId = scanResult.getContents();
            if (!isBlank(deviceId)) {
                Log.i("Main", "qrcode text = " + deviceId);
                importDeviceId(deviceId);
                return;
            }
        }

        if (resultCode == Activity.RESULT_OK) {
            Uri fileUri = intent.getData();
            doUpload(indexBrowser.getFolder(), indexBrowser.getCurrentPath(), Arrays.asList(fileUri));
        }
    }

    @Override
    public void onBackPressed() {
        if (indexBrowser != null) {
            ListView listView = (ListView) findViewById(R.id.main_folder_and_files_list_view);
            listView.performItemClick(listView.getAdapter().getView(0, null, null), 0, listView.getItemIdAtPosition(0)); //click item '0', ie '..' (go to parent)
        } else {
            super.onBackPressed();
        }
    }

    private void importDeviceId(String deviceId) {
        try {
            KeystoreHandler.validateDeviceId(deviceId);
            boolean modified = configuration.edit().addPeers(new DeviceInfo(deviceId, null));
            if (modified) {
                configuration.edit().persistLater();
                Toast.makeText(this, "successfully imported device: " + deviceId, Toast.LENGTH_SHORT).show();
                updateDeviceList();//TODO remove this if event triggered (and handler trigger update)
                updateIndexFromRemote();
            } else {
                Toast.makeText(this, "device already present: " + deviceId, Toast.LENGTH_SHORT).show();
            }
        } catch (Exception ex) {
            Log.e("Main", "error importing deviceId = " + deviceId, ex);
            Toast.makeText(this, "error importing device: " + ex, Toast.LENGTH_LONG).show();
        }
    }

    private String getDeviceName() {
        String manufacturer = nullToEmpty(Build.MANUFACTURER);
        String model = nullToEmpty(Build.MODEL);
        String deviceName;
        if (model.startsWith(manufacturer)) {
            deviceName = capitalize(model);
        } else {
            deviceName = capitalize(manufacturer) + " " + model;
        }
        if (isBlank(deviceName)) {
            deviceName = "android";
        }
        return deviceName;
    }

}