package org.edx.mobile.player; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.DialogFragment; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.ListView; import android.widget.Toast; import com.google.inject.Inject; import org.edx.mobile.R; import org.edx.mobile.base.BaseFragment; import org.edx.mobile.core.IEdxEnvironment; import org.edx.mobile.interfaces.SectionItemInterface; import org.edx.mobile.logger.Logger; import org.edx.mobile.model.api.EnrolledCoursesResponse; import org.edx.mobile.model.api.VideoResponseModel; import org.edx.mobile.model.db.DownloadEntry; import org.edx.mobile.module.analytics.ISegment; import org.edx.mobile.module.db.DataCallback; import org.edx.mobile.module.prefs.LoginPrefs; import org.edx.mobile.services.LastAccessManager; import org.edx.mobile.task.CircularProgressTask; import org.edx.mobile.util.AppConstants; import org.edx.mobile.util.NetworkUtil; import org.edx.mobile.util.ResourceUtil; import org.edx.mobile.view.Router; import org.edx.mobile.view.VideoListActivity; import org.edx.mobile.view.adapters.MyAllVideoAdapter; import org.edx.mobile.view.adapters.VideoBaseAdapter; import org.edx.mobile.view.custom.ProgressWheel; import org.edx.mobile.view.dialog.DeleteVideoDialogFragment; import org.edx.mobile.view.dialog.IDialogCallback; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class VideoListFragment extends BaseFragment { public static View offlineBar; private boolean isLandscape = false; private VideoBaseAdapter<SectionItemInterface> adapter; private ListView videoListView; private boolean isActivityStarted; private static final int MSG_UPDATE_PROGRESS = 1022; private DeleteVideoDialogFragment confirmDeleteFragment, downloadSizeExceedDialog; private String openInBrowserUrl; private String chapterName; private EnrolledCoursesResponse enrollment; private int playingVideoIndex = -1; private VideoListCallback callback; private DownloadEntry videoModel; private boolean downloadAvailable = false; private Button deleteButton; @Inject TranscriptManager transcriptManager; @Inject LastAccessManager lastAccessManager; @Inject protected IEdxEnvironment environment; private final Logger logger = new Logger(getClass().getName()); @Inject LoginPrefs loginPrefs; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); return inflater.inflate(R.layout.fragment_video_list, null); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); restore(savedInstanceState); Intent extraIntent = getActivity().getIntent(); if (extraIntent != null) { // read incoming enrollment model if (enrollment == null) { enrollment = (EnrolledCoursesResponse) extraIntent .getSerializableExtra(Router.EXTRA_COURSE_DATA); } } videoListView = (ListView) getView().findViewById(R.id.list_video); if (videoListView != null) { isLandscape = false; videoListView.setEmptyView(getView().findViewById( R.id.empty_list_view)); offlineBar = getView().findViewById(R.id.offline_bar); if (!(NetworkUtil.isConnected(getActivity()))) { if (offlineBar != null) offlineBar.setVisibility(View.VISIBLE); showDeletePanel(getView()); } else { if (offlineBar != null) { offlineBar.setVisibility(View.GONE); } } setAdaptertoVideoList(); } else { isLandscape = true; // probably the landscape player view, so hide action bar ActionBar bar = ((AppCompatActivity) getActivity()).getSupportActionBar(); if (bar != null) { bar.hide(); } } } public void setAdaptertoVideoList() { showDeletePanel(getView()); addDataToMyVideoAdapter(); } private void addDataToMyVideoAdapter() { try { String selectedId = null; if (adapter != null) { selectedId = adapter.getVideoId(); } adapter = new MyAllVideoAdapter(getActivity(), environment) { @Override public void onItemClicked(SectionItemInterface model, int position) { if (!AppConstants.myVideosDeleteMode) { //Check if the model is a DownloadEntry if (model.isDownload()) { DownloadEntry downloadEntry = (DownloadEntry) model; if (downloadEntry.isVideoForWebOnly) { Toast.makeText(getActivity(), R.string.video_only_on_web_short, Toast.LENGTH_SHORT).show(); } if (downloadEntry.isDownloaded()) { adapter.setVideoId(downloadEntry.videoId); // hide delete panel first, so that multiple-tap is blocked hideDeletePanel(VideoListFragment.this.getView()); showPlayer(); // initialize index for this model playingVideoIndex = position; play(model); notifyAdapter(); } } } } @Override public void onSelectItem() { handleCheckboxSelection(); } }; if (selectedId != null) { adapter.setVideoId(selectedId); } videoListView.setAdapter(adapter); videoListView.setOnItemClickListener(adapter); setActivityTitle(enrollment.getCourse().getName()); environment.getSegment().trackScreenView(ISegment.Screens.MY_VIDEOS_ALL); ArrayList<SectionItemInterface> list = environment.getStorage() .getSortedOrganizedVideosByCourse(enrollment.getCourse().getId()); downloadAvailable = false; if (list == null || list.size() == 0) { hideDeletePanel(getView()); downloadAvailable = false; } else { downloadAvailable = true; } for (SectionItemInterface m : list) { adapter.add(m); } notifyAdapter(); } catch (Exception e) { logger.error(e); } } /** * This function sets text to the title in Action Bar * * @param title */ private void setActivityTitle(String title) { try { getActivity().setTitle(title); } catch (Exception e) { logger.error(e); } } private void play(SectionItemInterface model) { if (model instanceof DownloadEntry) { DownloadEntry v = (DownloadEntry) model; try { final VideoResponseModel vrm = environment.getServiceManager().getVideoById(v.eid, v.videoId); lastAccessManager.setLastAccessed(v.eid, vrm.getSection().getId()); // capture chapter name if (chapterName == null) { // capture the chapter name of this video chapterName = v.chapter; } if (callback != null) { videoModel = v; callback.playVideoModel(v); adapter.setVideoId(videoModel.videoId); adapter.setSelectedPosition(playingVideoIndex); adapter.notifyDataSetChanged(); } } catch (Exception e) { logger.error(e); } } } private void showPlayer() { hideDeletePanel(getView()); adapter.setIsPlayerOn(true); adapter.setSelectedPosition(playingVideoIndex); adapter.notifyDataSetChanged(); try { if (getView() != null) { View container = getView().findViewById(R.id.container_player); if (container != null) { container.setVisibility(View.VISIBLE); } } } catch (Exception ex) { logger.error(ex); logger.warn("Error in showing player"); } } public void onOffline() { if (!isLandscape) { if (offlineBar != null) { offlineBar.setVisibility(View.VISIBLE); } } } public void handleDeleteView() { if (isPlayerVisible()) { hideDeletePanel(getView()); } else { if (downloadAvailable) { showDeletePanel(getView()); } } } public void onOnline() { if (!isLandscape) { if (offlineBar != null) { offlineBar.setVisibility(View.GONE); } } } private void hideDeletePanel(View view) { try { View v = view.findViewById(R.id.delete_button_panel); if (v != null) { v.setVisibility(View.GONE); } handler.postDelayed(new Runnable() { public void run() { try { // hide checkbox in action bar ((VideoListActivity) getActivity()).hideCheckBox(); // hide checkboxes in list notifyAdapter(); } catch (Exception ex) { logger.error(ex); } } }, 300); } catch (Exception ex) { logger.warn("Error in hiding delete button Panel"); logger.error(ex); } } private void showDeletePanel(View view) { try { if (isPlayerVisible()) { hideDeletePanel(view); } else { view.findViewById(R.id.delete_button_panel).setVisibility( View.VISIBLE); deleteButton = (Button) view.findViewById( R.id.delete_btn); final Button editButton = (Button) view.findViewById( R.id.edit_btn); final Button cancelButton = (Button) view.findViewById( R.id.cancel_btn); if (AppConstants.myVideosDeleteMode) { deleteButton.setVisibility(View.VISIBLE); cancelButton.setVisibility(View.VISIBLE); editButton.setVisibility(View.GONE); } else { deleteButton.setVisibility(View.GONE); cancelButton.setVisibility(View.GONE); editButton.setVisibility(View.VISIBLE); } deleteButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ArrayList<SectionItemInterface> list; list = adapter.getSelectedItems(); if (list != null && list.size() > 0) { // Confirmation Dialog before deleting Videos showConfirmDeleteDialog(list.size()); } } }); cancelButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { editButton.setVisibility(View.VISIBLE); AppConstants.myVideosDeleteMode = false; if (getActivity() instanceof VideoListActivity) { ((VideoListActivity) getActivity()).hideCheckBox(); } adapter.unselectAll(); videoListView.setOnItemClickListener(adapter); adapter.setSelectedPosition(playingVideoIndex); adapter.notifyDataSetChanged(); deleteButton.setVisibility(View.GONE); cancelButton.setVisibility(View.GONE); } }); editButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { editButton.setVisibility(View.GONE); AppConstants.myVideosDeleteMode = true; adapter.setSelectedPosition(playingVideoIndex); adapter.notifyDataSetChanged(); disableDeleteButton(); deleteButton.setVisibility(View.VISIBLE); cancelButton.setVisibility(View.VISIBLE); if (getActivity() instanceof VideoListActivity) { ((VideoListActivity) getActivity()).showCheckBox(); } } }); } } catch (Exception ex) { logger.warn("Error in showing delete panel"); logger.error(ex); } } @Override public void onStop() { super.onStop(); isActivityStarted = false; AppConstants.videoListDeleteMode = false; hideConfirmDeleteDialog(); adapter.unselectAll(); } @Override public void onDestroy() { super.onDestroy(); adapter.setIsPlayerOn(false); } @Override public void onStart() { super.onStart(); isActivityStarted = true; } public void setAllVideosChecked() { adapter.selectAll(); int count = adapter.getSelectedVideoItemsCount(); logger.debug("Video Count of selected videos" + count); enableDeleteButton(); adapter.setSelectedPosition(playingVideoIndex); adapter.notifyDataSetChanged(); } public void unsetAllVideosChecked() { adapter.unselectAll(); disableDeleteButton(); adapter.setSelectedPosition(playingVideoIndex); adapter.notifyDataSetChanged(); } private final Handler handler = new Handler(); public void markPlaying() { environment.getStorage().markVideoPlaying(videoModel, watchedStateCallback); } /** * This method inserts the Download Entry Model in the database * Called when a user clicks on a Video in the list * * @param v - Download Entry object */ public void addVideoDatatoDb(final DownloadEntry v) { try { if (v != null) { environment.getDatabase().addVideoData(v, new DataCallback<Long>() { @Override public void onResult(Long result) { if (result != -1) { logger.debug("Video entry inserted" + v.videoId); } } @Override public void onFail(Exception ex) { logger.error(ex); } }); } } catch (Exception ex) { logger.error(ex); } } public void saveCurrentPlaybackPosition(int offset) { try { DownloadEntry v = videoModel; if (v != null) { // mark this as partially watches, as playing has started environment.getDatabase().updateVideoLastPlayedOffset(v.videoId, offset, setCurrentPositionCallback); } } catch (Exception ex) { logger.error(ex); } } public void onPlaybackComplete() { try { DownloadEntry v = videoModel; if (v == null) { v = (DownloadEntry) adapter.getItem(playingVideoIndex); } if (v != null && v.watched == DownloadEntry.WatchedState.PARTIALLY_WATCHED) { videoModel.watched = DownloadEntry.WatchedState.WATCHED; // mark this as partially watches, as playing has started environment.getDatabase().updateVideoWatchedState(v.videoId, DownloadEntry.WatchedState.WATCHED, watchedStateCallback); } } catch (Exception ex) { logger.error(ex); } } public void onConfirmDelete() { ArrayList<SectionItemInterface> list; int deletedVideoCount = 0; list = adapter.getSelectedItems(); if (list != null) { for (SectionItemInterface section : list) { if (section.isDownload()) { DownloadEntry de = (DownloadEntry) section; environment.getStorage().removeDownload(de); deletedVideoCount++; } } } addDataToMyVideoAdapter(); adapter.setSelectedPosition(playingVideoIndex); adapter.notifyDataSetChanged(); ((VideoListActivity) getActivity()).hideCheckBox(); AppConstants.myVideosDeleteMode = false; videoListView.setOnItemClickListener(adapter); if (adapter.getCount() <= 0) { finishActivity(); } if (deletedVideoCount > 0) { try { String format = ResourceUtil.getFormattedStringForQuantity(getResources(), R.plurals.deleted_video, "video_count", deletedVideoCount).toString(); ((VideoListActivity) getActivity()) .showInfoMessage(String.format(format, deletedVideoCount)); } catch (Exception ex) { logger.error(ex); } } getView().findViewById(R.id.delete_btn).setVisibility(View.GONE); getView().findViewById(R.id.edit_btn).setVisibility(View.VISIBLE); getView().findViewById(R.id.cancel_btn).setVisibility(View.GONE); } private void finishActivity() { handler.postDelayed(new Runnable() { @Override public void run() { if (isActivityStarted) { getActivity().finish(); } } }, 300); } private void handleCheckboxSelection() { int selectedItemsNo = adapter.getSelectedVideoItemsCount(); int totalVideos = adapter.getTotalVideoItemsCount(); if (selectedItemsNo == 0) { disableDeleteButton(); } else { enableDeleteButton(); } if (selectedItemsNo == totalVideos) { ((VideoListActivity) getActivity()).setSelectAllChecked(true); } else if (selectedItemsNo < totalVideos) { ((VideoListActivity) getActivity()).setSelectAllChecked(false); } } public void startDownload(final DownloadEntry downloadEntry, final ProgressWheel progressWheel) { try { if (environment.getStorage() != null) { boolean isVideoFilePresentByUrl = environment.getDatabase().isVideoFilePresentByUrl( downloadEntry.url, null); boolean reloadListFlag = true; if (isVideoFilePresentByUrl) { CircularProgressTask circularTask = new CircularProgressTask(); circularTask.setProgressBar(progressWheel); circularTask.execute(); reloadListFlag = false; } if (environment.getSegment() != null) { environment.getSegment().trackSingleVideoDownload(downloadEntry.videoId, downloadEntry.eid, downloadEntry.lmsUrl); } if (environment.getStorage().addDownload(downloadEntry) != -1) { ((VideoListActivity) getActivity()) .showInfoMessage(getString(R.string.msg_started_one_video_download)); } else { ((VideoListActivity) getActivity()) .showInfoMessage(getString(R.string.msg_video_not_downloaded)); } ((VideoListActivity) getActivity()).updateProgress(); //If the video is already downloaded, dont reload the adapter if (reloadListFlag) { adapter.notifyDataSetChanged(); } transcriptManager.downloadTranscriptsForVideo(downloadEntry.transcript); } } catch (Exception e) { logger.error(e); } } protected void showStartDownloadDialog(final DownloadEntry de, final ProgressWheel progressWheel) { Map<String, String> dialogMap = new HashMap<String, String>(); dialogMap.put("title", getString(R.string.download_exceed_title)); dialogMap.put("message_1", getString(R.string.download_exceed_message)); dialogMap.put("yes_button", getString(R.string.label_yes)); dialogMap.put("no_button", getString(R.string.label_no)); downloadSizeExceedDialog = DeleteVideoDialogFragment.newInstance(dialogMap, new IDialogCallback() { @Override public void onPositiveClicked() { startDownload(de, progressWheel); } @Override public void onNegativeClicked() { notifyAdapter(); downloadSizeExceedDialog.dismiss(); } }); downloadSizeExceedDialog.setStyle(DialogFragment.STYLE_NO_TITLE, 0); downloadSizeExceedDialog.show(getFragmentManager(), "dialog"); downloadSizeExceedDialog.setCancelable(false); } protected void showConfirmDeleteDialog(int itemCount) { Map<String, String> dialogMap = new HashMap<String, String>(); dialogMap.put("title", getString(R.string.delete_dialog_title_help)); dialogMap.put("yes_button", getString(R.string.label_delete)); dialogMap.put("no_button", getString(R.string.label_cancel)); dialogMap.put("message_1", getResources().getQuantityString(R.plurals.delete_video_dialog_msg, itemCount)); confirmDeleteFragment = DeleteVideoDialogFragment.newInstance(dialogMap, new IDialogCallback() { @Override public void onPositiveClicked() { onConfirmDelete(); } @Override public void onNegativeClicked() { confirmDeleteFragment.dismiss(); } }); confirmDeleteFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0); confirmDeleteFragment.show(getFragmentManager(), "dialog"); confirmDeleteFragment.setCancelable(false); } protected void hideConfirmDeleteDialog() { try { if (confirmDeleteFragment != null) { confirmDeleteFragment.dismiss(); } } catch (Exception e) { logger.error(e); } } private boolean isPlayerVisible() { if (getActivity() == null) { return false; } View container = getActivity().findViewById(R.id.container_player); return (container != null && container.getVisibility() == View.VISIBLE); } public void notifyAdapter() { adapter.setSelectedPosition(playingVideoIndex); adapter.notifyDataSetChanged(); } public void setCallback(VideoListCallback callback) { this.callback = callback; } @Override public void onSaveInstanceState(Bundle outState) { outState.putInt("playingVideoIndex", playingVideoIndex); outState.putSerializable("model", videoModel); super.onSaveInstanceState(outState); } protected void restore(Bundle savedInstanceState) { if (savedInstanceState != null) { playingVideoIndex = savedInstanceState.getInt("playingVideoIndex", -1); videoModel = (DownloadEntry) savedInstanceState.getSerializable("model"); } } public static interface VideoListCallback { public void playVideoModel(DownloadEntry video); } private void disableDeleteButton() { deleteButton.setEnabled(false); } private void enableDeleteButton() { deleteButton.setEnabled(true); } public void playNext() { try { //playingVideoIndex++; int videoPos = adapter.getPositionByVideoId(videoModel.videoId); if (videoPos != -1) { // check next playable video entry videoPos++; while (videoPos < adapter.getCount()) { SectionItemInterface i = adapter.getItem(videoPos); if (i != null && i instanceof DownloadEntry) { if (!NetworkUtil.isConnected(getActivity())) { DownloadEntry de = (DownloadEntry) i; if (de.isDownloaded()) { if (callback != null) { videoModel = de; playingVideoIndex = videoPos; adapter.setSelectedPosition(playingVideoIndex); adapter.setVideoId(videoModel.videoId); callback.playVideoModel(videoModel); break; } } } else { if (callback != null) { videoModel = (DownloadEntry) i; playingVideoIndex = videoPos; adapter.setVideoId(videoModel.videoId); adapter.setSelectedPosition(playingVideoIndex); callback.playVideoModel(videoModel); break; } } } // try next videoPos++; } } } catch (Exception e) { logger.error(e); } } public boolean hasNextVideo() { try { if (videoModel != null) { int videoPos = adapter.getPositionByVideoId(videoModel.videoId); if (videoPos != -1) { for (int i = (videoPos + 1); i < adapter.getCount(); i++) { SectionItemInterface d = adapter.getItem(i); if (d != null && d instanceof DownloadEntry) { DownloadEntry de = (DownloadEntry) d; if (!NetworkUtil.isConnected(getActivity())) { if (de.isDownloaded()) { return true; } } else { return true; } } } } } } catch (Exception e) { logger.error(e); } return false; } public void playPrevious() { try { //playingVideoIndex--; int videoPos = adapter.getPositionByVideoId(videoModel.videoId); if (videoPos != -1) { // check next playable video entry videoPos--; while (videoPos >= 0) { SectionItemInterface i = adapter.getItem(videoPos); if (i != null && i instanceof DownloadEntry) { if (!NetworkUtil.isConnected(getActivity())) { DownloadEntry de = (DownloadEntry) i; if (de.isDownloaded()) { if (callback != null) { videoModel = de; adapter.setVideoId(videoModel.videoId); playingVideoIndex = videoPos; adapter.setSelectedPosition(playingVideoIndex); callback.playVideoModel(videoModel); break; } } } else { if (callback != null) { videoModel = (DownloadEntry) i; adapter.setVideoId(videoModel.videoId); playingVideoIndex = videoPos; adapter.setSelectedPosition(playingVideoIndex); callback.playVideoModel(videoModel); break; } } } videoPos--; } } } catch (Exception e) { logger.error(e); } } public boolean hasPreviousVideo() { try { if (videoModel != null) { int videoPos = adapter.getPositionByVideoId(videoModel.videoId); if (videoPos != -1) { for (int i = (videoPos - 1); i >= 0; i--) { SectionItemInterface d = adapter.getItem(i); if (d != null && d instanceof DownloadEntry) { DownloadEntry de = (DownloadEntry) d; if (!NetworkUtil.isConnected(getActivity())) { if (de.isDownloaded()) { return true; } } else { return true; } } } } } } catch (Exception e) { logger.error(e); } return false; } public View.OnClickListener getNextListener() { if (hasNextVideo()) { return new NextClickListener(); } return null; } public View.OnClickListener getPreviousListener() { if (hasPreviousVideo()) { return new PreviousClickListener(); } return null; } private class NextClickListener implements OnClickListener { @Override public void onClick(View v) { playNext(); } } private class PreviousClickListener implements OnClickListener { @Override public void onClick(View v) { playPrevious(); } } private DataCallback<Integer> watchedStateCallback = new DataCallback<Integer>() { @Override public void onResult(Integer result) { logger.debug("Watched State Updated"); } @Override public void onFail(Exception ex) { logger.error(ex); } }; private DataCallback<Integer> setCurrentPositionCallback = new DataCallback<Integer>() { @Override public void onResult(Integer result) { logger.debug("Current Playback Position Updated"); } @Override public void onFail(Exception ex) { logger.error(ex); } }; }