package org.sugr.gearshift.ui; import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AlertDialog; import android.support.v7.widget.SwitchCompat; import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.animation.DecelerateInterpolator; import android.widget.CheckBox; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.Toast; import org.sugr.gearshift.G; import org.sugr.gearshift.R; import org.sugr.gearshift.core.TransmissionProfile; import org.sugr.gearshift.core.TransmissionSession; import org.sugr.gearshift.service.DataService; import org.sugr.gearshift.service.DataServiceManager; import org.sugr.gearshift.ui.util.LocationDialogHelper; import org.sugr.gearshift.util.Base64; import org.sugr.gearshift.util.CheatSheet; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.List; public class TorrentListActivity extends BaseTorrentActivity implements TorrentListFragment.Callbacks { public static final String ARG_TEMPORARY_FILE = "torrent_file_uri"; public static final String ARG_TORRENT_FILE = "torrent_file_path"; public final static String ACTION_OPEN = "torrent_file_open_action"; /** * Whether or not the activity is in two-pane mode, i.e. running on a tablet * device. */ private boolean twoPaneLayout; private boolean intentConsumed = false; private boolean newTorrentDialogVisible = false; private boolean hasNewIntent = false; private static final String STATE_INTENT_CONSUMED = "intent_consumed"; private static final String STATE_CURRENT_PROFILE = "current_profile"; private static final int BROWSE_REQUEST_CODE = 1; private DrawerLayout drawerLayout; private ActionBarDrawerToggle drawerToggle; private ValueAnimator detailSlideAnimator; private boolean detailPanelVisible; private int currentTorrentIndex = -1; private boolean altSpeed = false; private int expecting = 0; private static class Expecting { static int ALT_SPEED_ON = 1; static int ALT_SPEED_OFF = 1 << 1; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); G.DEBUG = prefs.getBoolean(G.PREF_DEBUG, false); setContentView(R.layout.activity_torrent_list); PreferenceManager.setDefaultValues(this, R.xml.general_preferences, false); PreferenceManager.setDefaultValues(this, R.xml.filters_preferences, true); PreferenceManager.setDefaultValues(this, R.xml.sort_preferences, true); if (findViewById(R.id.torrent_detail_panel) != null) { // The detail container view will be present only in the // large-screen layouts (res/values-large and // res/values-sw600dp). If this view is present, then the // activity should be in two-pane mode. twoPaneLayout = true; // In two-pane mode, list items should be given the // 'activated' state when touched. ((TorrentListFragment) getSupportFragmentManager() .findFragmentById(R.id.torrent_list)) .setActivateOnItemClick(true); final LinearLayout slidingLayout = (LinearLayout) findViewById(R.id.sliding_layout); final View detailPanel = findViewById(R.id.torrent_detail_panel); detailSlideAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.weight_animator); detailSlideAnimator.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime)); detailSlideAnimator.setInterpolator(new DecelerateInterpolator()); detailSlideAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (manager == null) { return; } final FragmentManager fm = getSupportFragmentManager(); TorrentDetailFragment fragment = (TorrentDetailFragment) fm.findFragmentByTag( G.DETAIL_FRAGMENT_TAG); if (fragment == null) { fragment = new TorrentDetailFragment(); fragment.setArguments(new Bundle()); fm.beginTransaction() .replace(R.id.torrent_detail_container, fragment, G.DETAIL_FRAGMENT_TAG) .commit(); fm.executePendingTransactions(); } fragment.setCurrentTorrent(currentTorrentIndex); G.logD("Opening the detail panel"); manager.setDetails(true); new TorrentTask(TorrentListActivity.this, TorrentTask.Flags.UPDATE).execute(); fragment.onCreateOptionsMenu(menu, getMenuInflater()); Handler handler = new Handler(); handler.post(() -> { View bg=findViewById(R.id.torrent_detail_placeholder_background); if (bg != null) { bg.setVisibility(View.GONE); } View pager = findViewById(R.id.torrent_detail_pager); pager.setVisibility(View.VISIBLE); pager.animate().alpha((float) 1.0); }); } }); detailSlideAnimator.addUpdateListener(animation -> { float value = (Float) animation.getAnimatedValue(); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) detailPanel.getLayoutParams(); params.weight=value; slidingLayout.requestLayout(); }); } if (savedInstanceState == null) { setRefreshing(true, DataService.Requests.GET_TORRENTS); } else { if (savedInstanceState.containsKey(STATE_INTENT_CONSUMED)) { intentConsumed = savedInstanceState.getBoolean(STATE_INTENT_CONSUMED); } } Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getLayoutInflater().inflate(R.layout.alt_speed_switch, toolbar); getWindow().setBackgroundDrawable(null); drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_drawer, R.string.close_drawer) { }; drawerLayout.setDrawerListener(drawerToggle); drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, findViewById(R.id.sliding_menu_frame)); drawerToggle.setDrawerIndicatorEnabled(true); if (twoPaneLayout) { toggleRightPane(false); } if (savedInstanceState != null) { if (savedInstanceState.containsKey(STATE_CURRENT_PROFILE)) { profile = savedInstanceState.getParcelable(STATE_CURRENT_PROFILE); if (profile != null) { manager = new DataServiceManager(this, profile) .onRestoreInstanceState(savedInstanceState).startUpdating(); new SessionTask(this, SessionTask.Flags.START_TORRENT_TASK).execute(); } } } SwitchCompat altSpeed = (SwitchCompat) findViewById(R.id.menu_alt_speed); if (altSpeed != null) { CheatSheet.setup(altSpeed, this); altSpeed.setOnClickListener(v -> { expecting&= ~(Expecting.ALT_SPEED_ON | Expecting.ALT_SPEED_OFF); boolean altSpeed1 = TorrentListActivity.this.altSpeed; if (altSpeed1) { setAltSpeed(false); expecting|= Expecting.ALT_SPEED_OFF; } else { setAltSpeed(true); expecting|= Expecting.ALT_SPEED_ON; } session.setAltSpeedLimitEnabled(!altSpeed1); manager.setSession(session, "alt-speed-enabled"); }); } } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. drawerToggle.syncState(); } @Override protected void onResume() { super.onResume(); if (!newTorrentDialogVisible && hasNewIntent) { intentConsumed = false; /* Less than a minute ago */ long now = new Date().getTime(); if (now - lastServerActivity < 60000) { consumeIntent(); } } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); drawerToggle.onConfigurationChanged(newConfig); } /** * Callback method from {@link TorrentListFragment.Callbacks} * indicating that the item with the given ID was selected. */ @Override public void onItemSelected(int position) { if (twoPaneLayout) { currentTorrentIndex = position; if (!toggleRightPane(true)) { TorrentDetailFragment fragment = (TorrentDetailFragment) getSupportFragmentManager().findFragmentByTag( G.DETAIL_FRAGMENT_TAG); if (fragment != null) { fragment.setCurrentTorrent(currentTorrentIndex); } } } else { // In single-pane mode, simply start the detail activity // for the selected item ID. Intent detailIntent = new Intent(this, TorrentDetailActivity.class); detailIntent.putExtra(G.ARG_PAGE_POSITION, position); detailIntent.putExtra(G.ARG_PROFILE, profile); detailIntent.putExtra(G.ARG_SESSION, session); detailIntent.putExtra(G.ARG_LAST_SERVER_ACTIVITY, lastServerActivity); if (refreshing) { detailIntent.putExtra(G.ARG_REFRESH_TYPE, refreshType); } startActivity(detailIntent); overridePendingTransition(R.anim.slide_in_right, android.R.anim.fade_out); } } @Override public void onPageSelected(int position) { if (twoPaneLayout) { ((TorrentListFragment) getSupportFragmentManager() .findFragmentById(R.id.torrent_list)) .getListView().setItemChecked(position, true); } } @Override public void onBackPressed() { TorrentListFragment fragment = ((TorrentListFragment) getSupportFragmentManager() .findFragmentById(R.id.torrent_list)); int position = fragment.getListView().getCheckedItemPosition(); if (position == ListView.INVALID_POSITION) { super.onBackPressed(); } else { toggleRightPane(false); fragment.getListView().setItemChecked(position, false); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); this.menu = menu; getMenuInflater().inflate(R.menu.torrent_list_activity, menu); setSession(session); setAltSpeed(altSpeed); if (profile == null) { MenuItem item = menu.findItem(R.id.menu_refresh); item.setVisible(false); if (findViewById(R.id.swipe_container) != null) { findViewById(R.id.swipe_container).setEnabled(false); } } else { setRefreshing(refreshing, refreshType); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { if (drawerLayout.getDrawerLockMode( findViewById(R.id.sliding_menu_frame) ) == DrawerLayout.LOCK_MODE_UNLOCKED && drawerToggle.onOptionsItemSelected(item)) { return true; } switch(item.getItemId()) { case android.R.id.home: if (!twoPaneLayout) { return super.onOptionsItemSelected(item); } TorrentListFragment fragment = ((TorrentListFragment) getSupportFragmentManager() .findFragmentById(R.id.torrent_list)); int position = fragment.getListView().getCheckedItemPosition(); if (position == ListView.INVALID_POSITION) { return true; } else { toggleRightPane(false); fragment.getListView().setItemChecked(position, false); return true; } case R.id.menu_refresh: manager.update(); setRefreshing(true, DataService.Requests.GET_TORRENTS); return true; case R.id.menu_add_torrent: showAddTorrentDialog(); break; } if (twoPaneLayout) { Fragment fragment = getSupportFragmentManager().findFragmentByTag(G.DETAIL_FRAGMENT_TAG); if (fragment != null && fragment.onOptionsItemSelected(item)) { return true; } } return super.onOptionsItemSelected(item); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(STATE_INTENT_CONSUMED, intentConsumed); outState.putParcelable(STATE_CURRENT_PROFILE, profile); } @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); hasNewIntent = true; setIntent(intent); } @Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) { if (requestCode == BROWSE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { if (resultData != null) { Uri uri = resultData.getData(); new ReadTorrentDataTask().execute(uri); Toast.makeText(this, R.string.reading_torrent_file, Toast.LENGTH_SHORT).show(); } } } public boolean isDetailPanelVisible() { return twoPaneLayout && detailPanelVisible; } private boolean toggleRightPane(boolean show) { if (!twoPaneLayout) return false; View pager = findViewById(R.id.torrent_detail_pager); if (show) { if (!detailPanelVisible) { detailPanelVisible = true; detailSlideAnimator.start(); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) findViewById(R.id.torrent_list).getLayoutParams(); params.rightMargin = 0; return true; } } else { if (detailPanelVisible) { detailPanelVisible = false; LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) findViewById(R.id.torrent_detail_panel).getLayoutParams(); params.weight = 0; if (pager != null) { pager.setAlpha(0); pager.setVisibility(View.GONE); } int panelMargin = params.leftMargin; if (manager != null) { manager.setDetails(false); } FragmentManager fm = getSupportFragmentManager(); TorrentDetailFragment fragment = (TorrentDetailFragment) fm.findFragmentByTag(G.DETAIL_FRAGMENT_TAG); if (fragment != null) { fragment.removeMenuEntries(); } params = (LinearLayout.LayoutParams) findViewById(R.id.torrent_list).getLayoutParams(); params.rightMargin = -1 * panelMargin; return true; } } return false; } @Override public void setSession(TransmissionSession session) { if (session == null) { boolean sessionRemoved = false; if (this.session != null) { invalidateOptionsMenu(); sessionRemoved = true; } this.session = null; if (sessionRemoved) { LocalBroadcastManager.getInstance(this).sendBroadcast( new Intent(G.INTENT_SESSION_INVALIDATED).putExtra(G.ARG_SESSION_VALID, false)); } } else { boolean initial = false; if (this.session == null) { invalidateOptionsMenu(); initial = true; } else if (session.getRPCVersion() >= TransmissionSession.FREE_SPACE_METHOD_RPC_VERSION) { session.setDownloadDirFreeSpace(this.session.getDownloadDirFreeSpace()); } this.session = session; if (initial) { LocalBroadcastManager.getInstance(this).sendBroadcast( new Intent(G.INTENT_SESSION_INVALIDATED).putExtra(G.ARG_SESSION_VALID, true)); } if (initial && !intentConsumed && !newTorrentDialogVisible) { consumeIntent(); } } } @Override protected boolean handleSuccessServiceBroadcast(String type, Intent intent) { if (manager == null) { return false; } int flags = TorrentTask.Flags.CONNECTED; switch (type) { case DataService.Requests.GET_SESSION: new SessionTask(TorrentListActivity.this, 0).execute(); break; case DataService.Requests.SET_SESSION: manager.getSession(); break; case DataService.Requests.GET_TORRENTS: boolean added = intent.getBooleanExtra(G.ARG_ADDED, false); boolean removed = intent.getBooleanExtra(G.ARG_REMOVED, false); boolean statusChanged = intent.getBooleanExtra(G.ARG_STATUS_CHANGED, false); boolean incomplete = intent.getBooleanExtra(G.ARG_INCOMPLETE_METADATA, false); if (added) { flags |= TorrentTask.Flags.HAS_ADDED; } if (removed) { flags |= TorrentTask.Flags.HAS_REMOVED; } if (statusChanged) { flags |= TorrentTask.Flags.HAS_STATUS_CHANGED; } if (incomplete) { flags |= TorrentTask.Flags.HAS_INCOMPLETE_METADATA; } new TorrentTask(TorrentListActivity.this, flags).execute(); break; case DataService.Requests.ADD_TORRENT: manager.update(); flags |= TorrentTask.Flags.HAS_ADDED | TorrentTask.Flags.HAS_INCOMPLETE_METADATA; new TorrentTask(TorrentListActivity.this, flags).execute(); break; case DataService.Requests.REMOVE_TORRENT: manager.update(); flags |= TorrentTask.Flags.HAS_REMOVED; new TorrentTask(TorrentListActivity.this, flags).execute(); break; case DataService.Requests.SET_TORRENT_LOCATION: manager.update(); flags |= TorrentTask.Flags.HAS_ADDED | TorrentTask.Flags.HAS_REMOVED; new TorrentTask(TorrentListActivity.this, flags).execute(); break; case DataService.Requests.SET_TORRENT: case DataService.Requests.SET_TORRENT_ACTION: manager.update(); flags |= TorrentTask.Flags.HAS_STATUS_CHANGED; new TorrentTask(TorrentListActivity.this, flags).execute(); break; case DataService.Requests.GET_FREE_SPACE: long freeSpace = intent.getLongExtra(G.ARG_FREE_SPACE, 0); TransmissionSession session = getSession(); if (session != null && freeSpace != 0) { session.setDownloadDirFreeSpace(freeSpace); } break; } return true; } @Override protected boolean handleErrorServiceBroadcast(String type, int error, Intent intent) { expecting = 0; toggleRightPane(false); FragmentManager manager = getSupportFragmentManager(); TorrentListFragment fragment = (TorrentListFragment) manager.findFragmentById(R.id.torrent_list); if (fragment != null) { fragment.notifyTorrentListChanged(null, error, false, false, false, false, false); } return true; } @Override protected void onSessionTaskPostExecute(TransmissionSession session) { if (hasNewIntent && session != null) { hasNewIntent = false; if (!intentConsumed && !newTorrentDialogVisible) { consumeIntent(); } } } @Override protected void onTorrentTaskPostExecute(Cursor cursor, boolean added, boolean removed, boolean statusChanged, boolean incompleteMetadata, boolean connected) { if (session == null) { expecting = 0; } else { if (altSpeed == session.isAltSpeedLimitEnabled()) { expecting &= ~(Expecting.ALT_SPEED_ON | Expecting.ALT_SPEED_OFF); } else { if (expecting == 0 || (expecting & Expecting.ALT_SPEED_ON) > 0 && session.isAltSpeedLimitEnabled() || (expecting & Expecting.ALT_SPEED_OFF) > 0 && !session.isAltSpeedLimitEnabled()) { setAltSpeed(session.isAltSpeedLimitEnabled()); expecting &= ~(Expecting.ALT_SPEED_ON | Expecting.ALT_SPEED_OFF); } } } FragmentManager fm = getSupportFragmentManager(); TorrentListFragment fragment = (TorrentListFragment) fm.findFragmentById(R.id.torrent_list); if (fragment != null) { long now = new Date().getTime(); if (now - lastServerActivity < 30000) { connected = true; } fragment.notifyTorrentListChanged(cursor, 0, added, removed, statusChanged, incompleteMetadata, connected); } TorrentListMenuFragment menu = (TorrentListMenuFragment) fm.findFragmentById(R.id.torrent_list_menu); if (menu != null) { menu.notifyTorrentListChanged(cursor, 0, added, removed, statusChanged, incompleteMetadata, connected); } if (isDetailPanelVisible()) { TorrentDetailFragment detail = (TorrentDetailFragment) fm.findFragmentByTag( G.DETAIL_FRAGMENT_TAG); if (detail != null) { detail.notifyTorrentListChanged(cursor, 0, added, removed, statusChanged, incompleteMetadata, connected); } } } @Override public void setProfile(TransmissionProfile profile) { super.setProfile(profile); toggleRightPane(false); } @Override protected void setProfiles(List<TransmissionProfile> profileList) { super.setProfiles(profileList); FragmentManager fm = getSupportFragmentManager(); TorrentListMenuFragment menu = (TorrentListMenuFragment) fm.findFragmentById(R.id.torrent_list_menu); if (menu != null) { menu.notifyTransmissionProfileListChanged(getProfiles()); } if (profiles.size() == 0) { TorrentListFragment list = (TorrentListFragment) fm.findFragmentById(R.id.torrent_list); if (list != null) { list.setEmptyMessage(R.string.no_profiles_empty_list); } } } private void setAltSpeed(boolean alt) { if (menu == null) { return; } SwitchCompat altSpeed = (SwitchCompat) findViewById(R.id.menu_alt_speed); MenuItem addTorrent = menu.findItem(R.id.menu_add_torrent); if (altSpeed == null || addTorrent == null) { return; } this.altSpeed = alt; if (session == null) { altSpeed.setVisibility(View.GONE); addTorrent.setVisible(false); } else { altSpeed.setVisibility(View.VISIBLE); addTorrent.setVisible(true); altSpeed.setChecked(this.altSpeed); } } private void consumeIntent() { Intent intent = getIntent(); String action = intent.getAction(); if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0 && (Intent.ACTION_VIEW.equals(action) || ACTION_OPEN.equals(action))) { Uri data = intent.getData(); String temporaryFile = intent.getStringExtra(ARG_TEMPORARY_FILE); String torrentFile = intent.getStringExtra(ARG_TORRENT_FILE); showNewTorrentDialog(data, temporaryFile, torrentFile, null); } else { intentConsumed = true; } } private void showNewTorrentDialog(final Uri data, final String temporaryFile, final String torrentFile, final Uri documentUri) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); if (!prefs.getBoolean(G.PREF_SHOW_ADD_DIALOG, true)) { boolean startPaused = prefs.getBoolean(G.PREF_START_PAUSED, false); if (data != null && data.getScheme().equals("magnet")) { addMagnetLink(profile, data, null, startPaused); } else if (temporaryFile != null) { addTorrentFile(profile, temporaryFile, torrentFile, documentUri, null, startPaused, prefs.getBoolean(G.PREF_DELETE_LOCAL, false)); } else { return; } intentConsumed = true; return; } newTorrentDialogVisible = true; int title; DialogInterface.OnClickListener okListener; if (data != null && data.getScheme().equals("magnet")) { title = R.string.add_new_magnet; okListener = (dialog, id) -> { if (manager == null || session == null) { newTorrentDialogVisible = false; return; } LocationDialogHelper.Location location = locationDialogHelper.getLocation(); if (location == null) { return; } addMagnetLink(location.profile, data, location.directory, location.isPaused); intentConsumed = true; newTorrentDialogVisible = false; }; } else if (temporaryFile != null) { title = R.string.add_new_torrent; okListener = (dialog, which) -> { if (manager == null) { newTorrentDialogVisible = false; return; } LocationDialogHelper.Location location = locationDialogHelper.getLocation(); if (location == null) { return; } addTorrentFile(location.profile, temporaryFile, torrentFile, documentUri, location.directory, location.isPaused, location.deleteLocal); intentConsumed = true; newTorrentDialogVisible = false; }; } else { return; } AlertDialog dialog = locationDialogHelper.showDialog(R.layout.new_torrent_dialog, title, (dialog1, which) -> { intentConsumed = true; newTorrentDialogVisible = false; }, okListener ); if (dialog == null) { return; } CheckBox startPaused = (CheckBox) dialog.findViewById(R.id.start_paused); startPaused.setChecked(profile != null && profile.getStartPaused()); CheckBox deleteLocal = (CheckBox) dialog.findViewById(R.id.delete_local); deleteLocal.setChecked(profile != null && profile.getDeleteLocal()); if (temporaryFile != null) { deleteLocal.setVisibility(View.VISIBLE); } } private void showAddTorrentDialog() { LayoutInflater inflater = getLayoutInflater(); View view = inflater.inflate(R.layout.add_torrent_dialog, null); AlertDialog.Builder builder = new AlertDialog.Builder(this) .setCancelable(true) .setView(view) .setTitle(R.string.add_torrent) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(android.R.string.ok, (dialog, which) -> { EditText magnet = (EditText) ((AlertDialog) dialog).findViewById(R.id.magnet_link); Uri data = Uri.parse(magnet.getText().toString()); if (data != null && "magnet".equals(data.getScheme())) { showNewTorrentDialog(data, null, null, null); } else { Toast.makeText(TorrentListActivity.this, R.string.invalid_magnet_link, Toast.LENGTH_SHORT).show(); } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { builder.setNeutralButton(R.string.browse, (dialog, which) -> { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/x-bittorrent"); try { startActivityForResult(intent, BROWSE_REQUEST_CODE); } catch (ActivityNotFoundException e) { Toast.makeText(TorrentListActivity.this, R.string.no_activity_for_open_document, Toast.LENGTH_SHORT).show(); } }); } AlertDialog dialog = builder.create(); dialog.show(); } private void addMagnetLink(TransmissionProfile profile, Uri magnet, String directory, boolean isPaused) { manager.addTorrent(profile.getId(), Uri.decode(magnet.toString()), null, null, directory, isPaused, null); setRefreshing(true, DataService.Requests.ADD_TORRENT); } private void addTorrentFile(TransmissionProfile profile, String temporaryFile, String torrentFile, Uri documentUri, String directory, boolean isPaused, boolean deleteLocal) { Uri uri = documentUri; if (!deleteLocal) { uri = null; } manager.addTorrent(profile.getId(), null, temporaryFile, torrentFile, directory, isPaused, uri); setRefreshing(true, DataService.Requests.ADD_TORRENT); } private class ReadTorrentDataTask extends AsyncTask<Uri, Void, ReadTorrentDataTask.TaskData> { public class TaskData { public File file; public Uri uri; } @Override protected TaskData doInBackground(Uri... params) { if (params.length == 0) { return null; } Uri uri = params[0]; ContentResolver cr = getContentResolver(); InputStream stream = null; Base64.InputStream base64 = null; try { stream = cr.openInputStream(uri); base64 = new Base64.InputStream(stream, Base64.ENCODE | Base64.DO_BREAK_LINES); StringBuilder fileContent = new StringBuilder(""); int ch; while( (ch = base64.read()) != -1) fileContent.append((char)ch); File file = new File(TorrentListActivity.this.getCacheDir(), "torrentdata"); if (file.exists()) { //noinspection ResultOfMethodCallIgnored file.delete(); } if (file.createNewFile()) { BufferedWriter bw = new BufferedWriter(new FileWriter(file)); bw.append(fileContent); bw.close(); G.logD("Torrent file uri: " + uri.toString()); TaskData data = new TaskData(); data.file = file; data.uri = uri; return data; } else { return null; } } catch (Exception e) { G.logE("Error while reading the torrent file", e); return null; } finally { try { if (base64 != null) base64.close(); if (stream != null) stream.close(); } catch (IOException e) { e.printStackTrace(); } } } @Override protected void onPostExecute(TaskData data) { if (data == null) { Toast.makeText(TorrentListActivity.this, R.string.error_reading_torrent_file, Toast.LENGTH_SHORT).show(); } else { showNewTorrentDialog(null, data.file.toURI().toString(), null, data.uri); } } } }