Java tutorial
package org.schabi.newpipe; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.app.ListFragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.AbsListView; import android.widget.ListView; import android.widget.Toast; import java.io.IOException; import java.util.List; import org.schabi.newpipe.extractor.ExtractionException; import org.schabi.newpipe.extractor.SearchResult; import org.schabi.newpipe.extractor.StreamPreviewInfo; import org.schabi.newpipe.extractor.SearchEngine; import org.schabi.newpipe.extractor.StreamingService; /** * Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org> * VideoItemListFragment.java is part of NewPipe. * * NewPipe 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. * * NewPipe 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 NewPipe. If not, see <http://www.gnu.org/licenses/>. */ public class VideoItemListFragment extends ListFragment { private static final String TAG = VideoItemListFragment.class.toString(); private StreamingService streamingService; private VideoListAdapter videoListAdapter; // activity modes private static final int SEARCH_MODE = 0; private static final int PRESENT_VIDEOS_MODE = 1; private int mode; private String query = ""; private int lastPage; private Thread searchThread; private SearchRunnable searchRunnable; // used to track down if results posted by threads ar still valid private int currentRequestId = -1; private ListView list; private View footer; // used to suppress request for loading a new page while another page is already loading. private boolean loadingNextPage = true; private class ResultRunnable implements Runnable { private final SearchResult result; private final int requestId; public ResultRunnable(SearchResult result, int requestId) { this.result = result; this.requestId = requestId; } @Override public void run() { updateListOnResult(result, requestId); if (android.os.Build.VERSION.SDK_INT >= 19) { getListView().removeFooterView(footer); } } } private class SearchRunnable implements Runnable { public static final String YOUTUBE = "Youtube"; private final SearchEngine engine; private final String query; private final int page; final Handler h = new Handler(); private volatile boolean runs = true; private final int requestId; public SearchRunnable(SearchEngine engine, String query, int page, int requestId) { this.engine = engine; this.query = query; this.page = page; this.requestId = requestId; } void terminate() { runs = false; } @Override public void run() { SearchResult result = null; try { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); String searchLanguageKey = getContext().getString(R.string.search_language_key); String searchLanguage = sp.getString(searchLanguageKey, getString(R.string.default_language_value)); result = SearchResult.getSearchResult(engine, query, page, searchLanguage, new Downloader()); if (runs) { h.post(new ResultRunnable(result, requestId)); } // look for errors during extraction // soft errors: if (result != null && !result.errors.isEmpty()) { Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:"); for (Exception e : result.errors) { e.printStackTrace(); Log.e(TAG, "------"); } Activity a = getActivity(); View rootView = a.findViewById(R.id.videoitem_list); ErrorActivity.reportError(h, getActivity(), result.errors, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, /* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.light_parsing_error)); } // hard errors: } catch (IOException e) { postNewNothingFoundToast(h, R.string.network_error); e.printStackTrace(); } catch (SearchEngine.NothingFoundException e) { postNewErrorToast(h, e.getMessage()); } catch (ExtractionException e) { ErrorActivity.reportError(h, getActivity(), e, null, null, ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, /* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.parsing_error)); //postNewErrorToast(h, R.string.parsing_error); e.printStackTrace(); } catch (Exception e) { ErrorActivity.reportError(h, getActivity(), e, null, null, ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, /* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.general_error)); e.printStackTrace(); } } } public void present(List<StreamPreviewInfo> videoList) { mode = PRESENT_VIDEOS_MODE; setListShown(true); getListView().smoothScrollToPosition(0); updateList(videoList); } public void search(String query) { mode = SEARCH_MODE; this.query = query; this.lastPage = 1; videoListAdapter.clearVideoList(); setListShown(false); startSearch(query, lastPage); //todo: Somehow this command is not working on older devices, // although it was introduced with API level 8. Test this and find a solution. getListView().smoothScrollToPosition(0); } private void nextPage() { loadingNextPage = true; lastPage++; Log.d(TAG, getString(R.string.search_page) + Integer.toString(lastPage)); startSearch(query, lastPage); } private void startSearch(String query, int page) { currentRequestId++; terminateThreads(); searchRunnable = new SearchRunnable(streamingService.getSearchEngineInstance(new Downloader()), query, page, currentRequestId); searchThread = new Thread(searchRunnable); searchThread.start(); } public void setStreamingService(StreamingService streamingService) { this.streamingService = streamingService; } private void updateListOnResult(SearchResult result, int requestId) { if (requestId == currentRequestId) { setListShown(true); updateList(result.resultList); if (!result.suggestion.isEmpty()) { Toast.makeText(getActivity(), String.format(getString(R.string.did_you_mean), result.suggestion), Toast.LENGTH_LONG).show(); } } } private void updateList(List<StreamPreviewInfo> list) { try { videoListAdapter.addVideoList(list); terminateThreads(); } catch (java.lang.IllegalStateException e) { Toast.makeText(getActivity(), "Trying to set value while activity doesn't exist anymore.", Toast.LENGTH_SHORT).show(); Log.w(TAG, "Trying to set value while activity doesn't exist anymore."); } catch (Exception e) { Toast.makeText(getActivity(), getString(R.string.general_error), Toast.LENGTH_SHORT).show(); e.printStackTrace(); } finally { loadingNextPage = false; } } private void terminateThreads() { if (searchThread != null) { searchRunnable.terminate(); // No need to join, since we don't really terminate the thread. We just demand // it to post its result runnable into the gui main loop. } } /** * The serialization (saved instance state) Bundle key representing the * activated item position. Only used on tablets. */ private static final String STATE_ACTIVATED_POSITION = "activated_position"; /** * The current activated item position. Only used on tablets. */ private int mActivatedPosition = ListView.INVALID_POSITION; /** * A callback interface that all activities containing this fragment must * implement. This mechanism allows activities to be notified of item * selections. */ public interface Callbacks { /** * Callback for when an item has been selected. */ void onItemSelected(String id); } private Callbacks mCallbacks; @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); list = getListView(); videoListAdapter = new VideoListAdapter(getActivity(), this); footer = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE)) .inflate(R.layout.paginate_footer, null, false); setListAdapter(videoListAdapter); // Restore the previously serialized activated item position. if (savedInstanceState != null && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) { setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION)); } getListView().setOnScrollListener(new AbsListView.OnScrollListener() { long lastScrollDate; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (mode != PRESENT_VIDEOS_MODE && list.getChildAt(0) != null && list.getLastVisiblePosition() == list.getAdapter().getCount() - 1 && list.getChildAt(list.getChildCount() - 1).getBottom() >= list.getHeight()) { long time = System.currentTimeMillis(); if ((time - lastScrollDate) > 200 && !loadingNextPage) { lastScrollDate = time; getListView().addFooterView(footer); nextPage(); } } } }); } @Override public void onAttach(Context context) { super.onAttach(context); // Activities containing this fragment must implement its callbacks. if (!(context instanceof Callbacks)) { throw new IllegalStateException("Activity must implement fragment's callbacks."); } mCallbacks = (Callbacks) context; } @Override public void onListItemClick(ListView listView, View view, int position, long id) { super.onListItemClick(listView, view, position, id); setActivatedPosition(position); mCallbacks.onItemSelected(Long.toString(id)); } /** * Turns on activate-on-click mode. When this mode is on, list items will be * given the 'activated' state when touched. */ public void setActivateOnItemClick(@SuppressWarnings("SameParameterValue") boolean activateOnItemClick) { // When setting CHOICE_MODE_SINGLE, ListView will automatically // give items the 'activated' state when touched. getListView().setChoiceMode(activateOnItemClick ? ListView.CHOICE_MODE_SINGLE : ListView.CHOICE_MODE_NONE); } private void setActivatedPosition(int position) { if (position == ListView.INVALID_POSITION) { getListView().setItemChecked(mActivatedPosition, false); } else { getListView().setItemChecked(position, true); } mActivatedPosition = position; } private void postNewErrorToast(Handler h, final String message) { h.post(new Runnable() { @Override public void run() { setListShown(true); Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); } }); } private void postNewNothingFoundToast(Handler h, final int stringResource) { h.post(new Runnable() { @Override public void run() { setListShown(true); Toast.makeText(getActivity(), getString(stringResource), Toast.LENGTH_LONG).show(); } }); } }