/* * Created by Angel Leon (@gubatron), Alden Torres (aldenml) * Copyright (c) 2011-2014, FrostWire(R). All rights reserved. * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package com.bt.download.android.gui.fragments; import com.bt.download.android.R; import com.bt.download.android.core.ConfigurationManager; import com.bt.download.android.core.Constants; import com.bt.download.android.gui.NetworkManager; import com.bt.download.android.gui.activities.MainActivity; import com.bt.download.android.gui.activities.PreferencesActivity; import com.bt.download.android.gui.adapters.TransferListAdapter; import com.bt.download.android.gui.dialogs.MenuDialog; import com.bt.download.android.gui.dialogs.MenuDialog.MenuItem; import com.bt.download.android.gui.tasks.DownloadSoundcloudFromUrlTask; import com.bt.download.android.gui.transfers.BittorrentDownload; import com.bt.download.android.gui.transfers.HttpDownload; import com.bt.download.android.gui.transfers.SoundcloudDownload; import com.bt.download.android.gui.transfers.Transfer; import com.bt.download.android.gui.transfers.TransferManager; import com.bt.download.android.gui.transfers.YouTubeDownload; import com.bt.download.android.gui.util.UIUtils; import com.bt.download.android.gui.views.AbstractDialog.OnDialogClickListener; import com.bt.download.android.gui.views.AbstractFragment; import com.bt.download.android.gui.views.ClearableEditTextView; import com.bt.download.android.gui.views.ClearableEditTextView.OnActionListener; import com.bt.download.android.gui.views.ClickAdapter; import com.bt.download.android.gui.views.RichNotification; import com.bt.download.android.gui.views.TimerObserver; import com.bt.download.android.gui.views.TimerService; import com.bt.download.android.gui.views.TimerSubscription; import com.frostwire.logging.Logger; import com.frostwire.util.Ref; import com.frostwire.util.StringUtils; import com.wandoujia.ads.sdk.Ads; import com.wandoujia.ads.sdk.loader.Fetcher; import com.wandoujia.ads.sdk.widget.AdBanner; import android.app.Activity; import android.app.Fragment; import android.content.ClipData; import android.content.ClipData.Item; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Environment; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.ExpandableListView; import android.widget.ImageButton; import android.widget.TextView; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; /** * * @author gubatron * @author aldenml * */ public class TransfersFragment extends AbstractFragment implements TimerObserver, MainFragment, OnDialogClickListener { private static final Logger LOG = Logger.getLogger(TransfersFragment.class); private static final String SELECTED_STATUS_STATE_KEY = "selected_status"; private final Comparator<Transfer> transferComparator; private final ButtonAddTransferListener buttonAddTransferListener; private final ButtonMenuListener buttonMenuListener; private Button buttonSelectAll; private Button buttonSelectDownloading; private Button buttonSelectCompleted; private ExpandableListView list; private TextView textDownloads; private TextView textUploads; private ClearableEditTextView addTransferUrlTextView; private TransferListAdapter adapter; private TransferStatus selectedStatus; private TimerSubscription subscription; public AdBanner adBanner; private View adBannerView; public TransfersFragment() { super(R.layout.fragment_transfers); this.transferComparator = new TransferComparator(); this.buttonAddTransferListener = new ButtonAddTransferListener(this); this.buttonMenuListener = new ButtonMenuListener(this); selectedStatus = TransferStatus.ALL; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (savedInstanceState != null) { selectedStatus = TransferStatus.valueOf(savedInstanceState.getString(SELECTED_STATUS_STATE_KEY, TransferStatus.ALL.name())); } addTransferUrlTextView = findView(getView(), R.id.fragment_transfers_add_transfer_text_input); addTransferUrlTextView.replaceSearchIconDrawable(R.drawable.clearable_edittext_add_icon); addTransferUrlTextView.setFocusable(true); addTransferUrlTextView.setFocusableInTouchMode(true); addTransferUrlTextView.setOnKeyListener(new AddTransferTextListener(this)); } @Override public void onDestroy() { super.onDestroy(); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); subscription = TimerService.subscribe(this, 2); } @Override public void onResume() { super.onResume(); initStorageRelatedRichNotifications(getView()); } @Override public void onDestroyView() { super.onDestroyView(); subscription.unsubscribe(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(SELECTED_STATUS_STATE_KEY, selectedStatus.name()); } @Override public void onPause() { super.onPause(); if (adapter != null) { adapter.dismissDialogs(); } } @Override public void onTime() { if (adapter != null) { List<Transfer> transfers = filter(TransferManager.instance().getTransfers(), selectedStatus); Collections.sort(transfers, transferComparator); adapter.updateList(transfers); } else if (this.getActivity() != null) { setupAdapter(); } // format strings String sDown = UIUtils.rate2speed(TransferManager.instance().getDownloadsBandwidth()/1024); String sUp = UIUtils.rate2speed(TransferManager.instance().getUploadsBandwidth()/1024); // number of uploads (seeding) and downloads int downloads = TransferManager.instance().getActiveDownloads(); int uploads = TransferManager.instance().getActiveUploads(); textDownloads.setText(downloads + " @ " + sDown); textUploads.setText(uploads + " @ " + sUp); } @Override public View getHeader(Activity activity) { LayoutInflater inflater = LayoutInflater.from(activity); View header = inflater.inflate(R.layout.view_transfers_header, null); TextView text = (TextView) header.findViewById(R.id.view_transfers_header_text_title); text.setText(R.string.transfers); ImageButton buttonMenu = (ImageButton) header.findViewById(R.id.view_transfers_header_button_menu); buttonMenu.setOnClickListener(buttonMenuListener); ImageButton buttonAddTransfer = (ImageButton) header.findViewById(R.id.view_transfers_header_button_add_transfer); buttonAddTransfer.setOnClickListener(buttonAddTransferListener); return header; } public void selectStatusTab(TransferStatus status) { selectedStatus = status; switch (selectedStatus) { case ALL: buttonSelectAll.performClick(); break; case DOWNLOADING: buttonSelectDownloading.performClick(); break; case COMPLETED: buttonSelectCompleted.performClick(); break; } } @Override protected void initComponents(View v) { initStorageRelatedRichNotifications(v); buttonSelectAll = findView(v, R.id.fragment_transfers_button_select_all); buttonSelectAll.setOnClickListener(new ButtonTabListener(this, TransferStatus.ALL)); buttonSelectDownloading = findView(v, R.id.fragment_transfers_button_select_downloading); buttonSelectDownloading.setOnClickListener(new ButtonTabListener(this, TransferStatus.DOWNLOADING)); buttonSelectCompleted = findView(v, R.id.fragment_transfers_button_select_completed); buttonSelectCompleted.setOnClickListener(new ButtonTabListener(this, TransferStatus.COMPLETED)); list = findView(v, R.id.fragment_transfers_list); textDownloads = findView(v, R.id.fragment_transfers_text_downloads); textUploads = findView(v, R.id.fragment_transfers_text_uploads); showBannerAd(v); } /** * 显示banner广告 * */ public void showBannerAd(View view) { ViewGroup containerView = (ViewGroup)findView(view, R.id.fragment_transfers_banner_ad_container); if (adBannerView != null && containerView.indexOfChild(adBannerView) >= 0) { containerView.removeView(adBannerView); } if (Ads.isLoaded(Fetcher.AdFormat.banner, Constants.TAG_BANNER)) { containerView.setVisibility(View.VISIBLE); adBanner = Ads.showBannerAd(getActivity(), containerView, Constants.TAG_BANNER); adBannerView = adBanner.getView(); //如果当前选中的不是传输界面 Fragment Fragment = ((MainActivity) getActivity()).getCurrentFragment(); if (Fragment != null && !(Fragment instanceof TransfersFragment)) { adBanner.stopAutoScroll(); } } else { containerView.setVisibility(View.GONE); } } public void initStorageRelatedRichNotifications(View v) { if (v == null) { v = getView(); } RichNotification sdCardNotification = findView(v, R.id.fragment_transfers_sd_card_notification); sdCardNotification.setVisibility(View.GONE); RichNotification internalMemoryNotification = findView(v, R.id.fragment_transfers_internal_memory_notification); internalMemoryNotification.setVisibility(View.GONE); if (isUsingSDCardPrivateStorage() && !sdCardNotification.wasDismissed()) { sdCardNotification.setVisibility(View.VISIBLE); sdCardNotification.setOnClickListener(new SDCardNotificationListener(this)); } //if you do have an SD Card mounted and you're using internal memory, we'll let you know //that you now can use the SD Card. We'll keep this for a few releases. File sdCardDir = getBiggestSDCardDir(getActivity()); if (sdCardDir != null && com.bt.download.android.util.SystemUtils.isSecondaryExternalStorageMounted(sdCardDir) && !isUsingSDCardPrivateStorage() && !internalMemoryNotification.wasDismissed()) { String bytesAvailableInHuman = UIUtils.getBytesInHuman(com.bt.download.android.util.SystemUtils.getAvailableStorageSize(sdCardDir)); String internalMemoryNotificationDescription = getString(R.string.saving_to_internal_memory_description, bytesAvailableInHuman); internalMemoryNotification.setDescription(internalMemoryNotificationDescription); internalMemoryNotification.setVisibility(View.VISIBLE); internalMemoryNotification.setOnClickListener(new SDCardNotificationListener(this)); } } private void setupAdapter() { List<Transfer> transfers = filter(TransferManager.instance().getTransfers(), selectedStatus); Collections.sort(transfers, transferComparator); adapter = new TransferListAdapter(TransfersFragment.this.getActivity(), transfers); list.setAdapter(adapter); } private List<Transfer> filter(List<Transfer> transfers, TransferStatus status) { Iterator<Transfer> it; switch (status) { // replace this filter by a more functional style case DOWNLOADING: it = transfers.iterator(); while (it.hasNext()) { if (it.next().isComplete()) { it.remove(); } } return transfers; case COMPLETED: it = transfers.iterator(); while (it.hasNext()) { if (!it.next().isComplete()) { it.remove(); } } return transfers; default: return transfers; } } private static final String TRANSFERS_DIALOG_ID = "transfers_dialog"; private static final int CLEAR_MENU_DIALOG_ID = 0; private static final int PAUSE_MENU_DIALOG_ID = 1; private static final int RESUME_MENU_DIALOG_ID = 2; @Override public void onDialogClick(String tag, int which) { if (tag.equals(TRANSFERS_DIALOG_ID)) { switch (which) { case CLEAR_MENU_DIALOG_ID: TransferManager.instance().clearComplete(); break; case PAUSE_MENU_DIALOG_ID: TransferManager.instance().stopHttpTransfers(); TransferManager.instance().pauseTorrents(); break; case RESUME_MENU_DIALOG_ID: boolean bittorrentDisconnected = TransferManager.instance().isBittorrentDisconnected(); if (bittorrentDisconnected){ UIUtils.showLongMessage(getActivity(), R.string.cant_resume_torrent_transfers); } else { if (NetworkManager.instance().isDataUp()) { TransferManager.instance().resumeResumableTransfers(); } else { UIUtils.showShortMessage(getActivity(), R.string.please_check_connection_status_before_resuming_download); } } break; } setupAdapter(); } } private void showContextMenu() { MenuItem clear = new MenuItem(CLEAR_MENU_DIALOG_ID, R.string.transfers_context_menu_clear_finished, R.drawable.contextmenu_icon_remove_transfer); MenuItem pause = new MenuItem(PAUSE_MENU_DIALOG_ID, R.string.transfers_context_menu_pause_stop_all_transfers, R.drawable.contextmenu_icon_pause_transfer); MenuItem resume = new MenuItem(RESUME_MENU_DIALOG_ID, R.string.transfers_context_resume_all_torrent_transfers, R.drawable.contextmenu_icon_play); List<MenuItem> dlgActions = new ArrayList<MenuItem>(); TransferManager tm = TransferManager.instance(); boolean bittorrentDisconnected = tm.isBittorrentDisconnected(); final List<Transfer> transfers = tm.getTransfers(); if (transfers != null && transfers.size() > 0) { if (someTransfersComplete(transfers)) { dlgActions.add(clear); } if (!bittorrentDisconnected) { if (someTransfersActive(transfers)) { dlgActions.add(pause); } } //let's show it even if bittorrent is disconnected //user should get a message telling them to check why they can't resume. //Preferences > Connectivity is disconnected. if (someTransfersInactive(transfers)) { dlgActions.add(resume); } } if (dlgActions.size() > 0) { MenuDialog dlg = MenuDialog.newInstance(TRANSFERS_DIALOG_ID, dlgActions); dlg.show(getFragmentManager()); } } private boolean someTransfersInactive(List<Transfer> transfers) { for (Transfer t : transfers) { if (t instanceof BittorrentDownload) { BittorrentDownload bt = (BittorrentDownload) t; if (!bt.isDownloading() && !bt.isSeeding()) { return true; } } else if (t instanceof HttpDownload) { HttpDownload ht = (HttpDownload) t; if (ht.isComplete() || !ht.isDownloading()) { return true; } } else if (t instanceof YouTubeDownload) { YouTubeDownload yt = (YouTubeDownload) t; if (yt.isComplete() || !yt.isDownloading()) { return true; } } else if (t instanceof SoundcloudDownload) { SoundcloudDownload sd = (SoundcloudDownload) t; if (sd.isComplete() || !sd.isDownloading()) { return true; } } } return false; } private boolean someTransfersComplete(List<Transfer> transfers) { for (Transfer t : transfers) { if (t.isComplete()) { return true; } } return false; } private boolean someTransfersActive(List<Transfer> transfers) { for (Transfer t : transfers) { if (t instanceof BittorrentDownload) { BittorrentDownload bt = (BittorrentDownload) t; if (bt.isDownloading() || bt.isSeeding()) { return true; } } else if (t instanceof HttpDownload) { HttpDownload ht = (HttpDownload) t; if (ht.isDownloading()) { return true; } } else if (t instanceof YouTubeDownload) { YouTubeDownload yt = (YouTubeDownload) t; if (yt.isDownloading()) { return true; } } else if (t instanceof SoundcloudDownload) { SoundcloudDownload sd = (SoundcloudDownload) t; if (sd.isDownloading()) { return true; } } } return false; } public void startTransferFromURL() { String url = addTransferUrlTextView.getText(); if (!StringUtils.isNullOrEmpty(url) && (url.startsWith("magnet") || url.startsWith("http"))) { toggleAddTransferControls(); if (url.startsWith("http") && (url.contains("soundcloud.com/") || url.contains("youtube.com/"))) { startCloudTransfer(url); } else if (url.startsWith("http")) { //magnets are automatically started if found on the clipboard by autoPasteMagnetOrURL TransferManager.instance().downloadTorrent(url.trim()); UIUtils.showLongMessage(getActivity(), R.string.torrent_url_added); } addTransferUrlTextView.setText(""); } else { UIUtils.showLongMessage(getActivity(), R.string.please_enter_valid_url); } } private void startCloudTransfer(String text) { if (text.contains("soundcloud.com/")) { new DownloadSoundcloudFromUrlTask(getActivity(),text.trim()).execute(); } else if (text.contains("youtube.com/")) { startYouTubeSearchFromUrl(text.trim()); } else { UIUtils.showLongMessage(getActivity(), R.string.cloud_downloads_coming); } } private void startYouTubeSearchFromUrl(String ytUrl) { //fragments are not supposed to communicate directly so I'll let my activity know //(NOTE: This is a poor implementation of fragment to fragment communication // despite what the android documentation says http://developer.android.com/training/basics/fragments/communicating.html // as this could not scale if you wanted to reuse fragments on other activities) MainActivity activity = (MainActivity) getActivity(); activity.performYTSearch(ytUrl); } private void autoPasteMagnetOrURL() { ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); ClipData primaryClip = clipboard.getPrimaryClip(); if (primaryClip != null) { Item itemAt = primaryClip.getItemAt(0); try { CharSequence charSequence = itemAt.getText(); if (charSequence != null) { String text = null; if (charSequence instanceof String) { text = (String) charSequence; } else { text = charSequence.toString(); } if (!StringUtils.isNullOrEmpty(text)) { if (text.startsWith("http")) { addTransferUrlTextView.setText(text.trim()); } else if (text.startsWith("magnet")) { addTransferUrlTextView.setText(text.trim()); TransferManager.instance().downloadTorrent(text.trim()); UIUtils.showLongMessage(getActivity(), R.string.magnet_url_added); clipboard.setPrimaryClip(ClipData.newPlainText("", "")); toggleAddTransferControls(); } } } } catch (Throwable t) { } } } private void toggleAddTransferControls() { if (addTransferUrlTextView.getVisibility() == View.GONE) { addTransferUrlTextView.setVisibility(View.VISIBLE); autoPasteMagnetOrURL(); showAddTransfersKeyboard(); } else { addTransferUrlTextView.setVisibility(View.GONE); addTransferUrlTextView.setText(""); hideAddTransfersKeyboard(); } } private void showAddTransfersKeyboard() { if (addTransferUrlTextView.getVisibility() == View.VISIBLE && (addTransferUrlTextView.getText().startsWith("http") || addTransferUrlTextView.getText().isEmpty())) { UIUtils.showKeyboard(addTransferUrlTextView.getAutoCompleteTextView().getContext(), addTransferUrlTextView.getAutoCompleteTextView()); } } private void hideAddTransfersKeyboard() { InputMethodManager imm = (InputMethodManager) addTransferUrlTextView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(addTransferUrlTextView.getWindowToken(), 0); } /** * Is it using the SD Card's private (non-persistent after uninstall) app folder to save * downloaded files? * @return */ public static boolean isUsingSDCardPrivateStorage() { String primaryPath = Environment.getExternalStorageDirectory().getAbsolutePath(); String currentPath = ConfigurationManager.instance().getStoragePath(); return !primaryPath.equals(currentPath); } /** * Iterates over all the secondary external storage roots and returns the one with the most bytes available. * @param context * @return */ private static File getBiggestSDCardDir(Context context) { try { String primaryPath = context.getExternalFilesDir(null).getParent(); long biggestBytesAvailable = -1; File result = null; for (File f : com.bt.download.android.util.SystemUtils.getExternalFilesDirs(context)) { if (!f.getAbsolutePath().startsWith(primaryPath)) { long bytesAvailable = com.bt.download.android.util.SystemUtils.getAvailableStorageSize(f); if (bytesAvailable > biggestBytesAvailable) { biggestBytesAvailable = bytesAvailable; result = f; } } } //System.out.println("FW.SystemUtils.getSDCardDir() -> " + result.getAbsolutePath()); // -> /storage/extSdCard/Android/data/com.bt.download.android/files return result; } catch (Throwable e) { // the context could be null due to a UI bad logic or context.getExternalFilesDir(null) could be null LOG.error("Error getting the biggest SD card", e); } return null; } private static final class TransferComparator implements Comparator<Transfer> { @Override public int compare(Transfer lhs, Transfer rhs) { try { return -lhs.getDateCreated().compareTo(rhs.getDateCreated()); } catch (Throwable e) { // ignore, not really super important } return 0; } } public static enum TransferStatus { ALL, DOWNLOADING, COMPLETED } private static final class ButtonAddTransferListener extends ClickAdapter<TransfersFragment> { public ButtonAddTransferListener(TransfersFragment f) { super(f); } @Override public void onClick(TransfersFragment f, View v) { f.toggleAddTransferControls(); } } private static final class ButtonMenuListener extends ClickAdapter<TransfersFragment> { public ButtonMenuListener(TransfersFragment f) { super(f); } @Override public void onClick(TransfersFragment f, View v) { f.showContextMenu(); } } private static final class AddTransferTextListener extends ClickAdapter<TransfersFragment> implements OnItemClickListener, OnActionListener { public AddTransferTextListener(TransfersFragment owner) { super(owner); } @Override public boolean onKey(TransfersFragment owner, View v, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) { owner.startTransferFromURL(); return true; } return false; } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (Ref.alive(ownerRef)) { TransfersFragment owner = ownerRef.get(); owner.startTransferFromURL(); } } @Override public void onClear(View v) { if (Ref.alive(ownerRef)) { //TransfersFragment owner = ownerRef.get(); //might clear. LOG.debug("onClear"); } } @Override public void onTextChanged(View v, String str) { } } private static final class ButtonTabListener extends ClickAdapter<TransfersFragment> { private final TransferStatus status; public ButtonTabListener(TransfersFragment f, TransferStatus status) { super(f); this.status = status; } @Override public void onClick(TransfersFragment f, View v) { f.selectedStatus = status; f.onTime(); } } private static final class SDCardNotificationListener extends ClickAdapter<TransfersFragment> { public SDCardNotificationListener(TransfersFragment owner) { super(owner); } @Override public void onClick(TransfersFragment owner, View v) { Intent i = new Intent(owner.getActivity(), PreferencesActivity.class); i.setAction(Constants.ACTION_SETTINGS_SELECT_STORAGE); owner.getActivity().startActivity(i); } } }