/***************************************************************************** * VideoListActivity.java ***************************************************************************** * Copyright © 2011-2012 VLC authors and VideoLAN * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ package org.videolan.vlc.gui.video; import android.annotation.TargetApi; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.support.design.widget.Snackbar; import android.support.v4.app.FragmentActivity; import android.support.v4.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; 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.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.GridView; import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.PopupMenu.OnMenuItemClickListener; import android.widget.TextView; import org.videolan.libvlc.Media; import org.videolan.libvlc.util.AndroidUtil; import org.videolan.vlc.MediaDatabase; import org.videolan.vlc.MediaGroup; import org.videolan.vlc.MediaLibrary; import org.videolan.vlc.MediaWrapper; import org.videolan.vlc.R; import org.videolan.vlc.Thumbnailer; import org.videolan.vlc.VLCApplication; import org.videolan.vlc.gui.MainActivity; import org.videolan.vlc.gui.SecondaryActivity; import org.videolan.vlc.gui.browser.MediaBrowserFragment; import org.videolan.vlc.interfaces.ISortable; import org.videolan.vlc.interfaces.IVideoBrowser; import org.videolan.vlc.util.AndroidDevices; import org.videolan.vlc.util.Util; import org.videolan.vlc.util.VLCInstance; import org.videolan.vlc.util.WeakHandler; import org.videolan.vlc.widget.SwipeRefreshLayout; import java.io.File; import java.util.ArrayList; import java.util.List; public class VideoGridFragment extends MediaBrowserFragment implements ISortable, IVideoBrowser, SwipeRefreshLayout.OnRefreshListener, AdapterView.OnItemClickListener { public final static String TAG = "VLC/VideoListFragment"; public final static String KEY_GROUP = "key_group"; private static final int DELETE_MEDIA = 0; private static final int DELETE_DURATION = 3000; protected LinearLayout mLayoutFlipperLoading; protected GridView mGridView; protected TextView mTextViewNomedia; protected View mViewNomedia; protected String mGroup; private VideoListAdapter mVideoAdapter; private Thumbnailer mThumbnailer; private VideoGridAnimator mAnimator; private MainActivity mMainActivity; // Gridview position saved in onPause() private int mGVFirstVisiblePos; /* All subclasses of Fragment must include a public empty constructor. */ public VideoGridFragment() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mVideoAdapter = new VideoListAdapter(this); if (savedInstanceState != null) setGroup(savedInstanceState.getString(KEY_GROUP)); /* Load the thumbnailer */ FragmentActivity activity = getActivity(); if (activity != null) mThumbnailer = new Thumbnailer(activity, activity.getWindowManager().getDefaultDisplay()); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ View v = inflater.inflate(R.layout.video_grid, container, false); // init the information for the scan (1/2) mLayoutFlipperLoading = (LinearLayout) v.findViewById(R.id.layout_flipper_loading); mTextViewNomedia = (TextView) v.findViewById(R.id.textview_nomedia); mViewNomedia = v.findViewById(android.R.id.empty); mGridView = (GridView) v.findViewById(android.R.id.list); mSwipeRefreshLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipeLayout); mSwipeRefreshLayout.setColorSchemeResources(R.color.orange700); mSwipeRefreshLayout.setOnRefreshListener(this); mGridView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mSwipeRefreshLayout.setEnabled(firstVisibleItem == 0); } }); mGridView.setAdapter(mVideoAdapter); mGridView.setOnItemClickListener(this); return v; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); registerForContextMenu(mGridView); // init the information for the scan (2/2) IntentFilter filter = new IntentFilter(); filter.addAction(Util.ACTION_SCAN_START); filter.addAction(Util.ACTION_SCAN_STOP); getActivity().registerReceiver(messageReceiverVideoListFragment, filter); if (mMediaLibrary.isWorking()) { Util.actionScanStart(); } mAnimator = new VideoGridAnimator(mGridView); } @Override public void onPause() { super.onPause(); mGVFirstVisiblePos = mGridView.getFirstVisiblePosition(); mMediaLibrary.setBrowser(null); mMediaLibrary.removeUpdateHandler(mHandler); /* Stop the thumbnailer */ if (mThumbnailer != null) mThumbnailer.stop(); } @Override public void onResume() { super.onResume(); if ((getActivity() instanceof MainActivity)) mMainActivity = (MainActivity) getActivity(); mMediaLibrary.setBrowser(this); mMediaLibrary.addUpdateHandler(mHandler); final boolean refresh = mVideoAdapter.isEmpty(); if (refresh) updateList(); else { mViewNomedia.setVisibility(View.GONE); focusHelper(false); } //Get & set times ArrayMap<String, Long> times = MediaDatabase.getInstance().getVideoTimes(); mVideoAdapter.setTimes(times); mGridView.setSelection(mGVFirstVisiblePos); updateViewMode(); if (mGroup == null && refresh) mAnimator.animate(); /* Start the thumbnailer */ if (mThumbnailer != null) mThumbnailer.start(this); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(KEY_GROUP, mGroup); } @Override public void onDestroyView() { getActivity().unregisterReceiver(messageReceiverVideoListFragment); super.onDestroyView(); } @Override public void onDestroy() { super.onDestroy(); if (mThumbnailer != null) mThumbnailer.clearJobs(); mVideoAdapter.clear(); } protected String getTitle(){ if (mGroup == null) return getString(R.string.video); else return mGroup + "\u2026"; } private void updateViewMode() { if (getView() == null || getActivity() == null) { Log.w(TAG, "Unable to setup the view"); return; } Resources res = getResources(); boolean listMode = res.getBoolean(R.bool.list_mode); listMode |= res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT && PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("force_list_portrait", false); // Compute the left/right padding dynamically DisplayMetrics outMetrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(outMetrics); int sidePadding; // Select between grid or list if (!listMode) { sidePadding = (int) ((float)outMetrics.widthPixels / 100f * (float)Math.pow(outMetrics.density, 3) / 2f); mGridView.setNumColumns(GridView.AUTO_FIT); mGridView.setColumnWidth(res.getDimensionPixelSize(R.dimen.grid_card_width)); mGridView.setVerticalSpacing(res.getDimensionPixelSize(R.dimen.grid_card_vertical_spacing)); mVideoAdapter.setListMode(false); } else { sidePadding = res.getDimensionPixelSize(R.dimen.listview_side_padding); mGridView.setNumColumns(1); mGridView.setVerticalSpacing(0); mGridView.setHorizontalSpacing(0); mVideoAdapter.setListMode(true); } sidePadding = Math.max(0, Math.min(100, sidePadding)); mGridView.setPadding(sidePadding, mGridView.getPaddingTop(), sidePadding, mGridView.getPaddingBottom()); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { MediaWrapper media = mVideoAdapter.getItem(position); if (media == null) return; if (media instanceof MediaGroup) { MainActivity activity = (MainActivity)getActivity(); activity.showSecondaryFragment(SecondaryActivity.VIDEO_GROUP_LIST, media.getTitle()); } else playVideo(media, false); } protected void playVideo(MediaWrapper media, boolean fromStart) { media.removeFlags(MediaWrapper.MEDIA_FORCE_AUDIO); VideoPlayerActivity.start(getActivity(), media.getUri(), fromStart); } protected void playAudio(MediaWrapper media) { if (mService != null) { media.addFlags(MediaWrapper.MEDIA_FORCE_AUDIO); mService.load(media); } } private boolean handleContextItemSelected(MenuItem menu, int position) { if (position >= mVideoAdapter.getCount()) return false; MediaWrapper media = mVideoAdapter.getItem(position); if (media == null) return false; switch (menu.getItemId()){ case R.id.video_list_play_from_start: playVideo(media, true); return true; case R.id.video_list_play_audio: playAudio(media); return true; case R.id.video_list_info: Activity activity = getActivity(); if (activity instanceof MainActivity) ((MainActivity)activity).showSecondaryFragment(SecondaryActivity.MEDIA_INFO, media.getLocation()); else { Intent i = new Intent(activity, SecondaryActivity.class); i.putExtra("fragment", "mediaInfo"); i.putExtra("param", media.getLocation()); startActivity(i); } return true; case R.id.video_list_delete: Snackbar.make(getView(), getString(R.string.file_deleted), DELETE_DURATION) .setAction(android.R.string.cancel, mCancelDeleteMediaListener) .show(); Message msg = mDeleteHandler.obtainMessage(DELETE_MEDIA, position, 0); mDeleteHandler.sendMessageDelayed(msg, DELETE_DURATION); return true; } return false; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { // Do not show the menu of media group. AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo; MediaWrapper media = mVideoAdapter.getItem(info.position); if (media == null || media instanceof MediaGroup) return; MenuInflater inflater = getActivity().getMenuInflater(); inflater.inflate(R.menu.video_list, menu); setContextMenuItems(menu, media); } private void setContextMenuItems(Menu menu, MediaWrapper mediaWrapper) { long lastTime = mediaWrapper.getTime(); if (lastTime > 0) menu.findItem(R.id.video_list_play_from_start).setVisible(true); boolean hasInfo = false; final Media media = new Media(VLCInstance.get(), mediaWrapper.getUri()); media.parse(); if (media.getMeta(Media.Meta.Title) != null) hasInfo = true; media.release(); menu.findItem(R.id.video_list_info).setVisible(hasInfo); menu.findItem(R.id.video_list_delete).setVisible(!AndroidUtil.isLolliPopOrLater() || mediaWrapper.getLocation().startsWith("file://" + AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY)); } @Override public boolean onContextItemSelected(MenuItem menu) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) menu.getMenuInfo(); if (info != null && handleContextItemSelected(menu, info.position)) return true; return super.onContextItemSelected(menu); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void onContextPopupMenu(View anchor, final int position) { if (!AndroidUtil.isHoneycombOrLater()) { // Call the "classic" context menu anchor.performLongClick(); return; } PopupMenu popupMenu = new PopupMenu(getActivity(), anchor); popupMenu.getMenuInflater().inflate(R.menu.video_list, popupMenu.getMenu()); MediaWrapper media = mVideoAdapter.getItem(position); if (media == null) return; setContextMenuItems(popupMenu.getMenu(), media); popupMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { return handleContextItemSelected(item, position); } }); popupMenu.show(); } /** * Handle changes on the list */ private Handler mHandler = new VideoListHandler(this); public void updateItem(MediaWrapper item) { mVideoAdapter.update(item); } private void focusHelper(boolean idIsEmpty) { View parent = getView(); if (getActivity() == null || !(getActivity() instanceof MainActivity)) return; MainActivity activity = (MainActivity)getActivity(); activity.setMenuFocusDown(idIsEmpty, android.R.id.list); activity.setSearchAsFocusDown(idIsEmpty, parent, android.R.id.list); } public void updateList() { if (!mSwipeRefreshLayout.isRefreshing()) mSwipeRefreshLayout.setRefreshing(true); final List<MediaWrapper> itemList = mMediaLibrary.getVideoItems(); if (mThumbnailer != null) mThumbnailer.clearJobs(); else Log.w(TAG, "Can't generate thumbnails, the thumbnailer is missing"); if (itemList.size() > 0) { VLCApplication.runBackground(new Runnable() { @Override public void run() { final ArrayList<MediaWrapper> displayList = new ArrayList<MediaWrapper>(); if (mGroup != null || itemList.size() <= 10) { for (MediaWrapper item : itemList) { if (mGroup == null || item.getTitle().startsWith(mGroup)) { displayList.add(item); if (mThumbnailer != null) mThumbnailer.addJob(item); } } } else { List<MediaGroup> groups = MediaGroup.group(itemList); for (MediaGroup item : groups) { displayList.add(item.getMedia()); if (mThumbnailer != null) mThumbnailer.addJob(item.getMedia()); } } if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { private void addAllV7() { for (MediaWrapper item : displayList) mVideoAdapter.add(item); } @TargetApi(android.os.Build.VERSION_CODES.HONEYCOMB) private void addAllV11() { mVideoAdapter.addAll(displayList); } @Override public void run() { mVideoAdapter.clear(); if (AndroidUtil.isHoneycombOrLater()) addAllV11(); else addAllV7(); } }); } if (mReadyToDisplay) display(); } }); } else focusHelper(true); stopRefresh(); } @Override public void showProgressBar() { if (mMainActivity != null) mMainActivity.showProgressBar(); } @Override public void hideProgressBar() { if (mMainActivity != null) mMainActivity.hideProgressBar(); } @Override public void clearTextInfo() { if (mMainActivity != null) mMainActivity.clearTextInfo(); } @Override public void sendTextInfo(String info, int progress, int max) { if (mMainActivity != null) mMainActivity.sendTextInfo(info, progress, max); } @Override public void sortBy(int sortby) { mVideoAdapter.sortBy(sortby); } @Override public int sortDirection(int sortby) { return mVideoAdapter.sortDirection(sortby); } public void setItemToUpdate(MediaWrapper item) { mHandler.sendMessage(mHandler.obtainMessage(VideoListHandler.UPDATE_ITEM, item)); } public void setGroup(String prefix) { mGroup = prefix; } private final BroadcastReceiver messageReceiverVideoListFragment = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equalsIgnoreCase(Util.ACTION_SCAN_START)) { mLayoutFlipperLoading.setVisibility(View.VISIBLE); mTextViewNomedia.setVisibility(View.INVISIBLE); } else if (action.equalsIgnoreCase(Util.ACTION_SCAN_STOP)) { mLayoutFlipperLoading.setVisibility(View.INVISIBLE); mTextViewNomedia.setVisibility(View.VISIBLE); } } }; public void stopRefresh() { mSwipeRefreshLayout.setRefreshing(false); } @Override public void onRefresh() { if (getActivity()!=null && !MediaLibrary.getInstance().isWorking()) MediaLibrary.getInstance().scanMediaItems(true); } @Override public void display() { if (getActivity() != null) getActivity().runOnUiThread(new Runnable() { @Override public void run() { mViewNomedia.setVisibility(mVideoAdapter.getCount() > 0 ? View.GONE : View.VISIBLE); mReadyToDisplay = true; mVideoAdapter.sort(); mGVFirstVisiblePos = mGridView.getFirstVisiblePosition(); mGridView.setSelection(mGVFirstVisiblePos); mGridView.requestFocus(); focusHelper(false); } }); } public void clear(){ mVideoAdapter.clear(); } public void deleteMedia(int position){ final MediaWrapper media = mVideoAdapter.getItem(position); final String path = media.getUri().getPath(); VLCApplication.runBackground(new Runnable() { public void run() { Util.recursiveDelete(VLCApplication.getAppContext(), new File(path)); } }); mMediaLibrary.getMediaItems().remove(media); mVideoAdapter.remove(media); if (mService != null) { final List<String> list = mService.getMediaLocations(); if (list != null && list.contains(media.getLocation())) { mService.removeLocation(media.getLocation()); } } } View.OnClickListener mCancelDeleteMediaListener = new View.OnClickListener() { @Override public void onClick(View view) { mDeleteHandler.removeMessages(DELETE_MEDIA); } }; Handler mDeleteHandler = new VideoDeleteHandler(this); private static class VideoDeleteHandler extends WeakHandler<VideoGridFragment>{ public VideoDeleteHandler(VideoGridFragment owner) { super(owner); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case DELETE_MEDIA: getOwner().deleteMedia(msg.arg1); } } } }