org.proninyaroslav.libretorrent.fragments.DetailTorrentFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.proninyaroslav.libretorrent.fragments.DetailTorrentFragment.java

Source

/*
 * Copyright (C) 2016 Yaroslav Pronin <proninyaroslav@mail.ru>
 *
 * This file is part of LibreTorrent.
 *
 * LibreTorrent is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LibreTorrent 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 LibreTorrent.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.proninyaroslav.libretorrent.fragments;

import android.app.Activity;
import android.app.Fragment;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;

import com.frostwire.jlibtorrent.Priority;

import org.proninyaroslav.libretorrent.InputFilterMinMax;
import org.proninyaroslav.libretorrent.R;
import org.proninyaroslav.libretorrent.adapters.ViewPagerAdapter;
import org.proninyaroslav.libretorrent.core.BencodeFileItem;
import org.proninyaroslav.libretorrent.core.Torrent;
import org.proninyaroslav.libretorrent.core.TorrentMetaInfo;
import org.proninyaroslav.libretorrent.core.TorrentStateCode;
import org.proninyaroslav.libretorrent.core.stateparcel.PeerStateParcel;
import org.proninyaroslav.libretorrent.core.stateparcel.StateParcelCache;
import org.proninyaroslav.libretorrent.core.stateparcel.TorrentStateParcel;
import org.proninyaroslav.libretorrent.core.TorrentTaskServiceIPC;
import org.proninyaroslav.libretorrent.core.stateparcel.TrackerStateParcel;
import org.proninyaroslav.libretorrent.core.storage.TorrentStorage;
import org.proninyaroslav.libretorrent.core.utils.FileIOUtils;
import org.proninyaroslav.libretorrent.core.utils.TorrentUtils;
import org.proninyaroslav.libretorrent.core.utils.Utils;
import org.proninyaroslav.libretorrent.dialogs.BaseAlertDialog;
import org.proninyaroslav.libretorrent.dialogs.ErrorReportAlertDialog;
import org.proninyaroslav.libretorrent.dialogs.filemanager.FileManagerConfig;
import org.proninyaroslav.libretorrent.dialogs.filemanager.FileManagerDialog;
import org.proninyaroslav.libretorrent.services.TorrentTaskService;

import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class DetailTorrentFragment extends Fragment
        implements BaseAlertDialog.OnClickListener, BaseAlertDialog.OnDialogShowListener {
    @SuppressWarnings("unused")
    private static final String TAG = DetailTorrentFragment.class.getSimpleName();

    private static final String TAG_TORRENT_ID = "torrent_id";
    private static final String TAG_TORRENT_INFO_CHANGED = "torrent_info_changed";
    private static final String TAG_TORRENT_FILES_CHANGED = "torrent_files_changed";
    private static final String TAG_CHILD_IN_ACTION_MODE = "child_in_action_mode";
    private static final String TAG_ADD_TRACKERS_DIALOG = "add_trackers_dialog";
    private static final String TAG_DELETE_TORRENT_DIALOG = "delete_torrent_dialog";
    private static final String TAG_SAVE_ERR_TORRENT_FILE_DIALOG = "save_err_torrent_file_dialog";
    private static final String TAG_SPEED_LIMIT_DIALOG = "speed_limit_dialog";

    private static final int SAVE_TORRENT_FILE_CHOOSE_REQUEST = 1;

    private static final int SYNC_TIME = 1000; /* ms */
    private static final int INFO_FRAG_POS = 0;
    private static final int STATE_FRAG_POS = 1;
    private static final int FILE_FRAG_POS = 2;
    private static final int TRACKERS_FRAG_POS = 3;
    private static final int PEERS_FRAG_POS = 4;
    private static final int PIECES_FRAG_POS = 5;

    private AppCompatActivity activity;
    private Toolbar toolbar;
    private ViewPager viewPager;
    private CoordinatorLayout coordinatorLayout;
    private TabLayout tabLayout;
    private ViewPagerAdapter adapter;
    private FloatingActionButton saveChangesButton;

    private String torrentId;
    private Torrent torrent;
    private TorrentStorage repo;
    private Intent torrentServiceIntent;
    private Messenger serviceCallback = null;
    private Messenger clientCallback = new Messenger(new CallbackHandler(this));
    private TorrentTaskServiceIPC ipc = new TorrentTaskServiceIPC();
    /* Flag indicating whether we have called bind on the service. */
    private boolean bound;
    /* Update the torrent status params, which aren't included in TorrentStateParcel */
    private Handler updateTorrentState;

    private boolean isTorrentInfoChanged = false;
    private boolean isTorrentFilesChanged = false;
    /* One of the fragments in ViewPagerAdapter is in ActionMode */
    private boolean childInActionMode = false;

    private Exception sentError;

    /*
     * Caching data, if a fragment in tab isn't into view,
     * thereby preventing a useless update data in hidden fragments
     */
    private TorrentMetaInfo infoCache;
    private TorrentStateParcel stateCache;
    private int activeTimeCache, seedingTimeCache;
    private StateParcelCache<TrackerStateParcel> trackersCache = new StateParcelCache<>();
    private StateParcelCache<PeerStateParcel> peersCache = new StateParcelCache<>();
    private boolean[] piecesCache;
    private int uploadSpeedLimit = -1;
    private int downloadSpeedLimit = -1;

    public interface Callback {
        void onTorrentInfoChanged();

        void onTorrentInfoChangesUndone();

        void onTorrentFilesChanged();

        void onTrackersChanged(ArrayList<String> trackers, boolean replace);

        void openFile(String relativePath);
    }

    public static DetailTorrentFragment newInstance(String id) {
        DetailTorrentFragment fragment = new DetailTorrentFragment();
        fragment.setTorrentId(id);

        fragment.setArguments(new Bundle());

        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_detail_torrent, container, false);

        toolbar = (Toolbar) v.findViewById(R.id.detail_toolbar);
        coordinatorLayout = (CoordinatorLayout) v.findViewById(R.id.coordinator_layout);
        saveChangesButton = (FloatingActionButton) v.findViewById(R.id.save_changes_button);

        return v;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (context instanceof AppCompatActivity) {
            activity = (AppCompatActivity) context;
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (bound) {
            try {
                ipc.sendClientDisconnect(serviceCallback, clientCallback);

            } catch (RemoteException e) {
                Log.e(TAG, Log.getStackTraceString(e));
            }
            getActivity().unbindService(connection);

            bound = false;
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();

        Fragment fragment = getFragmentManager().findFragmentByTag(TAG_DELETE_TORRENT_DIALOG);

        /* Prevents leak the dialog in portrait mode */
        if (Utils.isTablet(activity.getApplicationContext()) && fragment != null) {
            ((BaseAlertDialog) fragment).dismiss();
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (activity == null) {
            activity = (AppCompatActivity) getActivity();
        }

        Utils.showColoredStatusBar_KitKat(activity);

        if (savedInstanceState != null) {
            torrentId = savedInstanceState.getString(TAG_TORRENT_ID);
            isTorrentInfoChanged = savedInstanceState.getBoolean(TAG_TORRENT_INFO_CHANGED);
            isTorrentFilesChanged = savedInstanceState.getBoolean(TAG_TORRENT_FILES_CHANGED);
            childInActionMode = savedInstanceState.getBoolean(TAG_CHILD_IN_ACTION_MODE);
        }

        if (isTorrentFilesChanged || isTorrentInfoChanged) {
            saveChangesButton.setVisibility(View.VISIBLE);
        } else {
            saveChangesButton.setVisibility(View.GONE);
        }

        saveChangesButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                saveChangesButton.hide();

                if (isTorrentInfoChanged) {
                    applyInfoChanges();
                }

                if (isTorrentFilesChanged) {
                    applyFilesChanges();
                }

                isTorrentInfoChanged = false;
                isTorrentFilesChanged = false;
            }
        });

        if (savedInstanceState != null) {
            torrentId = savedInstanceState.getString(TAG_TORRENT_ID);
        }

        repo = new TorrentStorage(activity.getApplicationContext());
        if (torrentId != null) {
            torrent = repo.getTorrentByID(torrentId);
        }

        torrentServiceIntent = new Intent(activity.getApplicationContext(), TorrentTaskService.class);
        activity.startService(torrentServiceIntent);

        activity.bindService(torrentServiceIntent, connection, Context.BIND_AUTO_CREATE);

        if (Utils.isTwoPane(activity.getApplicationContext())) {
            toolbar.inflateMenu(R.menu.detail_torrent);
            toolbar.setNavigationIcon(ContextCompat.getDrawable(activity.getApplicationContext(),
                    R.drawable.ic_arrow_back_white_24dp));
            toolbar.setNavigationOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    onBackPressed();
                }
            });
            toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    return onOptionsItemSelected(item);
                }
            });

        } else {
            if (torrent != null) {
                toolbar.setTitle(torrent.getName());
            }

            activity.setSupportActionBar(toolbar);
            setHasOptionsMenu(true);

            if (activity.getSupportActionBar() != null) {
                activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            }
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putBoolean(TAG_TORRENT_INFO_CHANGED, isTorrentInfoChanged);
        outState.putBoolean(TAG_TORRENT_FILES_CHANGED, isTorrentFilesChanged);
        outState.putString(TAG_TORRENT_ID, torrentId);
        outState.putBoolean(TAG_CHILD_IN_ACTION_MODE, childInActionMode);

        super.onSaveInstanceState(outState);
    }

    private ServiceConnection connection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            serviceCallback = new Messenger(service);
            bound = true;
            try {
                ipc.sendClientConnect(serviceCallback, clientCallback);

            } catch (RemoteException e) {
                Log.e(TAG, Log.getStackTraceString(e));
            }
            initRequest();
            startUpdateTorrentState();
        }

        public void onServiceDisconnected(ComponentName className) {
            serviceCallback = null;
            bound = false;
        }
    };

    public void setTorrentId(String id) {
        torrentId = id;
    }

    public String getTorrentId() {
        return torrentId;
    }

    private void initFragments() {
        if (viewPager != null) {
            return;
        }

        ArrayList<BencodeFileItem> files = null;
        ArrayList<Priority> priorities = null;

        if (infoCache != null) {
            files = infoCache.getFiles();
        }

        if (torrent != null && files != null) {
            /*
             * The index position in array must be
             * equal to the priority position in array
             */
            priorities = new ArrayList<>();
            List<Integer> torrentPriorities = torrent.getFilePriorities();

            for (int i = 0; i < torrentPriorities.size(); i++) {
                priorities.add(Priority.fromSwig(torrentPriorities.get(i)));
            }
        }

        DetailTorrentInfoFragment fragmentInfo = DetailTorrentInfoFragment.newInstance(torrent, infoCache);
        DetailTorrentStateFragment fragmentState = DetailTorrentStateFragment.newInstance(infoCache);
        DetailTorrentFilesFragment fragmentFiles = DetailTorrentFilesFragment.newInstance(files, priorities);
        DetailTorrentTrackersFragment fragmentTrackers = DetailTorrentTrackersFragment.newInstance();
        DetailTorrentPeersFragment fragmentPeers = DetailTorrentPeersFragment.newInstance();
        DetailTorrentPiecesFragment fragmentPieces = DetailTorrentPiecesFragment
                .newInstance(infoCache.getNumPieces(), infoCache.getPieceLength());

        viewPager = (ViewPager) activity.findViewById(R.id.detail_torrent_viewpager);

        if (viewPager == null) {
            return;
        }

        /* Removing previous ViewPagerAdapter fragments, if any */
        if (Utils.isTablet(activity.getApplicationContext())) {
            android.support.v4.app.FragmentManager fm = activity.getSupportFragmentManager();
            List<android.support.v4.app.Fragment> fragments = fm.getFragments();

            if (fragments != null) {
                FragmentTransaction ft = fm.beginTransaction();
                for (android.support.v4.app.Fragment f : fragments) {
                    if (f != null) {
                        ft.remove(f);
                    }
                }

                ft.commitAllowingStateLoss();
            }
        }

        adapter = new ViewPagerAdapter(activity.getSupportFragmentManager());
        adapter.addFragment(fragmentInfo, INFO_FRAG_POS, getString(R.string.torrent_info));
        adapter.addFragment(fragmentState, STATE_FRAG_POS, getString(R.string.torrent_state));
        adapter.addFragment(fragmentFiles, FILE_FRAG_POS, getString(R.string.torrent_files));
        adapter.addFragment(fragmentTrackers, TRACKERS_FRAG_POS, getString(R.string.torrent_trackers));
        adapter.addFragment(fragmentPeers, PEERS_FRAG_POS, getString(R.string.torrent_peers));
        adapter.addFragment(fragmentPieces, PIECES_FRAG_POS, getString(R.string.torrent_pieces));
        viewPager.setAdapter(adapter);
        viewPager.setOffscreenPageLimit(6);
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                /* Nothing */
            }

            @Override
            public void onPageSelected(int position) {
                switch (position) {
                case STATE_FRAG_POS:
                    DetailTorrentStateFragment stateFrag = (DetailTorrentStateFragment) adapter
                            .getItem(STATE_FRAG_POS);
                    if (stateFrag != null) {
                        stateFrag.setState(stateCache);
                        stateFrag.setActiveAndSeedingTime(activeTimeCache, seedingTimeCache);
                    }
                    break;
                case FILE_FRAG_POS:
                    DetailTorrentFilesFragment fileFrag = (DetailTorrentFilesFragment) adapter
                            .getItem(FILE_FRAG_POS);
                    if (fileFrag != null) {
                        fileFrag.setFilesReceivedBytes(stateCache.filesReceivedBytes);
                    }
                    break;
                case TRACKERS_FRAG_POS:
                    DetailTorrentTrackersFragment trackersFrag = (DetailTorrentTrackersFragment) adapter
                            .getItem(TRACKERS_FRAG_POS);
                    if (trackersFrag != null) {
                        trackersFrag.setTrackersList(new ArrayList<>(trackersCache.getAll()));
                    }
                    break;
                case PEERS_FRAG_POS:
                    DetailTorrentPeersFragment peersFrag = (DetailTorrentPeersFragment) adapter
                            .getItem(PEERS_FRAG_POS);
                    if (peersFrag != null) {
                        peersFrag.setPeerList(new ArrayList<>(peersCache.getAll()));
                    }
                    break;
                case PIECES_FRAG_POS:
                    DetailTorrentPiecesFragment piecesFrag = (DetailTorrentPiecesFragment) adapter
                            .getItem(PIECES_FRAG_POS);

                    if (piecesFrag != null) {
                        piecesFrag.setPieces(piecesCache);
                        piecesFrag.setDownloadedPiecesCount(stateCache.downloadedPieces);
                    }
                    break;
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                /* Nothing */
            }
        });
        tabLayout = (TabLayout) activity.findViewById(R.id.detail_torrent_tabs);

        int colorId = (childInActionMode ? R.color.action_mode : R.color.primary);
        setTabLayoutColor(colorId);

        tabLayout.setupWithViewPager(viewPager);

        if (bound && serviceCallback != null) {
            try {
                ipc.sendTorrentStateOneShot(serviceCallback, clientCallback);

            } catch (RemoteException e) {
                Log.e(TAG, Log.getStackTraceString(e));
            }
        }
    }

    public void onTorrentInfoChanged() {
        isTorrentInfoChanged = true;

        saveChangesButton.show();
    }

    public void onTorrentInfoChangesUndone() {
        isTorrentInfoChanged = false;

        saveChangesButton.hide();
    }

    public void onTorrentFilesChanged() {
        isTorrentFilesChanged = true;

        saveChangesButton.show();
    }

    private void applyInfoChanges() {
        DetailTorrentInfoFragment infoFrag = (DetailTorrentInfoFragment) adapter.getItem(INFO_FRAG_POS);
        DetailTorrentFilesFragment fileFrag = (DetailTorrentFilesFragment) adapter.getItem(FILE_FRAG_POS);

        if (infoFrag == null || fileFrag == null) {
            return;
        }

        String name = infoFrag.getTorrentName();
        String path = infoFrag.getDownloadPath();
        boolean sequential = infoFrag.isSequentialDownload();

        if (FileIOUtils.getFreeSpace(path) < fileFrag.getSeletedFileSize()) {
            Snackbar snackbar = Snackbar.make(coordinatorLayout, R.string.error_free_space, Snackbar.LENGTH_LONG);
            snackbar.show();

            return;
        }

        if (!name.equals(torrent.getName())) {
            try {
                ipc.sendSetTorrentName(serviceCallback, torrentId, name);
                torrent.setName(name);
                infoFrag.setTorrentName(name);

            } catch (RemoteException e) {
                /* Ignore */
            }
        }

        if (!path.equals(torrent.getDownloadPath())) {
            try {
                ArrayList<String> list = new ArrayList<>();
                list.add(torrentId);

                ipc.sendSetDownloadPath(serviceCallback, list, path);
                torrent.setDownloadPath(path);
                infoFrag.setDownloadPath(path);

            } catch (RemoteException e) {
                /* Ignore */
            }
        }

        if (sequential != torrent.isSequentialDownload()) {
            try {
                ipc.sendSetSequentialDownload(serviceCallback, torrentId, sequential);
                torrent.setSequentialDownload(sequential);
                infoFrag.setSequentialDownload(sequential);

            } catch (RemoteException e) {
                /* Ignore */
            }
        }
    }

    private void applyFilesChanges() {
        DetailTorrentFilesFragment fileFrag = (DetailTorrentFilesFragment) adapter.getItem(FILE_FRAG_POS);

        if (fileFrag == null) {
            return;
        }

        Priority[] priorities = fileFrag.getPriorities();

        if (priorities != null) {
            ArrayList<Integer> list = new ArrayList<>();

            for (Priority priority : priorities) {
                if (priority != null) {
                    list.add(priority.swig());
                }
            }

            fileFrag.disableSelectedFiles();

            changeFilesPriorityRequest(list);
        }
    }

    /*
     * Repaint TabLayout in ActionMode color if it created.
     */

    private void setTabLayoutColor(int colorId) {
        if (tabLayout == null) {
            return;
        }

        Utils.setBackground(tabLayout, ContextCompat.getDrawable(activity.getApplicationContext(), colorId));
    }

    public void onTrackersChanged(ArrayList<String> trackers, boolean replace) {
        trackersCache.clear();
        addTrackersRequest(trackers, replace);
    }

    public void showSnackbar(String text, int length) {
        Snackbar.make(coordinatorLayout, text, length).show();
    }

    public void openFile(String relativePath) {
        if (relativePath == null) {
            return;
        }

        String path = torrent.getDownloadPath() + File.separator + relativePath;

        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);
        /* Give user a choice than to open file (without determining MIME type) */
        intent.setDataAndType(Uri.fromFile(new File(path)), "*/*");
        startActivity(Intent.createChooser(intent, getString(R.string.open_using)));
    }

    private void torrentSaveChooseDialog() {
        Intent i = new Intent(activity, FileManagerDialog.class);

        FileManagerConfig config = new FileManagerConfig(FileIOUtils.getUserDirPath(), null, null,
                FileManagerConfig.DIR_CHOOSER_MODE);

        i.putExtra(FileManagerDialog.TAG_CONFIG, config);

        startActivityForResult(i, SAVE_TORRENT_FILE_CHOOSE_REQUEST);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);

        inflater.inflate(R.menu.detail_torrent, menu);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        prepareOptionsMenu(menu);
    }

    private void prepareOptionsMenu(Menu menu) {
        MenuItem pauseResume = menu.findItem(R.id.pause_resume_torrent_menu);

        if (torrent == null || !torrent.isPaused()) {
            pauseResume.setTitle(R.string.pause_torrent);
            if (!Utils.isTwoPane(activity.getApplicationContext())) {
                pauseResume.setIcon(R.drawable.ic_pause_white_24dp);
            }

        } else {
            pauseResume.setTitle(R.string.resume_torrent);
            if (!Utils.isTwoPane(activity.getApplicationContext())) {
                pauseResume.setIcon(R.drawable.ic_play_arrow_white_24dp);
            }
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            onBackPressed();
            break;
        case R.id.pause_resume_torrent_menu:
            pauseResumeTorrentRequest();
            break;
        case R.id.delete_torrent_menu:
            if (getFragmentManager().findFragmentByTag(TAG_DELETE_TORRENT_DIALOG) == null) {
                BaseAlertDialog deleteTorrentDialog = BaseAlertDialog.newInstance(getString(R.string.deleting),
                        getString(R.string.delete_selected_torrent), R.layout.dialog_delete_torrent,
                        getString(R.string.ok), getString(R.string.cancel), null, R.style.BaseTheme_Dialog,
                        DetailTorrentFragment.this);

                deleteTorrentDialog.show(getFragmentManager(), TAG_DELETE_TORRENT_DIALOG);
            }
            break;
        case R.id.force_recheck_torrent_menu:
            forceRecheckRequest();
            break;
        case R.id.force_announce_torrent_menu:
            forceAnnounceRequest();
            break;
        case R.id.share_magnet_menu:
            getMagnetRequest();
            break;
        case R.id.save_torrent_file_menu:
            torrentSaveChooseDialog();
            break;
        case R.id.add_trackers_menu:
            if (getFragmentManager().findFragmentByTag(TAG_ADD_TRACKERS_DIALOG) == null) {
                BaseAlertDialog addTrackersDialog = BaseAlertDialog.newInstance(getString(R.string.add_trackers),
                        getString(R.string.dialog_add_trackers), R.layout.dialog_multiline_text_input,
                        getString(R.string.add), getString(R.string.replace), getString(R.string.cancel),
                        R.style.BaseTheme_Dialog, DetailTorrentFragment.this);

                addTrackersDialog.show(getFragmentManager(), TAG_ADD_TRACKERS_DIALOG);
            }
            break;
        case R.id.torrent_speed_limit:
            if (getFragmentManager().findFragmentByTag(TAG_SPEED_LIMIT_DIALOG) == null) {
                BaseAlertDialog speedLimitDialog = BaseAlertDialog.newInstance(
                        getString(R.string.speed_limit_title), getString(R.string.speed_limit_dialog),
                        R.layout.dialog_speed_limit, getString(R.string.ok), getString(R.string.cancel), null,
                        R.style.BaseTheme_Dialog, DetailTorrentFragment.this);

                speedLimitDialog.show(getFragmentManager(), TAG_SPEED_LIMIT_DIALOG);
            }
            break;
        }

        return true;
    }

    @Override
    public void onShow(final AlertDialog dialog) {
        if (dialog == null) {
            return;
        }

        if (getFragmentManager().findFragmentByTag(TAG_ADD_TRACKERS_DIALOG) != null) {
            final TextInputEditText field = (TextInputEditText) dialog
                    .findViewById(R.id.multiline_text_input_dialog);
            final TextInputLayout fieldLayout = (TextInputLayout) dialog
                    .findViewById(R.id.layout_multiline_text_input_dialog);

            /* Dismiss error label if user has changed the text */
            if (field != null && fieldLayout != null) {
                field.addTextChangedListener(new TextWatcher() {
                    @Override
                    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                        /* Nothing */
                    }

                    @Override
                    public void onTextChanged(CharSequence s, int start, int before, int count) {
                        fieldLayout.setErrorEnabled(false);
                        fieldLayout.setError(null);

                        /* Clear selection of invalid url */
                        Spannable text = field.getText();
                        ForegroundColorSpan[] errorSpans = text.getSpans(0, text.length(),
                                ForegroundColorSpan.class);
                        for (ForegroundColorSpan span : errorSpans) {
                            text.removeSpan(span);
                        }
                    }

                    @Override
                    public void afterTextChanged(Editable s) {
                        /* Nothing */
                    }
                });
            }

            /*
             * It is necessary in order to the dialog is not closed by
             * pressing add/replace button if the text checker gave a false result
             */
            Button addButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
            Button replaceButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);

            addButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (field != null && fieldLayout != null) {
                        String text = field.getText().toString();
                        List<String> urls = Arrays.asList(text.split(Utils.getLineSeparator()));

                        if (checkEditTextField(urls, fieldLayout, field)) {
                            addTrackersRequest(new ArrayList<>(urls), false);

                            dialog.dismiss();
                        }
                    }
                }
            });

            replaceButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (field != null && fieldLayout != null) {
                        String text = field.getText().toString();
                        List<String> urls = Arrays.asList(text.split(Utils.getLineSeparator()));

                        if (checkEditTextField(urls, fieldLayout, field)) {
                            addTrackersRequest(new ArrayList<>(urls), true);

                            dialog.dismiss();
                        }
                    }
                }
            });

            /* Inserting links from the clipboard */
            String clipboard = Utils.getClipboard(activity.getApplicationContext());

            if (clipboard != null && field != null) {
                List<String> urls = Arrays.asList(clipboard.split(Utils.getLineSeparator()));
                ArrayList<String> validUrls = new ArrayList<>();

                for (String url : urls) {
                    if (Utils.isValidTrackerUrl(url)) {
                        validUrls.add(url);
                    }
                }

                field.setText(TextUtils.join(Utils.getLineSeparator(), validUrls));
            }

        } else if (getFragmentManager().findFragmentByTag(TAG_SPEED_LIMIT_DIALOG) != null) {
            TextInputEditText upload = (TextInputEditText) dialog.findViewById(R.id.upload_limit);
            TextInputEditText download = (TextInputEditText) dialog.findViewById(R.id.download_limit);

            if (upload != null && download != null) {
                int minSpeedLimit = 0;
                int maxSpeedLimit = Integer.MAX_VALUE;
                InputFilter[] filter = new InputFilter[] { new InputFilterMinMax(minSpeedLimit, maxSpeedLimit) };

                upload.setFilters(filter);
                if (TextUtils.isEmpty(upload.getText())) {
                    upload.setText((uploadSpeedLimit != -1 ? Integer.toString(uploadSpeedLimit / 1024)
                            : Integer.toString(minSpeedLimit)));
                }

                download.setFilters(filter);
                if (TextUtils.isEmpty(download.getText())) {
                    download.setText((downloadSpeedLimit != -1 ? Integer.toString(downloadSpeedLimit / 1024)
                            : Integer.toString(minSpeedLimit)));
                }
            }
        }
    }

    @Override
    public void onPositiveClicked(@Nullable View v) {
        if (v != null) {
            if (getFragmentManager().findFragmentByTag(TAG_DELETE_TORRENT_DIALOG) != null) {
                CheckBox withFiles = (CheckBox) v.findViewById(R.id.dialog_delete_torrent_with_downloaded_files);
                deleteTorrentRequest(withFiles.isChecked());

                finish(new Intent(), FragmentCallback.ResultCode.CANCEL);

            } else if (getFragmentManager().findFragmentByTag(TAG_SPEED_LIMIT_DIALOG) != null) {
                TextInputEditText upload = (TextInputEditText) v.findViewById(R.id.upload_limit);
                TextInputEditText download = (TextInputEditText) v.findViewById(R.id.download_limit);

                uploadSpeedLimit = Integer.parseInt(upload.getText().toString()) * 1024;
                downloadSpeedLimit = Integer.parseInt(download.getText().toString()) * 1024;

                setSpeedLimitRequest(uploadSpeedLimit, downloadSpeedLimit);

            } else if (getFragmentManager().findFragmentByTag(TAG_SAVE_ERR_TORRENT_FILE_DIALOG) != null) {
                if (sentError != null) {
                    EditText editText = (EditText) v.findViewById(R.id.comment);
                    String comment = editText.getText().toString();

                    Utils.reportError(sentError, comment);
                }
            }
        }
    }

    @Override
    public void onNegativeClicked(@Nullable View v) {
        /* Nothing */
    }

    @Override
    public void onNeutralClicked(@Nullable View v) {
        /* Nothing */
    }

    private boolean checkEditTextField(List<String> strings, TextInputLayout layout, TextInputEditText field) {
        if (strings == null || layout == null) {
            return false;
        }

        if (strings.isEmpty()) {
            layout.setErrorEnabled(true);
            layout.setError(getString(R.string.error_empty_link));
            layout.requestFocus();

            return false;
        }

        boolean valid = true;
        int curLineStartIndex = 0;
        for (String s : strings) {
            if (!Utils.isValidTrackerUrl(s)) {
                /* Select invalid url */
                field.getText().setSpan(
                        new ForegroundColorSpan(
                                ContextCompat.getColor(activity.getApplicationContext(), R.color.error)),
                        curLineStartIndex, curLineStartIndex + s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

                valid = false;
            }
            /* Considering newline char */
            curLineStartIndex += s.length() + 1;
        }

        if (valid) {
            layout.setErrorEnabled(false);
            layout.setError(null);
        } else {
            layout.setErrorEnabled(true);
            layout.setError(getString(R.string.error_invalid_link));
            layout.requestFocus();
        }

        return valid;
    }

    private void saveErrorTorrentFileDialog(Exception e) {
        sentError = e;

        if (getFragmentManager().findFragmentByTag(TAG_SAVE_ERR_TORRENT_FILE_DIALOG) == null) {
            ErrorReportAlertDialog errDialog = ErrorReportAlertDialog.newInstance(activity.getApplicationContext(),
                    getString(R.string.error), getString(R.string.error_save_torrent_file),
                    (e != null ? Log.getStackTraceString(e) : null), R.style.BaseTheme_Dialog, this);

            errDialog.show(getFragmentManager(), TAG_SAVE_ERR_TORRENT_FILE_DIALOG);
        }
    }

    private void initRequest() {
        if (!bound || serviceCallback == null) {
            return;
        }

        try {
            ipc.sendGetTorrentInfo(serviceCallback, clientCallback, torrentId);
            ipc.sendGetSpeedLimit(serviceCallback, clientCallback, torrentId);

        } catch (RemoteException e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }

    private void changeFilesPriorityRequest(ArrayList<Integer> priorities) {
        if (!bound || serviceCallback == null || priorities == null || torrentId == null) {
            return;
        }

        try {
            ipc.sendChangeFilesPriority(serviceCallback, torrentId, priorities);

        } catch (RemoteException e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }

    private void addTrackersRequest(ArrayList<String> trackers, boolean replace) {
        if (!bound || serviceCallback == null || trackers == null || torrentId == null) {
            return;
        }

        try {
            if (replace) {
                ipc.sendReplaceTrackers(serviceCallback, torrentId, trackers);
            } else {
                ipc.sendAddTrackers(serviceCallback, torrentId, trackers);
            }

        } catch (RemoteException e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }

    private void deleteTorrentRequest(boolean withFiles) {
        if (!bound || serviceCallback == null) {
            return;
        }

        ArrayList<String> list = new ArrayList<>();
        list.add(torrentId);

        try {
            ipc.sendDeleteTorrents(serviceCallback, list, withFiles);

        } catch (RemoteException e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }

    private void forceRecheckRequest() {
        if (!bound || serviceCallback == null) {
            return;
        }

        ArrayList<String> list = new ArrayList<>();
        list.add(torrentId);

        try {
            ipc.sendForceRecheck(serviceCallback, list);

        } catch (RemoteException e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }

    private void forceAnnounceRequest() {
        if (!bound || serviceCallback == null) {
            return;
        }

        ArrayList<String> list = new ArrayList<>();
        list.add(torrentId);

        try {
            ipc.sendForceAnnounce(serviceCallback, list);

        } catch (RemoteException e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }

    private void pauseResumeTorrentRequest() {
        if (!bound || serviceCallback == null) {
            return;
        }

        ArrayList<String> list = new ArrayList<>();
        list.add(torrentId);

        try {
            ipc.sendPauseResumeTorrents(serviceCallback, list);

        } catch (RemoteException e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }

    private void getMagnetRequest() {
        if (!bound || serviceCallback == null) {
            return;
        }

        try {
            ipc.sendGetMagnet(serviceCallback, clientCallback, torrentId);

        } catch (RemoteException e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }

    private void setSpeedLimitRequest(int uploadSpeedLimit, int downloadSpeedLimit) {
        if (!bound || serviceCallback == null) {
            return;
        }

        try {
            ipc.sendSetUploadSpeedLimit(serviceCallback, torrentId, uploadSpeedLimit);
            ipc.sendSetDownloadSpeedLimit(serviceCallback, torrentId, downloadSpeedLimit);

        } catch (RemoteException e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }

    private void startUpdateTorrentState() {
        if (updateTorrentState != null) {
            return;
        }

        updateTorrentState = new Handler();
        Runnable r = new Runnable() {
            @Override
            public void run() {
                if (bound && serviceCallback != null && torrentId != null) {
                    try {
                        ipc.sendGetActiveAndSeedingTime(serviceCallback, clientCallback, torrentId);
                        ipc.sendGetTrackersStates(serviceCallback, clientCallback, torrentId);
                        ipc.sendGetPeersStates(serviceCallback, clientCallback, torrentId);
                        ipc.sendGetPieces(serviceCallback, clientCallback, torrentId);

                    } catch (RemoteException e) {
                        /* Ignore */
                    }
                }
                updateTorrentState.postDelayed(this, SYNC_TIME);
            }
        };
        updateTorrentState.postDelayed(r, SYNC_TIME);
    }

    static class CallbackHandler extends Handler {
        WeakReference<DetailTorrentFragment> fragment;

        public CallbackHandler(DetailTorrentFragment fragment) {
            this.fragment = new WeakReference<>(fragment);
        }

        @Override
        public void handleMessage(Message msg) {
            Bundle b;

            try {
                switch (msg.what) {
                case TorrentTaskServiceIPC.UPDATE_STATES_ONESHOT: {
                    b = msg.getData();
                    b.setClassLoader(TorrentStateParcel.class.getClassLoader());

                    Bundle states = b.getParcelable(TorrentTaskServiceIPC.TAG_STATES_LIST);
                    if (states != null) {
                        TorrentStateParcel state = states.getParcelable(fragment.get().torrentId);

                        if (state != null) {
                            fragment.get().stateCache = state;

                            if (fragment.get().torrent != null) {
                                if (state.stateCode == TorrentStateCode.PAUSED
                                        || fragment.get().torrent.isPaused()) {

                                    fragment.get().torrent.setPaused(state.stateCode == TorrentStateCode.PAUSED);

                                    /* Redraw pause/resume menu */
                                    if (Utils.isTwoPane(fragment.get().activity.getApplicationContext())) {
                                        fragment.get().prepareOptionsMenu(fragment.get().toolbar.getMenu());
                                    } else {
                                        fragment.get().activity.invalidateOptionsMenu();
                                    }
                                }
                            }

                            switch (fragment.get().viewPager.getCurrentItem()) {
                            case STATE_FRAG_POS:
                                DetailTorrentStateFragment stateFrag = (DetailTorrentStateFragment) fragment
                                        .get().adapter.getItem(STATE_FRAG_POS);

                                if (stateFrag != null) {
                                    stateFrag.setState(state);
                                }
                                break;
                            case FILE_FRAG_POS:
                                DetailTorrentFilesFragment fileFrag = (DetailTorrentFilesFragment) fragment
                                        .get().adapter.getItem(FILE_FRAG_POS);

                                if (fileFrag != null) {
                                    fileFrag.setFilesReceivedBytes(state.filesReceivedBytes);
                                }
                                break;
                            case PIECES_FRAG_POS:
                                DetailTorrentPiecesFragment piecesFrag = (DetailTorrentPiecesFragment) fragment
                                        .get().adapter.getItem(PIECES_FRAG_POS);

                                if (piecesFrag != null) {
                                    piecesFrag.setDownloadedPiecesCount(state.downloadedPieces);
                                }
                                break;
                            }
                        }
                    }
                    break;
                }
                case TorrentTaskServiceIPC.UPDATE_STATE: {
                    b = msg.getData();
                    b.setClassLoader(TorrentStateParcel.class.getClassLoader());
                    TorrentStateParcel state = b.getParcelable(TorrentTaskServiceIPC.TAG_STATE);

                    if (state != null) {
                        fragment.get().stateCache = state;

                        if (fragment.get().torrent != null) {
                            if (state.stateCode == TorrentStateCode.PAUSED || fragment.get().torrent.isPaused()) {

                                fragment.get().torrent.setPaused(state.stateCode == TorrentStateCode.PAUSED);

                                /* Redraw pause/resume menu */
                                if (Utils.isTwoPane(fragment.get().activity.getApplicationContext())) {
                                    fragment.get().prepareOptionsMenu(fragment.get().toolbar.getMenu());
                                } else {
                                    fragment.get().activity.invalidateOptionsMenu();
                                }
                            }
                        }

                        switch (fragment.get().viewPager.getCurrentItem()) {
                        case STATE_FRAG_POS:
                            DetailTorrentStateFragment stateFrag = (DetailTorrentStateFragment) fragment
                                    .get().adapter.getItem(STATE_FRAG_POS);

                            if (stateFrag != null) {
                                stateFrag.setState(state);
                            }
                            break;
                        case FILE_FRAG_POS:
                            DetailTorrentFilesFragment fileFrag = (DetailTorrentFilesFragment) fragment
                                    .get().adapter.getItem(FILE_FRAG_POS);

                            if (fileFrag != null) {
                                fileFrag.setFilesReceivedBytes(state.filesReceivedBytes);
                            }
                            break;
                        case PIECES_FRAG_POS:
                            DetailTorrentPiecesFragment piecesFrag = (DetailTorrentPiecesFragment) fragment
                                    .get().adapter.getItem(PIECES_FRAG_POS);

                            if (piecesFrag != null) {
                                piecesFrag.setDownloadedPiecesCount(state.downloadedPieces);
                            }
                            break;
                        }
                    }
                    break;
                }
                case TorrentTaskServiceIPC.GET_TORRENT_INFO: {
                    b = msg.getData();
                    b.setClassLoader(TorrentMetaInfo.class.getClassLoader());
                    fragment.get().infoCache = b.getParcelable(TorrentTaskServiceIPC.TAG_TORRENT_INFO);
                    fragment.get().initFragments();
                    break;
                }
                case TorrentTaskServiceIPC.GET_ACTIVE_AND_SEEDING_TIME: {
                    int activeTime = msg.arg1;
                    int seedingTime = msg.arg2;
                    fragment.get().activeTimeCache = activeTime;
                    fragment.get().seedingTimeCache = seedingTime;

                    if (fragment.get().viewPager.getCurrentItem() == STATE_FRAG_POS) {
                        DetailTorrentStateFragment stateFrag = (DetailTorrentStateFragment) fragment.get().adapter
                                .getItem(STATE_FRAG_POS);

                        if (stateFrag != null) {
                            stateFrag.setActiveAndSeedingTime(activeTime, seedingTime);
                        }
                    }
                    break;
                }
                case TorrentTaskServiceIPC.GET_TRACKERS_STATES: {
                    b = msg.getData();
                    b.setClassLoader(TrackerStateParcel.class.getClassLoader());
                    ArrayList<TrackerStateParcel> states = b
                            .getParcelableArrayList(TorrentTaskServiceIPC.TAG_TRACKERS_STATES_LIST);

                    if (!fragment.get().trackersCache.containsAll(states)) {
                        fragment.get().trackersCache.clear();
                        fragment.get().trackersCache.putAll(states);

                        if (fragment.get().viewPager.getCurrentItem() == TRACKERS_FRAG_POS) {
                            DetailTorrentTrackersFragment trackersFrag = (DetailTorrentTrackersFragment) fragment
                                    .get().adapter.getItem(TRACKERS_FRAG_POS);

                            if (trackersFrag != null) {
                                trackersFrag.setTrackersList(states);
                            }
                        }
                    }
                    break;
                }
                case TorrentTaskServiceIPC.GET_PEERS_STATES: {
                    b = msg.getData();
                    b.setClassLoader(PeerStateParcel.class.getClassLoader());
                    ArrayList<PeerStateParcel> states = b
                            .getParcelableArrayList(TorrentTaskServiceIPC.TAG_PEERS_STATES_LIST);

                    if (!fragment.get().peersCache.containsAll(states) || states.isEmpty()) {
                        fragment.get().peersCache.clear();
                        fragment.get().peersCache.putAll(states);

                        if (fragment.get().viewPager.getCurrentItem() == PEERS_FRAG_POS) {
                            DetailTorrentPeersFragment peersFrag = (DetailTorrentPeersFragment) fragment
                                    .get().adapter.getItem(PEERS_FRAG_POS);

                            if (peersFrag != null) {
                                peersFrag.setPeerList(states);
                            }
                        }
                    }
                    break;
                }
                case TorrentTaskServiceIPC.GET_PIECES: {
                    b = msg.getData();
                    boolean[] pieces = b.getBooleanArray(TorrentTaskServiceIPC.TAG_PIECE_LIST);

                    if (!Arrays.equals(fragment.get().piecesCache, pieces)) {
                        fragment.get().piecesCache = pieces;

                        if (fragment.get().viewPager.getCurrentItem() == PIECES_FRAG_POS) {
                            DetailTorrentPiecesFragment piecesFrag = (DetailTorrentPiecesFragment) fragment
                                    .get().adapter.getItem(PIECES_FRAG_POS);

                            if (piecesFrag != null) {
                                piecesFrag.setPieces(pieces);
                            }
                        }
                    }
                    break;
                }
                case TorrentTaskServiceIPC.GET_MAGNET: {
                    String magnet = msg.getData().getString(TorrentTaskServiceIPC.TAG_MAGNET);

                    if (magnet != null) {
                        Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
                        sharingIntent.setType("text/plain");
                        sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "magnet");
                        sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, magnet);
                        fragment.get().startActivity(
                                Intent.createChooser(sharingIntent, fragment.get().getString(R.string.share_via)));
                    }
                    break;
                }
                case TorrentTaskServiceIPC.GET_SPEED_LIMIT: {
                    fragment.get().uploadSpeedLimit = msg.arg1;
                    fragment.get().downloadSpeedLimit = msg.arg2;
                    break;
                }
                default:
                    super.handleMessage(msg);
                }

            } catch (Exception e) {
                /* Ignore */
            }
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == SAVE_TORRENT_FILE_CHOOSE_REQUEST && resultCode == Activity.RESULT_OK) {
            if (data.hasExtra(FileManagerDialog.TAG_RETURNED_PATH)) {
                String path = data.getStringExtra(FileManagerDialog.TAG_RETURNED_PATH);

                if (path != null && torrentId != null && torrent != null) {
                    try {
                        if (TorrentUtils.copyTorrentFile(activity.getApplicationContext(), torrentId, path,
                                torrent.getName() + ".torrent")) {
                            showSnackbar(getString(R.string.save_torrent_file_successfully), Snackbar.LENGTH_SHORT);
                        } else {
                            saveErrorTorrentFileDialog(null);
                        }

                    } catch (IOException e) {
                        saveErrorTorrentFileDialog(e);
                    }
                }
            }
        }
    }

    public void onBackPressed() {
        finish(new Intent(), FragmentCallback.ResultCode.BACK);
    }

    private void finish(Intent intent, FragmentCallback.ResultCode code) {
        ((FragmentCallback) activity).fragmentFinished(intent, code);
    }
}