Java tutorial
/* * 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.app.FragmentManager; import android.app.FragmentTransaction; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; 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.Parcelable; import android.os.RemoteException; import android.support.annotation.Nullable; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.Snackbar; import android.support.design.widget.TextInputEditText; import android.support.design.widget.TextInputLayout; import android.support.v4.view.MenuItemCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; import android.support.v7.widget.Toolbar; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.util.Patterns; import android.view.ActionMode; import android.view.ContextMenu; 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.AdapterView; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; import com.github.clans.fab.FloatingActionButton; import com.github.clans.fab.FloatingActionMenu; import org.proninyaroslav.libretorrent.*; import org.proninyaroslav.libretorrent.adapters.ToolbarSpinnerAdapter; import org.proninyaroslav.libretorrent.adapters.TorrentListAdapter; import org.proninyaroslav.libretorrent.core.Torrent; import org.proninyaroslav.libretorrent.core.TorrentStateCode; import org.proninyaroslav.libretorrent.core.stateparcel.TorrentStateParcel; import org.proninyaroslav.libretorrent.core.TorrentTaskServiceIPC; import org.proninyaroslav.libretorrent.core.exceptions.FileAlreadyExistsException; import org.proninyaroslav.libretorrent.core.utils.Utils; import org.proninyaroslav.libretorrent.customviews.EmptyRecyclerView; import org.proninyaroslav.libretorrent.customviews.RecyclerViewDividerDecoration; 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.receivers.NotificationReceiver; import org.proninyaroslav.libretorrent.services.TorrentTaskService; import org.proninyaroslav.libretorrent.settings.SettingsActivity; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; /* * The list of torrents. */ public class MainFragment extends Fragment implements TorrentListAdapter.ViewHolder.ClickListener, BaseAlertDialog.OnClickListener, BaseAlertDialog.OnDialogShowListener { @SuppressWarnings("unused") private static final String TAG = MainFragment.class.getSimpleName(); private static final String TAG_PREV_IMPL_INTENT = "prev_impl_intent"; private static final String TAG_SELECTABLE_ADAPTER = "selectable_adapter"; private static final String TAG_SELECTED_TORRENTS = "selected_torrents"; private static final String TAG_IN_ACTION_MODE = "in_action_mode"; private static final String TAG_DELETE_TORRENT_DIALOG = "delete_torrent_dialog"; private static final String TAG_ADD_LINK_DIALOG = "add_link_dialog"; private static final String TAG_ERROR_OPEN_TORRENT_FILE_DIALOG = "error_open_torrent_file_dialog"; private static final String TAG_SAVE_ERROR_DIALOG = "save_error_dialog"; private static final String TAG_TORRENTS_LIST_STATE = "torrents_list_state"; private static final String TAG_ABOUT_DIALOG = "about_dialog"; private static final int ADD_TORRENT_REQUEST = 1; private static final int TORRENT_FILE_CHOOSE_REQUEST = 2; private AppCompatActivity activity; private Toolbar toolbar; private FloatingActionMenu addTorrentButton; private FloatingActionButton openFileButton; private FloatingActionButton addLinkButton; private SearchView searchView; private CoordinatorLayout coordinatorLayout; private LinearLayoutManager layoutManager; private EmptyRecyclerView torrentsList; /* Save state scrolling */ private Parcelable torrentsListState; private Map<String, TorrentStateParcel> torrentStates = new HashMap<>(); private TorrentListAdapter adapter; private ActionMode actionMode; private ActionModeCallback actionModeCallback = new ActionModeCallback(); private boolean inActionMode = false; private ArrayList<String> selectedTorrents = new ArrayList<>(); private ToolbarSpinnerAdapter spinnerAdapter; private Spinner spinner; private boolean addTorrentMenu = false; /* Prevents re-adding the torrent, obtained through implicit intent */ private Intent prevImplIntent; /* Messenger for communicating with the service. */ 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; private ReentrantLock sync; /* * Torrents are added to the queue, if the client is not bounded to service. * Trying to add torrents will be made at the first connect. */ private HashSet<Torrent> torrentsQueue = new HashSet<>(); private Exception sentError; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_main, container, false); coordinatorLayout = (CoordinatorLayout) v.findViewById(R.id.main_coordinator_layout); return v; } @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof AppCompatActivity) { activity = (AppCompatActivity) context; } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (activity == null) { activity = (AppCompatActivity) getActivity(); } showBlankFragment(); sync = new ReentrantLock(); toolbar = (Toolbar) activity.findViewById(R.id.toolbar); if (toolbar != null) { toolbar.setTitle(R.string.app_name); } View spinnerContainer = LayoutInflater.from(activity).inflate(R.layout.toolbar_spinner, toolbar, false); ActionBar.LayoutParams lp = new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); toolbar.addView(spinnerContainer, lp); spinnerAdapter = new ToolbarSpinnerAdapter(activity); spinnerAdapter.addItems(getSpinnerList()); spinner = (Spinner) spinnerContainer.findViewById(R.id.toolbar_spinner); spinner.setAdapter(spinnerAdapter); spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { setTorrentListFilter(spinnerAdapter.getItem(i)); } @Override public void onNothingSelected(AdapterView<?> adapterView) { /* Nothing */ } }); activity.setSupportActionBar(toolbar); setHasOptionsMenu(true); addTorrentButton = (FloatingActionMenu) activity.findViewById(R.id.add_torrent_button); addTorrentButton.setClosedOnTouchOutside(true); openFileButton = (FloatingActionButton) activity.findViewById(R.id.open_file_button); openFileButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { addTorrentButton.close(true); torrentFileChooserDialog(); } }); addLinkButton = (FloatingActionButton) activity.findViewById(R.id.add_link_button); addLinkButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { addTorrentButton.close(true); addLinkDialog(); } }); if (savedInstanceState != null) { prevImplIntent = savedInstanceState.getParcelable(TAG_PREV_IMPL_INTENT); } activity.bindService(new Intent(activity.getApplicationContext(), TorrentTaskService.class), connection, Context.BIND_AUTO_CREATE); torrentsList = (EmptyRecyclerView) activity.findViewById(R.id.torrent_list); layoutManager = new LinearLayoutManager(activity); torrentsList.setLayoutManager(layoutManager); /* * A RecyclerView by default creates another copy of the ViewHolder in order to * fade the views into each other. This causes the problem because the old ViewHolder gets * the payload but then the new one doesn't. So needs to explicitly tell it to reuse the old one. */ DefaultItemAnimator animator = new DefaultItemAnimator() { @Override public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) { return true; } }; torrentsList.setItemAnimator(animator); torrentsList.addItemDecoration(new RecyclerViewDividerDecoration(activity.getApplicationContext(), R.drawable.torrent_list_divider)); torrentsList.setEmptyView(activity.findViewById(R.id.empty_view_torrent_list)); adapter = new TorrentListAdapter(new ArrayList<TorrentStateParcel>(), activity, R.layout.item_torrent_list, this); setTorrentListFilter((String) spinner.getSelectedItem()); torrentsList.setAdapter(adapter); Intent i = activity.getIntent(); /* If add torrent dialog has been called by an implicit intent */ if (i != null && i.hasExtra(AddTorrentActivity.TAG_RESULT_TORRENT)) { if (prevImplIntent == null || !prevImplIntent.equals(i)) { prevImplIntent = i; Torrent torrent = i.getParcelableExtra(AddTorrentActivity.TAG_RESULT_TORRENT); if (torrent != null) { ArrayList<Torrent> list = new ArrayList<>(); list.add(torrent); addTorrentsRequest(list); } } } else if (i != null && i.getAction() != null) { switch (i.getAction()) { case NotificationReceiver.NOTIFY_ACTION_ADD_TORRENT: addTorrentMenu = true; /* Prevents re-reading action after device configuration changes */ i.setAction(null); break; } } /* Show add torrent menu (called from service) after window is displayed */ activity.getWindow().findViewById(android.R.id.content).post(new Runnable() { @Override public void run() { if (addTorrentMenu) { /* Hide notification bar */ activity.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); addTorrentMenu = false; View v = activity.getWindow().findViewById(android.R.id.content); registerForContextMenu(v); activity.openContextMenu(v); unregisterForContextMenu(v); } } }); if (savedInstanceState != null) { selectedTorrents = savedInstanceState.getStringArrayList(TAG_SELECTED_TORRENTS); if (savedInstanceState.getBoolean(TAG_IN_ACTION_MODE, false)) { actionMode = activity.startActionMode(actionModeCallback); adapter.setSelectedItems(savedInstanceState.getIntegerArrayList(TAG_SELECTABLE_ADAPTER)); actionMode.setTitle(String.valueOf(adapter.getSelectedItemCount())); } } } @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 onResume() { super.onResume(); if (torrentsListState != null) { layoutManager.onRestoreInstanceState(torrentsListState); } } @Override public void onSaveInstanceState(Bundle outState) { outState.putParcelable(TAG_PREV_IMPL_INTENT, prevImplIntent); outState.putIntegerArrayList(TAG_SELECTABLE_ADAPTER, adapter.getSelectedItems()); outState.putBoolean(TAG_IN_ACTION_MODE, inActionMode); outState.putStringArrayList(TAG_SELECTED_TORRENTS, selectedTorrents); torrentsListState = layoutManager.onSaveInstanceState(); outState.putParcelable(TAG_TORRENTS_LIST_STATE, torrentsListState); super.onSaveInstanceState(outState); } @Override public void onViewStateRestored(Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); if (savedInstanceState != null) { torrentsListState = savedInstanceState.getParcelable(TAG_TORRENTS_LIST_STATE); } } private ServiceConnection connection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { serviceCallback = new Messenger(service); bound = true; if (!torrentsQueue.isEmpty()) { addTorrentsRequest(torrentsQueue); torrentsQueue.clear(); } try { ipc.sendClientConnect(serviceCallback, clientCallback); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(e)); } } public void onServiceDisconnected(ComponentName className) { serviceCallback = null; bound = false; } }; static class CallbackHandler extends Handler { WeakReference<MainFragment> fragment; public CallbackHandler(MainFragment fragment) { this.fragment = new WeakReference<>(fragment); } @Override public void handleMessage(Message msg) { if (fragment.get() == null) { return; } Bundle b; TorrentStateParcel state; 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) { fragment.get().torrentStates.clear(); for (String key : states.keySet()) { state = states.getParcelable(key); if (state != null) { fragment.get().torrentStates.put(state.torrentId, state); } } fragment.get().reloadAdapter(); } break; } case TorrentTaskServiceIPC.UPDATE_STATE: b = msg.getData(); b.setClassLoader(TorrentStateParcel.class.getClassLoader()); state = b.getParcelable(TorrentTaskServiceIPC.TAG_STATE); if (state != null) { fragment.get().torrentStates.put(state.torrentId, state); fragment.get().reloadAdapterItem(state); } break; case TorrentTaskServiceIPC.TERMINATE_ALL_CLIENTS: fragment.get().finish(new Intent(), FragmentCallback.ResultCode.SHUTDOWN); break; case TorrentTaskServiceIPC.TORRENTS_ADDED: { b = msg.getData(); b.setClassLoader(TorrentStateParcel.class.getClassLoader()); List<TorrentStateParcel> states = b.getParcelableArrayList(TorrentTaskServiceIPC.TAG_STATES_LIST); if (states != null && !states.isEmpty()) { for (TorrentStateParcel s : states) { fragment.get().torrentStates.put(s.torrentId, s); } fragment.get().reloadAdapter(); } Object o = b.getSerializable(TorrentTaskServiceIPC.TAG_EXCEPTIONS_LIST); if (o != null) { ArrayList<Throwable> exceptions = (ArrayList<Throwable>) o; for (Throwable e : exceptions) { fragment.get().saveTorrentError(e); } } break; } default: super.handleMessage(msg); } } } private class ActionModeCallback implements ActionMode.Callback { @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { inActionMode = true; mode.getMenuInflater().inflate(R.menu.main_action_mode, menu); Utils.showActionModeStatusBar(activity, true); return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { ArrayList<Integer> indexes = adapter.getSelectedItems(); switch (item.getItemId()) { case R.id.delete_torrent_menu: mode.finish(); if (getFragmentManager().findFragmentByTag(TAG_DELETE_TORRENT_DIALOG) == null) { BaseAlertDialog deleteTorrentDialog = BaseAlertDialog.newInstance(getString(R.string.deleting), (indexes.size() > 1 ? getString(R.string.delete_selected_torrents) : getString(R.string.delete_selected_torrent)), R.layout.dialog_delete_torrent, getString(R.string.ok), getString(R.string.cancel), null, R.style.BaseTheme_Dialog, MainFragment.this); deleteTorrentDialog.show(getFragmentManager(), TAG_DELETE_TORRENT_DIALOG); } break; case R.id.select_all_torrent_menu: for (int i = 0; i < adapter.getItemCount(); i++) { if (adapter.isSelected(i)) { continue; } onItemSelected(adapter.getItem(i).torrentId, i); } break; case R.id.force_recheck_torrent_menu: mode.finish(); forceRecheckRequest(); break; case R.id.force_announce_torrent_menu: mode.finish(); forceAnnounceRequest(); break; } return true; } @Override public void onDestroyActionMode(ActionMode mode) { adapter.clearSelection(); actionMode = null; inActionMode = false; Utils.showActionModeStatusBar(activity, false); } } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); activity.getMenuInflater().inflate(R.menu.main_context, menu); } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.add_link_menu: addLinkDialog(); break; case R.id.open_file_menu: torrentFileChooserDialog(); break; } return true; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.main, menu); searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search)); searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { adapter.search(query); /* Submit the search will hide the keyboard */ searchView.clearFocus(); return true; } @Override public boolean onQueryTextChange(String newText) { adapter.search(newText); return true; } }); searchView.setQueryHint(getString(R.string.search)); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.settings_menu: startActivity(new Intent(activity, SettingsActivity.class)); break; case R.id.about_menu: aboutDialog(); break; case R.id.shutdown_app_menu: activity.stopService(new Intent(activity.getApplicationContext(), TorrentTaskService.class)); /* FIXME: Fix leaking popup menu after close app */ activity.closeOptionsMenu(); finish(new Intent(), FragmentCallback.ResultCode.OK); break; } return true; } private void aboutDialog() { if (getFragmentManager().findFragmentByTag(TAG_ABOUT_DIALOG) == null) { BaseAlertDialog aboutDialog = BaseAlertDialog.newInstance(getString(R.string.about_title), null, R.layout.dialog_about, getString(R.string.ok), getString(R.string.about_changelog), null, R.style.BaseTheme_Dialog, this); aboutDialog.show(getFragmentManager(), TAG_ABOUT_DIALOG); } } private void addLinkDialog() { if (getFragmentManager().findFragmentByTag(TAG_ADD_LINK_DIALOG) == null) { BaseAlertDialog addLinkDialog = BaseAlertDialog.newInstance(getString(R.string.dialog_add_link_title), null, R.layout.dialog_text_input, getString(R.string.ok), getString(R.string.cancel), null, R.style.BaseTheme_Dialog, this); addLinkDialog.show(getFragmentManager(), TAG_ADD_LINK_DIALOG); } } private boolean checkEditTextField(String s, TextInputLayout layout) { if (s == null || layout == null) { return false; } if (TextUtils.isEmpty(s)) { layout.setErrorEnabled(true); layout.setError(getString(R.string.error_empty_link)); layout.requestFocus(); return false; } if (s.startsWith(Utils.MAGNET_PREFIX)) { layout.setErrorEnabled(false); layout.setError(null); return true; } if (!Patterns.WEB_URL.matcher(s).matches()) { layout.setErrorEnabled(true); layout.setError(getString(R.string.error_invalid_link)); layout.requestFocus(); return false; } layout.setErrorEnabled(false); layout.setError(null); return true; } /* * Returns a list of torrents sorting categories for spinner. */ private List<String> getSpinnerList() { List<String> categories = new ArrayList<String>(); categories.add(getString(R.string.spinner_all_torrents)); categories.add(getString(R.string.spinner_downloading_torrents)); categories.add(getString(R.string.spinner_downloaded_torrents)); return categories; } private void setTorrentListFilter(String filter) { if (filter == null) { return; } if (filter.equals(getString(R.string.spinner_downloading_torrents))) { adapter.setDisplayFilter(new TorrentListAdapter.DisplayFilter(TorrentStateCode.DOWNLOADING)); } else if (filter.equals(getString(R.string.spinner_downloaded_torrents))) { adapter.setDisplayFilter(new TorrentListAdapter.DisplayFilter(TorrentStateCode.SEEDING)); } else { adapter.setDisplayFilter(new TorrentListAdapter.DisplayFilter()); } } @Override public void onPauseButtonClicked(int position, TorrentStateParcel torrentState) { pauseResumeTorrentRequest(torrentState.torrentId); } @Override public void onItemClicked(int position, TorrentStateParcel torrentState) { if (actionMode == null) { /* Mark this torrent as open in the list */ adapter.markAsOpen(torrentState); showDetailTorrent(torrentState.torrentId); } else { onItemSelected(torrentState.torrentId, position); } } @Override public boolean onItemLongClicked(int position, TorrentStateParcel torrentState) { if (actionMode == null) { actionMode = activity.startActionMode(actionModeCallback); } onItemSelected(torrentState.torrentId, position); return true; } private void onItemSelected(String id, int position) { toggleSelection(position); if (selectedTorrents.contains(id)) { selectedTorrents.remove(id); } else { selectedTorrents.add(id); } } private void toggleSelection(int position) { adapter.toggleSelection(position); int count = adapter.getSelectedItemCount(); if (count == 0) { actionMode.finish(); } else { actionMode.setTitle(String.valueOf(count)); actionMode.invalidate(); } } /* * Uncheck current torrent from the list and set blank fragment. */ public void resetCurOpenTorrent() { adapter.markAsOpen(null); showBlankFragment(); } @Override public void onShow(final AlertDialog dialog) { if (dialog != null) { if (getFragmentManager().findFragmentByTag(TAG_ADD_LINK_DIALOG) != null) { initAddDialog(dialog); } else if (getFragmentManager().findFragmentByTag(TAG_ABOUT_DIALOG) != null) { initAboutDialog(dialog); } } } private void initAddDialog(final AlertDialog dialog) { final TextInputEditText field = (TextInputEditText) dialog.findViewById(R.id.text_input_dialog); final TextInputLayout fieldLayout = (TextInputLayout) dialog.findViewById(R.id.layout_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); } @Override public void afterTextChanged(Editable s) { /* Nothing */ } }); } /* * It is necessary in order to the dialog is not closed by * pressing positive button if the text checker gave a false result */ Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); positiveButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (field != null && fieldLayout != null) { String link = field.getText().toString(); if (checkEditTextField(link, fieldLayout)) { String url; if (link.startsWith(Utils.MAGNET_PREFIX)) { url = link; } else { url = Utils.normalizeURL(link); } if (url != null) { addTorrentDialog(Uri.parse(url)); } dialog.dismiss(); } } } }); /* Inserting a link from the clipboard */ String clipboard = Utils.getClipboard(activity.getApplicationContext()); String url; if (clipboard != null) { if (!clipboard.startsWith(Utils.MAGNET_PREFIX)) { url = Utils.normalizeURL(clipboard); } else { url = clipboard; } if (field != null && url != null) { field.setText(url); } } } private void initAboutDialog(final AlertDialog dialog) { TextView version = (TextView) dialog.findViewById(R.id.about_version); if (version != null) { try { PackageInfo info = activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0); version.setText(info.versionName); } catch (PackageManager.NameNotFoundException e) { /* Ignore */ } } } @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); DetailTorrentFragment f = getCurrentDetailFragment(); if (f != null) { String id = f.getTorrentId(); if (selectedTorrents.contains(id)) { resetCurOpenTorrent(); } } deleteTorrentsRequest(withFiles.isChecked()); selectedTorrents.clear(); } else if (getFragmentManager().findFragmentByTag(TAG_ERROR_OPEN_TORRENT_FILE_DIALOG) != null || getFragmentManager().findFragmentByTag(TAG_SAVE_ERROR_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) { if (getFragmentManager().findFragmentByTag(TAG_DELETE_TORRENT_DIALOG) != null) { selectedTorrents.clear(); } else if (getFragmentManager().findFragmentByTag(TAG_ABOUT_DIALOG) != null) { Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse(getString(R.string.about_changelog_link))); startActivity(i); } } @Override public void onNeutralClicked(@Nullable View v) { /* Nothing */ } private void reloadAdapterItem(TorrentStateParcel state) { sync.lock(); try { adapter.updateItem(state); } finally { sync.unlock(); } } final synchronized void reloadAdapter() { adapter.clearAll(); if (torrentStates == null || torrentStates.size() == 0) { adapter.notifyDataSetChanged(); } else { adapter.addItems(torrentStates.values()); } } private void torrentFileChooserDialog() { Intent i = new Intent(activity, FileManagerDialog.class); List<String> fileType = new ArrayList<>(); fileType.add("torrent"); FileManagerConfig config = new FileManagerConfig(null, getString(R.string.torrent_file_chooser_title), fileType, FileManagerConfig.FILE_CHOOSER_MODE); i.putExtra(FileManagerDialog.TAG_CONFIG, config); startActivityForResult(i, TORRENT_FILE_CHOOSE_REQUEST); } private void addTorrentDialog(Uri uri) { if (uri == null) { return; } Intent i = new Intent(activity, AddTorrentActivity.class); i.putExtra(AddTorrentActivity.TAG_URI, uri); startActivityForResult(i, ADD_TORRENT_REQUEST); } private void showDetailTorrent(String id) { if (Utils.isTwoPane(activity.getApplicationContext())) { FragmentManager fm = getFragmentManager(); DetailTorrentFragment detail = DetailTorrentFragment.newInstance(id); Fragment fragment = fm.findFragmentById(R.id.detail_torrent_fragmentContainer); if (fragment != null && fragment instanceof DetailTorrentFragment) { String oldId = ((DetailTorrentFragment) fragment).getTorrentId(); if (oldId != null && id.equals(oldId)) { return; } } fm.beginTransaction().replace(R.id.detail_torrent_fragmentContainer, detail) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE).commit(); } else { Intent i = new Intent(activity, DetailTorrentActivity.class); i.putExtra(DetailTorrentActivity.TAG_TORRENT_ID, id); startActivity(i); } } private void showBlankFragment() { if (Utils.isTablet(activity.getApplicationContext())) { FragmentManager fm = getFragmentManager(); BlankFragment blank = BlankFragment.newInstance(); fm.beginTransaction().replace(R.id.detail_torrent_fragmentContainer, blank) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE).commitAllowingStateLoss(); } } public DetailTorrentFragment getCurrentDetailFragment() { if (!Utils.isTwoPane(activity.getApplicationContext())) { return null; } Fragment fragment = getFragmentManager().findFragmentById(R.id.detail_torrent_fragmentContainer); return (fragment instanceof DetailTorrentFragment ? (DetailTorrentFragment) fragment : null); } private void deleteTorrentsRequest(boolean withFiles) { if (!bound || serviceCallback == null) { return; } try { ipc.sendDeleteTorrents(serviceCallback, selectedTorrents, withFiles); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(e)); } } private void forceRecheckRequest() { if (!bound || serviceCallback == null) { return; } try { ipc.sendForceRecheck(serviceCallback, selectedTorrents); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(e)); } selectedTorrents.clear(); } private void forceAnnounceRequest() { if (!bound || serviceCallback == null) { return; } try { ipc.sendForceAnnounce(serviceCallback, selectedTorrents); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(e)); } selectedTorrents.clear(); } private void addTorrentsRequest(Collection<Torrent> torrents) { if (!bound || serviceCallback == null) { torrentsQueue.addAll(torrents); return; } try { ipc.sendAddTorrents(serviceCallback, new ArrayList<>(torrents)); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(e)); } } private void saveTorrentError(Throwable e) { if (e == null) { return; } sentError = new Exception(e); if (e instanceof FileNotFoundException) { ErrorReportAlertDialog errDialog = ErrorReportAlertDialog.newInstance(activity.getApplicationContext(), getString(R.string.error), getString(R.string.error_file_not_found_add_torrent), Log.getStackTraceString(e), R.style.BaseTheme_Dialog, this); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.add(errDialog, TAG_SAVE_ERROR_DIALOG); ft.commitAllowingStateLoss(); } else if (e instanceof IOException) { ErrorReportAlertDialog errDialog = ErrorReportAlertDialog.newInstance(activity.getApplicationContext(), getString(R.string.error), getString(R.string.error_io_add_torrent), Log.getStackTraceString(e), R.style.BaseTheme_Dialog, this); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.add(errDialog, TAG_SAVE_ERROR_DIALOG); ft.commitAllowingStateLoss(); } else if (e instanceof FileAlreadyExistsException) { Snackbar.make(coordinatorLayout, R.string.torrent_exist, Snackbar.LENGTH_LONG).show(); } else { ErrorReportAlertDialog errDialog = ErrorReportAlertDialog.newInstance(activity.getApplicationContext(), getString(R.string.error), getString(R.string.error_add_torrent), Log.getStackTraceString(e), R.style.BaseTheme_Dialog, this); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.add(errDialog, TAG_SAVE_ERROR_DIALOG); ft.commitAllowingStateLoss(); } } private void pauseResumeTorrentRequest(String id) { if (!bound || serviceCallback == null) { return; } ArrayList<String> list = new ArrayList<>(); list.add(id); try { ipc.sendPauseResumeTorrents(serviceCallback, list); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(e)); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case TORRENT_FILE_CHOOSE_REQUEST: if (resultCode == Activity.RESULT_OK) { if (data.hasExtra(FileManagerDialog.TAG_RETURNED_PATH)) { String path = data.getStringExtra(FileManagerDialog.TAG_RETURNED_PATH); try { addTorrentDialog(Uri.fromFile(new File(path))); } catch (Exception e) { sentError = e; Log.e(TAG, Log.getStackTraceString(e)); if (getFragmentManager().findFragmentByTag(TAG_ERROR_OPEN_TORRENT_FILE_DIALOG) == null) { ErrorReportAlertDialog errDialog = ErrorReportAlertDialog.newInstance( activity.getApplicationContext(), getString(R.string.error), getString(R.string.error_open_torrent_file), Log.getStackTraceString(e), R.style.BaseTheme_Dialog, this); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.add(errDialog, TAG_ERROR_OPEN_TORRENT_FILE_DIALOG); ft.commitAllowingStateLoss(); } } } } break; case ADD_TORRENT_REQUEST: if (resultCode == Activity.RESULT_OK) { if (data.hasExtra(AddTorrentActivity.TAG_RESULT_TORRENT)) { Torrent torrent = data.getParcelableExtra(AddTorrentActivity.TAG_RESULT_TORRENT); if (torrent != null) { ArrayList<Torrent> list = new ArrayList<>(); list.add(torrent); addTorrentsRequest(list); } } } break; } } private void finish(Intent intent, FragmentCallback.ResultCode code) { ((FragmentCallback) activity).fragmentFinished(intent, code); } }