package org.sugr.gearshift.ui; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.view.ViewPager; import android.support.v7.app.AlertDialog; import android.text.Html; 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.CheckBox; import android.widget.TextView; import org.sugr.gearshift.G; import org.sugr.gearshift.R; import org.sugr.gearshift.core.Torrent; import org.sugr.gearshift.core.TransmissionProfile; import org.sugr.gearshift.core.TransmissionSession; import org.sugr.gearshift.datasource.DataSource; import org.sugr.gearshift.datasource.TorrentNameStatus; import org.sugr.gearshift.service.DataService; import org.sugr.gearshift.service.DataServiceManager; import org.sugr.gearshift.service.DataServiceManagerInterface; import org.sugr.gearshift.ui.util.LocationDialogHelper; import org.sugr.gearshift.ui.util.LocationDialogHelperInterface; import org.sugr.gearshift.ui.util.QueueManagementDialogHelperInterface; import java.util.Arrays; import java.util.HashMap; public class TorrentDetailFragment extends Fragment implements TorrentListNotificationInterface { public static final String ARG_SHOW_PAGER = "show_pager"; public interface PagerCallbacks { void onPageSelected(int position); } private PagerCallbacks pagerCallbacks = dummyCallbacks; private ViewPager pager; private String currentTorrentHashString; private int currentTorrentPosition = 0; private int currentTorrentStatus; private String currentTorrentName; private boolean showPager = false; private static final String STATE_ACTION_MOVE_HASH_STRINGS= "action_move_hash_strings"; private String[] actionMoveHashStrings; private Menu menu; private String[] torrentHashStrings; private HashMap<String, Integer> torrentPositionMap = new HashMap<>(); private static PagerCallbacks dummyCallbacks = position -> { }; /** * Mandatory empty constructor for the fragment manager to instantiate the * fragment (e.g. upon screen orientation changes). */ public TorrentDetailFragment() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments().containsKey(G.ARG_PAGE_POSITION)) { currentTorrentPosition = getArguments().getInt(G.ARG_PAGE_POSITION); if (currentTorrentPosition < 0) { currentTorrentPosition = 0; } setHasOptionsMenu(true); } if (savedInstanceState != null) { if (savedInstanceState.containsKey(STATE_ACTION_MOVE_HASH_STRINGS)) { actionMoveHashStrings = savedInstanceState.getStringArray(STATE_ACTION_MOVE_HASH_STRINGS); if (actionMoveHashStrings != null) { showMoveDialog(actionMoveHashStrings); } } } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View root = inflater.inflate(R.layout.fragment_torrent_detail, container, false); pager = (ViewPager) root.findViewById(R.id.torrent_detail_pager); pager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { if (currentTorrentPosition != -1) { Intent intent = new Intent(G.INTENT_PAGE_UNSELECTED); intent.putExtra(G.ARG_TORRENT_HASH_STRING, currentTorrentPosition); LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent); } currentTorrentPosition = position; setCurrentTorrentHashString(position); pagerCallbacks.onPageSelected(position); new QueryCurrentDataTask().execute(currentTorrentHashString); } }); if (getArguments().containsKey(ARG_SHOW_PAGER)) { if (getArguments().getBoolean(ARG_SHOW_PAGER)) { showPager = true; } } return root; } @Override public void onAttach(Activity activity) { super.onAttach(activity); // Activities containing this fragment must implement its callbacks. if (!(activity instanceof PagerCallbacks)) { throw new IllegalStateException("Activity must implement fragment's callbacks."); } pagerCallbacks = (PagerCallbacks) activity; } @Override public void onDetach() { super.onDetach(); // Reset the active callbacks interface to the dummy implementation. pagerCallbacks = dummyCallbacks; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); this.menu = menu; inflater.inflate(R.menu.torrent_detail_fragment, menu); setMenuTorrentState(); } @Override public boolean onOptionsItemSelected(final MenuItem item) { final DataServiceManager manager = ((DataServiceManagerInterface) getActivity()).getDataServiceManager(); final TransmissionSessionInterface context = ((TransmissionSessionInterface) getActivity()); if (manager == null || torrentHashStrings == null || torrentHashStrings.length <= currentTorrentPosition) { return super.onOptionsItemSelected(item); } final String[] hashStrings = new String[] { currentTorrentHashString }; G.TorrentAction action; switch (item.getItemId()) { case R.id.remove: ViewGroup v = (ViewGroup) getActivity().getLayoutInflater().inflate(R.layout.remove_torrent_dialog, null); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) .setCancelable(false) .setView(v) .setNegativeButton(android.R.string.no, null); ((TextView) v.findViewById(R.id.remove_torrent_text)).setText( String.format(getString(R.string.remove_current_confirmation), G.trimTrailingWhitespace(Html.fromHtml(currentTorrentName)))); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); ((CheckBox) v.findViewById(R.id.remove_data)).setChecked(prefs.getBoolean(G.PREF_DELETE_DATA, false)); builder.setPositiveButton(android.R.string.yes, (dialog, id) -> { boolean removeData = ((CheckBox) ((AlertDialog) dialog).findViewById(R.id.remove_data)) .isChecked(); manager.removeTorrent(hashStrings, removeData); context.setRefreshing(true, DataService.Requests.REMOVE_TORRENT); }).show(); return true; case R.id.resume: switch(currentTorrentStatus) { case Torrent.Status.DOWNLOAD_WAITING: case Torrent.Status.SEED_WAITING: action = G.TorrentAction.START_NOW; break; default: action = G.TorrentAction.START; break; } break; case R.id.pause: action = G.TorrentAction.STOP; break; case R.id.move: actionMoveHashStrings = hashStrings; return showMoveDialog(hashStrings); case R.id.verify: action = G.TorrentAction.VERIFY; break; case R.id.reannounce: action = G.TorrentAction.REANNOUNCE; break; case R.id.queue: return showQueueManagementDialog(hashStrings); default: return false; } manager.setTorrentAction(hashStrings, action); context.setRefreshing(true, DataService.Requests.SET_TORRENT_ACTION); return true; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putStringArray(STATE_ACTION_MOVE_HASH_STRINGS, actionMoveHashStrings); } public boolean changeCursor(Cursor newCursor) { boolean equal = updateTorrentData(newCursor); if (pager.getAdapter() == null) { setCurrentTorrentHashString(currentTorrentPosition); resetPagerAdapter(); setMenuTorrentState(); } return equal; } public int getTorrentPositionInCursor(String hash) { Integer position = torrentPositionMap.get(hash); return position == null ? -1 : position; } public String getTorrentHashString(int position) { return torrentHashStrings != null && torrentHashStrings.length > position && position != -1 ? torrentHashStrings[position] : null; } public void removeMenuEntries() { if (menu == null) { return; } menu.removeItem(R.id.resume); menu.removeItem(R.id.pause); menu.removeItem(R.id.remove); menu.removeItem(R.id.delete); menu.removeItem(R.id.move); menu.removeItem(R.id.verify); menu.removeItem(R.id.reannounce); } public void setCurrentTorrent(int position) { if (pager.getAdapter() == null) { currentTorrentPosition = position; return; } if (position == pager.getCurrentItem()) { if (position != currentTorrentPosition) { currentTorrentPosition = position; setCurrentTorrentHashString(position); new QueryCurrentDataTask().execute(currentTorrentHashString); } } else { pager.setCurrentItem(position); } } private boolean updateTorrentData(Cursor cursor) { int cursorPosition = cursor.getPosition(); int position = -1; String[] oldHashStrings = torrentHashStrings; torrentHashStrings = new String[cursor.getCount()]; torrentPositionMap.clear(); cursor.moveToFirst(); while (!cursor.isAfterLast()) { String hash = Torrent.getHashString(cursor); torrentHashStrings[++position] = hash; torrentPositionMap.put(hash, position); if (currentTorrentHashString == null && position == currentTorrentPosition || hash.equals(currentTorrentHashString)) { currentTorrentStatus = Torrent.getStatus(cursor); currentTorrentName = Torrent.getName(cursor); } cursor.moveToNext(); } cursor.moveToPosition(cursorPosition); return Arrays.equals(oldHashStrings, torrentHashStrings); } public void resetPagerAdapter() { if (torrentHashStrings == null) { pager.setAdapter(null); } else { pager.setAdapter(new TorrentDetailPagerAdapter(getActivity(), torrentHashStrings.length)); pager.setCurrentItem(currentTorrentPosition); if (showPager) { showPager = false; pager.setVisibility(View.VISIBLE); pager.animate().alpha((float) 1.0); } } } public void notifyTorrentListChanged(Cursor cursor, int error, boolean added, boolean removed, boolean status, boolean metadata, boolean connected) { TransmissionSessionInterface context = ((TransmissionSessionInterface) getActivity()); if (context == null || context.getSession() == null || cursor == null || cursor.isClosed()) { setMenuTorrentState(); return; } boolean updateMenu = status; if (!changeCursor(cursor)) { int position = getTorrentPositionInCursor(currentTorrentHashString); resetPagerAdapter(); if (position != -1) { pager.setCurrentItem(position); } else { updateMenu = true; int cursorPosition = cursor.getPosition(); cursor.moveToPosition(currentTorrentPosition); if (!cursor.isAfterLast()) { currentTorrentHashString = Torrent.getHashString(cursor); currentTorrentName = Torrent.getName(cursor); currentTorrentStatus = Torrent.getStatus(cursor); } cursor.moveToPosition(cursorPosition); } new Handler().post(() -> pagerCallbacks.onPageSelected(pager.getCurrentItem())); } if (updateMenu) { setMenuTorrentState(); } if (currentTorrentPosition != -1) { int limit = pager.getOffscreenPageLimit(); int startPosition = currentTorrentPosition - limit; if (startPosition < 0) { startPosition = 0; } for (int i = startPosition; i < currentTorrentPosition + limit + 1; ++i) { Intent intent = new Intent(G.INTENT_TORRENT_UPDATE); intent.putExtra(G.ARG_TORRENT_HASH_STRING, getTorrentHashString(i)); LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent); } } } private void setCurrentTorrentHashString(int position) { currentTorrentHashString = getTorrentHashString(position); } private void setMenuTorrentState() { if (menu == null || getActivity() == null || menu.findItem(R.id.remove) == null) { return; } boolean visible = ((TransmissionSessionInterface) getActivity()).getSession() != null; menu.findItem(R.id.remove).setVisible(visible); menu.findItem(R.id.move).setVisible(visible); menu.findItem(R.id.verify).setVisible(visible); menu.findItem(R.id.reannounce).setVisible(visible); boolean isActive = Torrent.isActive(currentTorrentStatus); boolean resumeState = false; boolean pauseState = false; if (currentTorrentHashString != null) { if (isActive) { resumeState = false; pauseState = true; } else { resumeState = true; pauseState = false; } } MenuItem item = menu.findItem(R.id.resume); item.setVisible(resumeState).setEnabled(resumeState); item = menu.findItem(R.id.pause); item.setVisible(pauseState).setEnabled(pauseState); } private boolean showMoveDialog(final String[] hashStrings) { final DataServiceManager manager = ((DataServiceManagerInterface) getActivity()).getDataServiceManager(); if (manager == null) { return true; } final TransmissionSession session = ((TransmissionSessionInterface) getActivity()).getSession(); if (session == null) { return true; } final LocationDialogHelper helper = ((LocationDialogHelperInterface) getActivity()).getLocationDialogHelper(); AlertDialog dialog = helper.showDialog(R.layout.torrent_location_dialog, R.string.set_location, (dialog1, which) -> actionMoveHashStrings = null, (dialog1, which) -> { LocationDialogHelper.Location location = helper.getLocation(); manager.setTorrentLocation(hashStrings, location.directory, location.moveData); ((TransmissionSessionInterface) getActivity()).setRefreshing(true, DataService.Requests.SET_TORRENT_LOCATION); actionMoveHashStrings = null; } ); TransmissionProfile profile = ((TransmissionProfileInterface) getActivity()).getProfile(); ((CheckBox) dialog.findViewById(R.id.move)).setChecked( profile != null && profile.getMoveData()); return true; } private boolean showQueueManagementDialog(final String[] hashStrings) { ((QueueManagementDialogHelperInterface) getActivity()).getQueueManagementDialogHelper() .showDialog(hashStrings); return true; } private class QueryCurrentDataTask extends AsyncTask<String, Void, Boolean> { @Override protected Boolean doInBackground(String... hashStrings) { if (!isCancelled() && getActivity() != null) { TransmissionProfileInterface context = (TransmissionProfileInterface) getActivity(); if (context == null) { return null; } DataSource readSource = new DataSource(getActivity()); readSource.open(); try { TorrentNameStatus tuple = readSource.getTorrentNameStatus( context.getProfile().getId(), hashStrings[0]); if (tuple != null) { currentTorrentName = tuple.name; currentTorrentStatus = tuple.status; } } finally { readSource.close(); } } return null; } @Override protected void onPostExecute(Boolean success) { if (isResumed()) { setMenuTorrentState(); } } } }