/* * 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.adapters; import com.bt.download.android.R; import com.bt.download.android.core.Constants; import com.bt.download.android.core.FileDescriptor; import com.bt.download.android.gui.Librarian; import com.bt.download.android.gui.Peer; import com.bt.download.android.gui.adapters.FileListAdapter.FileDescriptorItem; import com.bt.download.android.gui.adapters.menu.DeleteFileMenuAction; import com.bt.download.android.gui.adapters.menu.DownloadCheckedMenuAction; import com.bt.download.android.gui.adapters.menu.DownloadMenuAction; import com.bt.download.android.gui.adapters.menu.OpenMenuAction; import com.bt.download.android.gui.adapters.menu.RenameFileMenuAction; import com.bt.download.android.gui.adapters.menu.SendFileMenuAction; import com.bt.download.android.gui.adapters.menu.SetAsRingtoneMenuAction; import com.bt.download.android.gui.adapters.menu.SetAsWallpaperMenuAction; import com.bt.download.android.gui.adapters.menu.SetSharedStateFileGrainedMenuAction; import com.bt.download.android.gui.adapters.menu.ToggleFileGrainedSharingMenuAction; import com.bt.download.android.gui.services.Engine; import com.bt.download.android.gui.transfers.DownloadTransfer; import com.bt.download.android.gui.transfers.ExistingDownload; import com.bt.download.android.gui.transfers.TransferManager; import com.bt.download.android.gui.util.UIUtils; import com.bt.download.android.gui.views.AbstractListAdapter; import com.bt.download.android.gui.views.BrowseThumbnailImageButton; import com.bt.download.android.gui.views.BrowseThumbnailImageButton.OverlayState; import com.bt.download.android.gui.views.ListAdapterFilter; import com.bt.download.android.gui.views.MenuAction; import com.bt.download.android.gui.views.MenuAdapter; import com.bt.download.android.gui.views.MenuBuilder; import com.bt.download.android.util.ImageUtils; import com.bt.download.android.util.SystemUtils; import com.frostwire.util.Condition; import com.frostwire.uxstats.UXAction; import com.frostwire.uxstats.UXStats; import android.content.ContentUris; import android.content.Context; import android.content.res.Resources; import android.net.Uri; import android.provider.MediaStore.Images; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; /** * Adapter in control of the List View shown when we're browsing the files of * one peer. * * @author gubatron * @author aldenml * */ public class FileListAdapter extends AbstractListAdapter<FileDescriptorItem> { private final Peer peer; private final boolean local; private final byte fileType; private final PadLockClickListener padLockClickListener; private final DownloadButtonClickListener downloadButtonClickListener; public static final int FILE_LIST_FILTER_SHOW_ALL = 0; public static final int FILE_LIST_FILTER_SHOW_SHARED = 1; public static final int FILE_LIST_FILTER_SHOW_UNSHARED = 2; private FileListFilter fileListFilter; private Context context; public FileListAdapter(Context context, List<FileDescriptor> files, Peer peer, boolean local, byte fileType) { super(context, getViewItemId(local, fileType), convertFiles(files)); setShowMenuOnClick(true); fileListFilter = new FileListFilter(); setAdapterFilter(fileListFilter); this.context = context; this.peer = peer; this.local = local; this.fileType = fileType; this.padLockClickListener = new PadLockClickListener(); this.downloadButtonClickListener = new DownloadButtonClickListener(); checkSDStatus(); } public byte getFileType() { return fileType; } /** * @param sharedState FILE_LIST_FILTER_SHOW_ALL, FILE_LIST_FILTER_SHOW_SHARED, FILE_LIST_FILTER_SHOW_UNSHARED */ public void setFileVisibilityBySharedState(int sharedState) { fileListFilter.filterBySharedState(sharedState); } public int getFileVisibilityBySharedState() { return fileListFilter.getCurrentSharedStateShown(); } @Override protected final void populateView(View view, FileDescriptorItem item) { if (getViewItemId() == R.layout.view_browse_thumbnail_peer_list_item) { populateViewThumbnail(view, item); } else { populateViewPlain(view, item); } } @Override protected MenuAdapter getMenuAdapter(View view) { Context context = getContext(); List<MenuAction> items = new ArrayList<MenuAction>(); // due to long click generic handle FileDescriptor fd = null; if (view.getTag() instanceof FileDescriptorItem) { FileDescriptorItem item = (FileDescriptorItem) view.getTag(); fd = item.fd; } else if (view.getTag() instanceof FileDescriptor) { fd = (FileDescriptor) view.getTag(); } if (local && checkIfNotExists(fd)) { return null; } List<FileDescriptor> checked = convertItems(getChecked()); int numChecked = checked.size(); boolean showSingleOptions = showSingleOptions(checked, fd); if (local) { if (showSingleOptions) { items.add(new OpenMenuAction(context, fd.filePath, fd.mime)); if (fd.fileType != Constants.FILE_TYPE_APPLICATIONS && numChecked <= 1) { items.add(new SendFileMenuAction(context, fd)); //applications cause a force close with GMail } if (fd.fileType == Constants.FILE_TYPE_RINGTONES && numChecked <= 1) { items.add(new SetAsRingtoneMenuAction(context, fd)); } if (fd.fileType == Constants.FILE_TYPE_PICTURES && numChecked <= 1) { items.add(new SetAsWallpaperMenuAction(context, fd)); } if (fd.fileType != Constants.FILE_TYPE_APPLICATIONS && numChecked <= 1) { items.add(new RenameFileMenuAction(context, this, fd)); } } List<FileDescriptor> list = checked; if (list.size() == 0) { list = Arrays.asList(fd); } //Share Selected items.add(new SetSharedStateFileGrainedMenuAction(context, this, list, true)); //Unshare Selected items.add(new SetSharedStateFileGrainedMenuAction(context, this, list, false)); //Toogle Shared States items.add(new ToggleFileGrainedSharingMenuAction(context, this, list)); if (fd.fileType != Constants.FILE_TYPE_APPLICATIONS) { items.add(new DeleteFileMenuAction(context, this, list)); } } else { if (0 < numChecked && numChecked <= Constants.MAX_NUM_DOWNLOAD_CHECKED) { items.add(new DownloadCheckedMenuAction(context, this, checked, peer)); } items.add(new DownloadMenuAction(context, this, peer, fd)); } return new MenuAdapter(context, fd.title, items); } protected void onLocalPlay() { } private void localPlay(FileDescriptor fd) { if (fd == null) { return; } onLocalPlay(); if (fd.mime != null && fd.mime.contains("audio")) { if (fd.equals(Engine.instance().getMediaPlayer().getCurrentFD())) { Engine.instance().getMediaPlayer().stop(); } else { try { UIUtils.playEphemeralPlaylist(fd); UXStats.instance().log(UXAction.LIBRARY_PLAY_AUDIO_FROM_FILE); } catch (RuntimeException re) { UIUtils.showShortMessage(getContext(), R.string.media_player_failed); } } notifyDataSetChanged(); } else { if (fd.filePath != null && fd.mime != null) { UIUtils.openFile(getContext(), fd.filePath, fd.mime); } } } /** * Start a transfer */ private DownloadTransfer startDownload(FileDescriptor fd) { DownloadTransfer download = TransferManager.instance().download(peer, fd); notifyDataSetChanged(); return download; } private void populateViewThumbnail(View view, FileDescriptorItem item) { FileDescriptor fd = item.fd; BrowseThumbnailImageButton fileThumbnail = findView(view, R.id.view_browse_peer_list_item_file_thumbnail); fileThumbnail.setScaleType(ImageView.ScaleType.CENTER_CROP); if (local && fileType == Constants.FILE_TYPE_APPLICATIONS) { Uri uri = Uri.withAppendedPath(ImageUtils.APPLICATION_THUMBNAILS_URI, fd.album); ImageUtils.load(context, uri, fileThumbnail); } else { if (Condition.in(fileType, Constants.FILE_TYPE_AUDIO, Constants.FILE_TYPE_VIDEOS, Constants.FILE_TYPE_RINGTONES)) { if (fd.equals(Engine.instance().getMediaPlayer().getCurrentFD())) { fileThumbnail.setOverlayState(OverlayState.STOP); } else { fileThumbnail.setOverlayState(OverlayState.PLAY); } } if (fd.fileType == Constants.FILE_TYPE_AUDIO) { Uri uri = ContentUris.withAppendedId(ImageUtils.ALBUM_THUMBNAILS_URI, fd.albumId); ImageUtils.load(context, uri, fileThumbnail); } else if (fd.fileType == Constants.FILE_TYPE_VIDEOS) { Uri uri = ContentUris.withAppendedId(ImageUtils.VIDEO_THUMBNAILS_URI, fd.id); ImageUtils.load(context, uri, fileThumbnail); } else if (fd.fileType == Constants.FILE_TYPE_PICTURES) { Uri uri = ContentUris.withAppendedId(Images.Media.EXTERNAL_CONTENT_URI, fd.id); ImageUtils.load(context, uri, fileThumbnail); } } ImageButton padlock = findView(view, R.id.view_browse_peer_list_item_lock_toggle); TextView title = findView(view, R.id.view_browse_peer_list_item_file_title); title.setText(fd.title); populatePadlockAppearance(fd, padlock, title); if (fd.fileType == Constants.FILE_TYPE_AUDIO || fd.fileType == Constants.FILE_TYPE_APPLICATIONS) { TextView fileExtra = findView(view, R.id.view_browse_peer_list_item_extra_text); fileExtra.setText(fd.artist); } else { TextView fileExtra = findView(view, R.id.view_browse_peer_list_item_extra_text); fileExtra.setText(R.string.empty_string); } TextView fileSize = findView(view, R.id.view_browse_peer_list_item_file_size); fileSize.setText(UIUtils.getBytesInHuman(fd.fileSize)); fileThumbnail.setTag(fd); fileThumbnail.setOnClickListener(downloadButtonClickListener); populateSDState(view, item); } /** * Same factors are considered to show the padlock icon state and color. * * When the file is not local and it's been marked for download the text color appears as blue. * * @param fd * @param padlock * @param title */ private void populatePadlockAppearance(FileDescriptor fd, ImageButton padlock, TextView title) { if (local) { padlock.setVisibility(View.VISIBLE); padlock.setTag(fd); padlock.setOnClickListener(padLockClickListener); if (fd.shared) { padlock.setImageResource(R.drawable.browse_peer_padlock_unlocked_icon); } else { padlock.setImageResource(R.drawable.browse_peer_padlock_locked_icon); } } else { padlock.setVisibility(View.GONE); } } private void populateViewPlain(View view, FileDescriptorItem item) { FileDescriptor fd = item.fd; ImageButton padlock = findView(view, R.id.view_browse_peer_list_item_lock_toggle); TextView title = findView(view, R.id.view_browse_peer_list_item_file_title); title.setText(fd.title); populatePadlockAppearance(fd, padlock, title); populateContainerAction(view); if (fd.fileType == Constants.FILE_TYPE_AUDIO || fd.fileType == Constants.FILE_TYPE_APPLICATIONS) { TextView fileExtra = findView(view, R.id.view_browse_peer_list_item_extra_text); fileExtra.setText(fd.artist); } else { TextView fileExtra = findView(view, R.id.view_browse_peer_list_item_extra_text); fileExtra.setText(R.string.empty_string); } TextView fileSize = findView(view, R.id.view_browse_peer_list_item_file_size); fileSize.setText(UIUtils.getBytesInHuman(fd.fileSize)); BrowseThumbnailImageButton downloadButton = findView(view, R.id.view_browse_peer_list_item_download); if (local) { if (fd.equals(Engine.instance().getMediaPlayer().getCurrentFD())) { downloadButton.setOverlayState(OverlayState.STOP); } else { downloadButton.setOverlayState(OverlayState.PLAY); } } else { downloadButton.setImageResource(R.drawable.download_icon); } downloadButton.setTag(fd); downloadButton.setOnClickListener(downloadButtonClickListener); populateSDState(view, item); } private void populateSDState(View v, FileDescriptorItem item) { ImageView img = findView(v, R.id.view_browse_peer_list_item_sd); ImageView lock = findView(v, R.id.view_browse_peer_list_item_lock_toggle); if (item.inSD) { if (item.mounted) { v.setBackgroundResource(R.drawable.listview_item_background_selector); setNormalTextColors(v); img.setVisibility(View.GONE); } else { v.setBackgroundResource(R.drawable.browse_peer_listview_item_inactive_background); setInactiveTextColors(v); img.setVisibility(View.VISIBLE); if (item.fd.shared) { lock.setImageResource(R.drawable.browse_peer_padlock_unlocked_icon_inactive); } else { lock.setImageResource(R.drawable.browse_peer_padlock_locked_icon_inactive); } } } else { v.setBackgroundResource(R.drawable.listview_item_background_selector); setNormalTextColors(v); img.setVisibility(View.GONE); } } private void setNormalTextColors(View v) { TextView title = findView(v, R.id.view_browse_peer_list_item_file_title); TextView text = findView(v, R.id.view_browse_peer_list_item_extra_text); TextView size = findView(v, R.id.view_browse_peer_list_item_file_size); Resources res = getContext().getResources(); title.setTextColor(res.getColor(R.color.browse_peer_listview_item_foreground)); text.setTextColor(res.getColor(R.color.browse_peer_listview_item_foreground)); size.setTextColor(res.getColor(R.color.app_highlight_text)); } private void setInactiveTextColors(View v) { TextView title = findView(v, R.id.view_browse_peer_list_item_file_title); TextView text = findView(v, R.id.view_browse_peer_list_item_extra_text); TextView size = findView(v, R.id.view_browse_peer_list_item_file_size); Resources res = getContext().getResources(); title.setTextColor(res.getColor(R.color.browse_peer_listview_item_inactive_foreground)); text.setTextColor(res.getColor(R.color.browse_peer_listview_item_inactive_foreground)); size.setTextColor(res.getColor(R.color.browse_peer_listview_item_inactive_foreground)); } private void populateContainerAction(View view) { ImageButton preview = findView(view, R.id.view_browse_peer_list_item_button_preview); if (local) { preview.setVisibility(View.GONE); } else { // just for now preview.setVisibility(View.GONE); } } private boolean showSingleOptions(List<FileDescriptor> checked, FileDescriptor fd) { if (checked.size() > 1) { return false; } if (checked.size() == 1) { return checked.get(0).equals(fd); } return true; } private static int getViewItemId(boolean local, byte fileType) { if (local && (fileType == Constants.FILE_TYPE_PICTURES || fileType == Constants.FILE_TYPE_VIDEOS || fileType == Constants.FILE_TYPE_APPLICATIONS || fileType == Constants.FILE_TYPE_AUDIO)) { return R.layout.view_browse_thumbnail_peer_list_item; } else { return R.layout.view_browse_peer_list_item; } } private static ArrayList<FileDescriptor> convertItems(Collection<FileDescriptorItem> items) { if (items == null) { return new ArrayList<FileDescriptor>(); } ArrayList<FileDescriptor> list = new ArrayList<FileDescriptor>(items.size()); for (FileDescriptorItem item : items) { list.add(item.fd); } return list; } private static ArrayList<FileDescriptorItem> convertFiles(Collection<FileDescriptor> fds) { if (fds == null) { return new ArrayList<FileDescriptorItem>(); } ArrayList<FileDescriptorItem> list = new ArrayList<FileDescriptorItem>(fds.size()); for (FileDescriptor fd : fds) { FileDescriptorItem item = new FileDescriptorItem(); item.fd = fd; list.add(item); } return list; } public void deleteItem(FileDescriptor fd) { FileDescriptorItem item = new FileDescriptorItem(); item.fd = fd; super.deleteItem(item); } private void checkSDStatus() { Map<String, Boolean> sds = new HashMap<String, Boolean>(); String privateSubpath = "Android" + File.separator + "data"; File[] externalDirs = SystemUtils.getExternalFilesDirs(getContext()); for (int i = 1; i < externalDirs.length; i++) { File path = externalDirs[i]; String absolutePath = path.getAbsolutePath(); boolean isSecondaryExternalStorageMounted = SystemUtils.isSecondaryExternalStorageMounted(path); sds.put(absolutePath, isSecondaryExternalStorageMounted); if (absolutePath.contains(privateSubpath)) { String prefix = absolutePath.substring(0, absolutePath.indexOf(privateSubpath)-1); sds.put(prefix, isSecondaryExternalStorageMounted); } } if (sds.isEmpty()) { return; // yes, fast return (for now) } for (FileDescriptorItem item : getList()) { item.inSD = false; for (Entry<String, Boolean> e : sds.entrySet()) { if (item.fd.filePath.contains(e.getKey())) { item.inSD = true; item.mounted = e.getValue(); } } item.exists = true; } } private boolean checkIfNotExists(FileDescriptor fd) { if (fd == null) { return true; } File f = new File(fd.filePath); if (!f.exists()) { if (SystemUtils.isSecondaryExternalStorageMounted(f.getAbsoluteFile())) { UIUtils.showShortMessage(getContext(), R.string.file_descriptor_sd_mounted); Librarian.instance().deleteFiles(fileType, Arrays.asList(fd)); deleteItem(fd); } else { UIUtils.showShortMessage(getContext(), R.string.file_descriptor_sd_unmounted); } return true; } else { return false; } } private static class FileListFilter implements ListAdapterFilter<FileDescriptorItem> { private int visibleFiles; public void filterBySharedState(int state) { this.visibleFiles = state; } public int getCurrentSharedStateShown() { return visibleFiles; } @Override public boolean accept(FileDescriptorItem obj, CharSequence constraint) { if (visibleFiles != FILE_LIST_FILTER_SHOW_ALL && ((obj.fd.shared && visibleFiles == FILE_LIST_FILTER_SHOW_UNSHARED) || (!obj.fd.shared && visibleFiles == FILE_LIST_FILTER_SHOW_SHARED))) { return false; } String keywords = constraint.toString(); if (keywords == null || keywords.length() == 0) { return true; } keywords = keywords.toLowerCase(Locale.US); FileDescriptor fd = obj.fd; if (fd.fileType == Constants.FILE_TYPE_AUDIO) { return fd.album.trim().toLowerCase(Locale.US).contains(keywords) || fd.artist.trim().toLowerCase(Locale.US).contains(keywords) || fd.title.trim().toLowerCase(Locale.US).contains(keywords) || fd.filePath.trim().toLowerCase(Locale.US).contains(keywords); } else { return fd.title.trim().toLowerCase(Locale.US).contains(keywords) || fd.filePath.trim().toLowerCase(Locale.US).contains(keywords); } } } private final class PadLockClickListener implements OnClickListener { @Override public void onClick(View v) { FileDescriptor fd = (FileDescriptor) v.getTag(); if (fd == null) { return; } if (checkIfNotExists(fd)) { return; } fd.shared = !fd.shared; UXStats.instance().log(fd.shared ? UXAction.WIFI_SHARING_SHARED : UXAction.WIFI_SHARING_UNSHARED); notifyDataSetChanged(); Librarian.instance().updateSharedStates(fileType, Arrays.asList(fd)); } } private final class DownloadButtonClickListener implements OnClickListener { @Override public void onClick(View v) { FileDescriptor fd = (FileDescriptor) v.getTag(); if (fd == null) { return; } if (local && checkIfNotExists(fd)) { return; } if (local) { localPlay(fd); } else { List<FileDescriptor> list = convertItems(getChecked()); if (list == null || list.size() == 0) { // if no files are selected, they want to download this one. if (!(startDownload(fd) instanceof ExistingDownload)) { UIUtils.showLongMessage(getContext(), R.string.download_added_to_queue); UIUtils.showTransfersOnDownloadStart(getContext()); } } else { // if many are selected... do they want to download many // or just this one? List<MenuAction> items = new ArrayList<MenuAction>(2); items.add(new DownloadCheckedMenuAction(getContext(), FileListAdapter.this, list, peer)); items.add(new DownloadMenuAction(getContext(), FileListAdapter.this, peer, fd)); MenuAdapter menuAdapter = new MenuAdapter(getContext(), R.string.wanna_download_question, items); trackDialog(new MenuBuilder(menuAdapter).show()); } } } } public static class FileDescriptorItem { public FileDescriptor fd; public boolean inSD; public boolean mounted; public boolean exists; @Override public boolean equals(Object o) { if (!(o instanceof FileDescriptorItem)) { return false; } return fd.equals(((FileDescriptorItem) o).fd); } } }