package tv.emby.embyatv.itemhandling; import android.os.Handler; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.HeaderItem; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.PresenterSelector; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.GregorianCalendar; import java.util.List; import java.util.TimeZone; import mediabrowser.apiinteraction.EmptyResponse; import mediabrowser.apiinteraction.Response; import mediabrowser.model.apiclient.ServerInfo; import mediabrowser.model.dto.BaseItemDto; import mediabrowser.model.dto.BaseItemPerson; import mediabrowser.model.dto.UserDto; import mediabrowser.model.livetv.ChannelInfoDto; import mediabrowser.model.livetv.LiveTvChannelQuery; import mediabrowser.model.livetv.RecommendedProgramQuery; import mediabrowser.model.livetv.RecordingGroupQuery; import mediabrowser.model.livetv.RecordingQuery; import mediabrowser.model.net.HttpException; import mediabrowser.model.querying.ArtistsQuery; import mediabrowser.model.querying.ItemFields; import mediabrowser.model.querying.ItemQuery; import mediabrowser.model.querying.ItemsResult; import mediabrowser.model.querying.NextUpQuery; import mediabrowser.model.querying.PersonsQuery; import mediabrowser.model.querying.SeasonQuery; import mediabrowser.model.querying.SimilarItemsQuery; import mediabrowser.model.querying.UpcomingEpisodesQuery; import mediabrowser.model.results.ChannelInfoDtoResult; import mediabrowser.model.search.SearchHint; import mediabrowser.model.search.SearchHintResult; import mediabrowser.model.search.SearchQuery; import tv.emby.embyatv.R; import tv.emby.embyatv.TvApp; import tv.emby.embyatv.browsing.EnhancedBrowseFragment; import tv.emby.embyatv.livetv.TvManager; import tv.emby.embyatv.model.ChangeTriggerType; import tv.emby.embyatv.model.ChapterItemInfo; import tv.emby.embyatv.model.FilterOptions; import tv.emby.embyatv.playback.MediaManager; import tv.emby.embyatv.presentation.IPositionablePresenter; import tv.emby.embyatv.presentation.TextItemPresenter; import tv.emby.embyatv.querying.QueryType; import tv.emby.embyatv.querying.SpecialsQuery; import tv.emby.embyatv.querying.StdItemQuery; import tv.emby.embyatv.querying.TrailersQuery; import tv.emby.embyatv.querying.ViewQuery; import tv.emby.embyatv.ui.GridButton; import tv.emby.embyatv.ui.HorizontalGridFragment; import tv.emby.embyatv.util.Utils; /** * Created by Eric on 12/5/2014. */ public class ItemRowAdapter extends ArrayObjectAdapter { private ItemQuery mQuery; private NextUpQuery mNextUpQuery; private SeasonQuery mSeasonQuery; private UpcomingEpisodesQuery mUpcomingQuery; private SimilarItemsQuery mSimilarQuery; private PersonsQuery mPersonsQuery; private SearchQuery mSearchQuery; private SpecialsQuery mSpecialsQuery; private TrailersQuery mTrailersQuery; private LiveTvChannelQuery mTvChannelQuery; private RecommendedProgramQuery mTvProgramQuery; private RecordingQuery mTvRecordingQuery; private RecordingGroupQuery mTvRecordingGroupQuery; private ArtistsQuery mArtistsQuery; private QueryType queryType; private String mSortBy; private FilterOptions mFilters; private EmptyResponse mRetrieveStartedListener; private EmptyResponse mRetrieveFinishedListener; private ChangeTriggerType[] reRetrieveTriggers = new ChangeTriggerType[] {}; private Calendar lastFullRetrieve; private BaseItemPerson[] mPersons; private ServerInfo[] mServers; private List<ChapterItemInfo> mChapters; private List<BaseItemDto> mItems; private ServerInfo mServer; private ArrayObjectAdapter mParent; private ListRow mRow; private int chunkSize = 0; private int itemsLoaded = 0; private int totalItems = 0; private boolean fullyLoaded = false; private boolean currentlyRetrieving = false; private boolean preferParentThumb = false; private boolean staticHeight = false; public boolean isCurrentlyRetrieving() { synchronized (this) { return currentlyRetrieving; } } public void setCurrentlyRetrieving(boolean currentlyRetrieving) { synchronized (this) { this.currentlyRetrieving = currentlyRetrieving; } } public boolean getPreferParentThumb() { return preferParentThumb; } public boolean isStaticHeight() { return staticHeight; } public QueryType getQueryType() { return queryType; } public ArrayObjectAdapter getParent() { return mParent; } public void setRow(ListRow row) { mRow = row; } public void setReRetrieveTriggers(ChangeTriggerType[] reRetrieveTriggers) { this.reRetrieveTriggers = reRetrieveTriggers; } public ItemRowAdapter(ItemQuery query, int chunkSize, boolean preferParentThumb, Presenter presenter, ArrayObjectAdapter parent) { this(query, chunkSize, preferParentThumb, false, presenter, parent); } public ItemRowAdapter(ItemQuery query, int chunkSize, boolean preferParentThumb, boolean staticHeight, Presenter presenter, ArrayObjectAdapter parent, QueryType queryType) { super(presenter); mParent = parent; mQuery = query; mQuery.setUserId(TvApp.getApplication().getCurrentUser().getId()); this.chunkSize = chunkSize; this.preferParentThumb = preferParentThumb; this.staticHeight = staticHeight; if (chunkSize > 0) mQuery.setLimit(chunkSize); this.queryType = queryType; add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(ItemQuery query, int chunkSize, boolean preferParentThumb, boolean staticHeight, PresenterSelector presenter, ArrayObjectAdapter parent, QueryType queryType) { super(presenter); mParent = parent; mQuery = query; mQuery.setUserId(TvApp.getApplication().getCurrentUser().getId()); this.chunkSize = chunkSize; this.preferParentThumb = preferParentThumb; this.staticHeight = staticHeight; if (chunkSize > 0) mQuery.setLimit(chunkSize); this.queryType = queryType; add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(ItemQuery query, int chunkSize, boolean preferParentThumb, boolean staticHeight, Presenter presenter, ArrayObjectAdapter parent) { this(query, chunkSize, preferParentThumb, staticHeight, presenter, parent, QueryType.Items); } public ItemRowAdapter(ArtistsQuery query, int chunkSize, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mArtistsQuery = query; mArtistsQuery.setUserId(TvApp.getApplication().getCurrentUser().getId()); staticHeight = true; this.chunkSize = chunkSize; if (chunkSize > 0) mArtistsQuery.setLimit(chunkSize); queryType = QueryType.AlbumArtists; add(new BaseRowItem(new GridButton(0, TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(NextUpQuery query, boolean preferParentThumb, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mNextUpQuery = query; mNextUpQuery.setUserId(TvApp.getApplication().getCurrentUser().getId()); queryType = QueryType.NextUp; this.preferParentThumb = preferParentThumb; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(BaseItemPerson[] people, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mPersons = people; queryType = QueryType.StaticPeople; } public ItemRowAdapter(List<ChapterItemInfo> chapters, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mChapters = chapters; queryType = QueryType.StaticChapters; } public ItemRowAdapter(List<BaseItemDto> items, Presenter presenter, ArrayObjectAdapter parent, QueryType queryType) { super(presenter); mParent = parent; mItems = items; this.queryType = queryType; } public ItemRowAdapter(List<BaseItemDto> items, Presenter presenter, ArrayObjectAdapter parent, boolean staticItems) { // last param is just for sig super(presenter); mParent = parent; mItems = items; queryType = QueryType.StaticItems; } public ItemRowAdapter(ServerInfo[] servers, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mServers = servers; queryType = QueryType.StaticServers; } public ItemRowAdapter(SpecialsQuery query, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mSpecialsQuery = query; queryType = QueryType.Specials; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(TrailersQuery query, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mTrailersQuery = query; queryType = QueryType.Trailers; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(LiveTvChannelQuery query, int chunkSize, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mTvChannelQuery = query; this.chunkSize = chunkSize; if (chunkSize > 0) mTvChannelQuery.setLimit(chunkSize); queryType = QueryType.LiveTvChannel; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(RecommendedProgramQuery query, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mTvProgramQuery = query; queryType = QueryType.LiveTvProgram; staticHeight = true; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(RecordingQuery query, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mTvRecordingQuery = query; queryType = QueryType.LiveTvRecording; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(RecordingGroupQuery query, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mTvRecordingGroupQuery = query; queryType = QueryType.LiveTvRecordingGroup; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(SimilarItemsQuery query, QueryType queryType, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mSimilarQuery = query; mSimilarQuery.setUserId(TvApp.getApplication().getCurrentUser().getId()); this.queryType = queryType; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(UpcomingEpisodesQuery query, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mUpcomingQuery = query; mUpcomingQuery.setUserId(TvApp.getApplication().getCurrentUser().getId()); queryType = QueryType.Upcoming; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(SeasonQuery query, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mSeasonQuery = query; mSeasonQuery.setUserId(TvApp.getApplication().getCurrentUser().getId()); queryType = QueryType.Season; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(PersonsQuery query, int chunkSize, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; this.chunkSize = chunkSize; mPersonsQuery = query; mPersonsQuery.setUserId(TvApp.getApplication().getCurrentUser().getId()); if (chunkSize > 0) mPersonsQuery.setLimit(chunkSize); queryType = QueryType.Persons; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(SearchQuery query, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mSearchQuery = query; mSearchQuery.setUserId(TvApp.getApplication().getCurrentUser().getId()); mSearchQuery.setLimit(50); queryType = QueryType.Search; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(ViewQuery query, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; queryType = QueryType.Views; staticHeight = true; add(new BaseRowItem(new GridButton(0,TvApp.getApplication().getString(R.string.lbl_loading_elipses), R.drawable.loading))); } public ItemRowAdapter(ServerInfo serverInfo, Presenter presenter, ArrayObjectAdapter parent) { super(presenter); mParent = parent; mServer = serverInfo; queryType = QueryType.Users; } public void setItemsLoaded(int itemsLoaded) { this.itemsLoaded = itemsLoaded; this.fullyLoaded = chunkSize == 0 || itemsLoaded >= totalItems; } public void setSearchString(String value) { if (mSearchQuery != null) { mSearchQuery.setSearchTerm(value); } } public int getItemsLoaded() { return itemsLoaded; } public void setTotalItems(int amt) { totalItems = amt; } public int getTotalItems() { return totalItems; } public void setSortBy(HorizontalGridFragment.SortOption option) { if (!option.value.equals(mSortBy)) { mSortBy = option.value; switch (queryType) { case AlbumArtists: mArtistsQuery.setSortBy(new String[]{mSortBy}); mArtistsQuery.setSortOrder(option.order); break; default: mQuery.setSortBy(new String[] {mSortBy}); mQuery.setSortOrder(option.order); break; } if (!"SortName".equals(option.value)) setStartLetter(null); } } public BaseRowItem findByIndex(int ndx) { //search for actual index number and return matching item for (int i = 0; i < getItemsLoaded(); i++) { BaseRowItem item = (BaseRowItem)this.get(i); if (item.getIndex() == ndx) return item; } return null; } public String getSortBy() { return mSortBy; } public FilterOptions getFilters() { return mFilters; } public void setFilters(FilterOptions filters) { mFilters = filters; switch (queryType) { case AlbumArtists: mArtistsQuery.setFilters(mFilters != null ? mFilters.getFilters() : null); break; default: mQuery.setFilters(mFilters != null ? mFilters.getFilters() : null); } } public void setPosition(int pos) { ((IPositionablePresenter)(getParent().getPresenter(this))).setPosition(pos); } public String getStartLetter() { return mQuery != null ? mQuery.getNameStartsWithOrGreater() : null; } public void setStartLetter(String value) { switch (queryType) { case AlbumArtists: if (value != null && value.equals("#")) mArtistsQuery.setNameStartsWithOrGreater(null); else mArtistsQuery.setNameStartsWithOrGreater(value); break; default: if (value != null && value.equals("#")) mQuery.setNameStartsWithOrGreater(null); else mQuery.setNameStartsWithOrGreater(value); break; } } public void removeRow() { if (mParent == null) { // just clear us clear(); return; }; if (mParent.size() == 1) { // we will be removing the last row - show something and prevent the framework from crashing // because there is nowhere for focus to land ArrayObjectAdapter emptyRow = new ArrayObjectAdapter(new TextItemPresenter()); emptyRow.add(TvApp.getApplication().getString(R.string.lbl_no_items)); mParent.add(new ListRow(new HeaderItem(TvApp.getApplication().getString(R.string.lbl_empty)), emptyRow)); } mParent.remove(mRow); } public void loadMoreItemsIfNeeded(long pos) { if (fullyLoaded) { //TvApp.getApplication().getLogger().Debug("Row is fully loaded"); return; } if (isCurrentlyRetrieving()) { TvApp.getApplication().getLogger().Debug("Not loading more because currently retrieving"); return; } if (pos >= itemsLoaded - 20) { TvApp.getApplication().getLogger().Debug("Loading more items starting at " + itemsLoaded); RetrieveNext(); } } public void RetrieveNext() { switch (queryType) { case Persons: if (fullyLoaded || mPersonsQuery == null || isCurrentlyRetrieving()) return; setCurrentlyRetrieving(true); //set the query to go get the next chunk mPersonsQuery.setStartIndex(itemsLoaded); Retrieve(mPersonsQuery); break; case LiveTvChannel: if (fullyLoaded || mTvChannelQuery == null || isCurrentlyRetrieving()) return; setCurrentlyRetrieving(true); //set the query to go get the next chunk mTvChannelQuery.setStartIndex(itemsLoaded); Retrieve(mTvChannelQuery); break; case AlbumArtists: if (fullyLoaded || mArtistsQuery == null || isCurrentlyRetrieving()) return; setCurrentlyRetrieving(true); //set the query to go get the next chunk mArtistsQuery.setStartIndex(itemsLoaded); Retrieve(mArtistsQuery); break; default: if (fullyLoaded || mQuery == null || isCurrentlyRetrieving()) return; setCurrentlyRetrieving(true); //set the query to go get the next chunk mQuery.setStartIndex(itemsLoaded); Retrieve(mQuery); break; } } public boolean ReRetrieveIfNeeded() { if (reRetrieveTriggers == null) return false; boolean retrieve = false; TvApp app = TvApp.getApplication(); for (ChangeTriggerType trigger : reRetrieveTriggers) { switch (trigger) { case LibraryUpdated: retrieve |= lastFullRetrieve.before(app.getLastLibraryChange()); break; case MoviePlayback: retrieve |= lastFullRetrieve.before(app.getLastMoviePlayback()); break; case TvPlayback: retrieve |= lastFullRetrieve.before(app.getLastTvPlayback()); break; case MusicPlayback: retrieve |= lastFullRetrieve.getTimeInMillis() < app.getLastMusicPlayback(); break; case FavoriteUpdate: retrieve |= lastFullRetrieve.getTimeInMillis() < app.getLastFavoriteUpdate(); break; case VideoQueueChange: retrieve |= lastFullRetrieve.getTimeInMillis() < app.getLastVideoQueueChange(); break; case GuideNeedsLoad: Calendar start = new GregorianCalendar(TimeZone.getTimeZone("Z")); start.set(Calendar.MINUTE, start.get(Calendar.MINUTE) >= 30 ? 30 : 0); start.set(Calendar.SECOND, 0); retrieve |= TvManager.programsNeedLoad(start); break; case Always: retrieve = true; break; } } if (retrieve) { TvApp.getApplication().getLogger().Info("Re-retrieving row of type "+ queryType); Retrieve(); } return retrieve; } public void Retrieve() { setCurrentlyRetrieving(true); lastFullRetrieve = Calendar.getInstance(); itemsLoaded = 0; notifyRetrieveStarted(); switch (queryType) { case Items: Retrieve(mQuery); break; case NextUp: Retrieve(mNextUpQuery); break; case Upcoming: Retrieve(mUpcomingQuery); break; case Season: Retrieve(mSeasonQuery); break; case Views: RetrieveViews(); break; case SimilarSeries: RetrieveSimilarSeries(mSimilarQuery); break; case SimilarMovies: RetrieveSimilarMovies(mSimilarQuery); break; case Persons: Retrieve(mPersonsQuery); break; case LiveTvChannel: Retrieve(mTvChannelQuery); break; case LiveTvProgram: Retrieve(mTvProgramQuery); break; case LiveTvRecording: Retrieve(mTvRecordingQuery); break; case LiveTvRecordingGroup: Retrieve(mTvRecordingGroupQuery); break; case StaticPeople: LoadPeople(); break; case StaticServers: LoadServers(); break; case StaticChapters: LoadChapters(); break; case StaticItems: LoadStaticItems(); break; case StaticAudioQueueItems: LoadStaticAudioItems(); break; case Specials: Retrieve(mSpecialsQuery); break; case Trailers: Retrieve(mTrailersQuery); break; case Users: RetrieveUsers(mServer); break; case Search: Retrieve(mSearchQuery); break; case AlbumArtists: Retrieve(mArtistsQuery); break; case AudioPlaylists: RetrieveAudioPlaylists(mQuery); break; case Premieres: RetrievePremieres(mQuery); break; case ContinueWatching: RetrieveContinueWatching(mQuery); break; } } private void RetrieveUsers(ServerInfo mServer) { final ItemRowAdapter adapter = this; TvApp.getApplication().getLoginApiClient().GetPublicUsersAsync(new Response<UserDto[]>() { @Override public void onResponse(UserDto[] response) { for (UserDto user : response) { adapter.add(new BaseRowItem(user)); } totalItems = response.length; setItemsLoaded(totalItems); if (totalItems == 0) removeRow(); currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving users", exception); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); removeRow(); currentlyRetrieving = false; } }); } private void LoadPeople() { if (mPersons != null) { for (BaseItemPerson person : mPersons) { add(new BaseRowItem(person)); } } else { removeRow(); } currentlyRetrieving = false; } private void LoadChapters() { if (mChapters != null) { for (ChapterItemInfo chapter : mChapters) { add(new BaseRowItem(chapter)); } } else { removeRow(); } currentlyRetrieving = false; } private void LoadStaticItems() { if (mItems != null) { for (BaseItemDto item : mItems) { add(new BaseRowItem(item)); } itemsLoaded = mItems.size(); } else { removeRow(); } currentlyRetrieving = false; } private void LoadStaticAudioItems() { if (mItems != null) { int i = 0; for (BaseItemDto item : mItems) { add(new AudioQueueItem(i++, item)); } itemsLoaded = i; } else { removeRow(); } currentlyRetrieving = false; } private void LoadServers() { if (mServers != null) { for (ServerInfo server : mServers) { add(new BaseRowItem(server)); } itemsLoaded = mServers.length; } else { removeRow(); } currentlyRetrieving = false; } private static String[] ignoreTypes = new String[] {"books","games"}; private static List<String> ignoreTypeList = Arrays.asList(ignoreTypes); private void RetrieveViews() { final ItemRowAdapter adapter = this; UserDto user = TvApp.getApplication().getCurrentUser(); TvApp.getApplication().getConnectionManager().GetApiClient(user).GetUserViews(user.getId(), new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { int i = 0; int prevItems = adapter.size() > 0 ? adapter.size() : 0; for (BaseItemDto item : response.getItems()) { //re-map the display prefs id to our actual id item.setDisplayPreferencesId(item.getId()); if (!ignoreTypeList.contains(item.getCollectionType()) && !ignoreTypeList.contains(item.getType())) adapter.add(new BaseRowItem(i++, item, preferParentThumb, staticHeight)); } totalItems = response.getTotalRecordCount(); setItemsLoaded(itemsLoaded + i); if (prevItems > 0) { // remove previous items as we re-retrieved // this is done this way instead of clearing the adapter to avoid bugs in the framework elements removeItems(0, prevItems); } } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving items", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } private void Retrieve(SearchQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetSearchHintsAsync(query, new Response<SearchHintResult>() { @Override public void onResponse(SearchHintResult response) { if (response.getTotalRecordCount() > 0) { int i = 0; if (adapter.size() > 0) adapter.clear(); for (SearchHint item : response.getSearchHints()) { if (!ignoreTypeList.contains(item.getType())) { i++; adapter.add(new BaseRowItem(item)); } } totalItems = response.getTotalRecordCount(); setItemsLoaded(itemsLoaded + i); if (itemsLoaded > 0 && mParent != null) mParent.add(mRow); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving search results", exception); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void GetResultSizeAsync(final Response<Integer> outerResponse) { switch (queryType) { case AlbumArtists: mArtistsQuery.setLimit(1); // minimum result set because we just need total record count TvApp.getApplication().getApiClient().GetAlbumArtistsAsync(mArtistsQuery, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { mArtistsQuery.setLimit(chunkSize > 0 ? chunkSize : null); outerResponse.onResponse(response.getTotalRecordCount()); } @Override public void onError(Exception exception) { mArtistsQuery.setLimit(chunkSize > 0 ? chunkSize : null); outerResponse.onError(exception); } }); break; case Items: StdItemQuery sizeQuery = new StdItemQuery(new ItemFields[]{}); sizeQuery.setIncludeItemTypes(mQuery.getIncludeItemTypes()); sizeQuery.setNameStartsWithOrGreater(mQuery.getNameStartsWithOrGreater()); sizeQuery.setNameLessThan(mQuery.getNameLessThan()); sizeQuery.setFilters(getFilters().getFilters()); sizeQuery.setRecursive(mQuery.getRecursive()); sizeQuery.setParentId(mQuery.getParentId()); sizeQuery.setLimit(1); // minimum result set because we just need total record count TvApp.getApplication().getApiClient().GetItemsAsync(sizeQuery, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { outerResponse.onResponse(response.getTotalRecordCount()); } @Override public void onError(Exception exception) { outerResponse.onError(exception); } }); break; default: outerResponse.onError(new Exception("Can only be used with standard query")); break; } } public void Retrieve(ItemQuery query) { TvApp.getApplication().getApiClient().GetItemsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { setTotalItems(response.getTotalRecordCount()); int i = getItemsLoaded(); int prevItems = i == 0 && size() > 0 ? size() : 0; for (BaseItemDto item : response.getItems()) { add(new BaseRowItem(i++, item, getPreferParentThumb(), isStaticHeight())); //TvApp.getApplication().getLogger().Debug("Item Type: "+item.getType()); } setItemsLoaded(i); if (i == 0) { removeRow(); } else if (prevItems > 0) { // remove previous items as we re-retrieved // this is done this way instead of clearing the adapter to avoid bugs in the framework elements removeItems(0, prevItems); } } else { // no results - don't show us if (getItemsLoaded() == 0) removeRow(); } setCurrentlyRetrieving(false); notifyRetrieveFinished(); } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving items", exception); if (exception instanceof HttpException) { HttpException httpException = (HttpException) exception; if (httpException.getStatusCode() != null && httpException.getStatusCode() == 401 && "ParentalControl".equals(httpException.getHeaders().get("X-Application-Error-Code"))) { Utils.showToast(TvApp.getApplication(), TvApp.getApplication().getString(R.string.msg_access_restricted)); new Handler().postDelayed(new Runnable() { @Override public void run() { System.exit(1); } }, 3000); } else { removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); } } else { removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); } setCurrentlyRetrieving(false); notifyRetrieveFinished(); } }); } public void RetrieveAudioPlaylists(final ItemQuery query) { //Add specialized playlists first clear(); add(new GridButton(EnhancedBrowseFragment.FAVSONGS, TvApp.getApplication().getString(R.string.lbl_favorites), R.drawable.genericmusic)); itemsLoaded = 1; Retrieve(query); } public void RetrieveContinueWatching(final ItemQuery query) { //Add current video queue first if there clear(); if (MediaManager.hasVideoQueueItems()) { TvApp.getApplication().getLogger().Debug("Adding video queue..."); add(new BaseRowItem(new GridButton(TvApp.VIDEO_QUEUE_OPTION_ID, TvApp.getApplication().getString(R.string.lbl_current_queue), R.drawable.playlist))); itemsLoaded = 1; } Retrieve(query); } public void Retrieve(ArtistsQuery query) { TvApp.getApplication().getApiClient().GetAlbumArtistsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { setTotalItems(response.getTotalRecordCount()); int i = getItemsLoaded(); int prevItems = i == 0 && size() > 0 ? size() : 0; for (BaseItemDto item : response.getItems()) { add(new BaseRowItem(i++, item, getPreferParentThumb(), isStaticHeight())); //TvApp.getApplication().getLogger().Debug("Item Type: "+item.getType()); } setItemsLoaded(i); if (i == 0) { removeRow(); } else if (prevItems > 0) { // remove previous items as we re-retrieved // this is done this way instead of clearing the adapter to avoid bugs in the framework elements removeItems(0, prevItems); } } else { // no results - don't show us setTotalItems(0); removeRow(); } setCurrentlyRetrieving(false); notifyRetrieveFinished(); } }); } private void RetrievePremieres(final ItemQuery query) { final ItemRowAdapter adapter = this; //First we need current Next Up to filter our list with NextUpQuery nextUp = new NextUpQuery(); nextUp.setUserId(query.getUserId()); nextUp.setParentId(query.getParentId()); nextUp.setLimit(75); TvApp.getApplication().getApiClient().GetNextUpEpisodesAsync(nextUp, new Response<ItemsResult>() { @Override public void onResponse(final ItemsResult nextUpResponse) { TvApp.getApplication().getApiClient().GetItemsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (adapter.size() > 0) adapter.clear(); if (response.getTotalRecordCount() > 0) { int i = 0; Calendar compare = Calendar.getInstance(); compare.add(Calendar.MONTH, -2); BaseItemDto[] nextUpItems = nextUpResponse.getItems(); for (BaseItemDto item : response.getItems()) { if (item.getIndexNumber() != null && item.getIndexNumber() == 1 && (item.getDateCreated() == null || item.getDateCreated().after(compare.getTime())) && (item.getUserData() == null || item.getUserData().getLikes() == null || item.getUserData().getLikes()) ) { // new unwatched episode 1 not disliked - check to be sure prev episode not already in next up BaseItemDto nextUpItem = null; for (int n = 0; n < nextUpItems.length; n++) { if (nextUpItems[n].getSeriesId().equals(item.getSeriesId())) { nextUpItem = nextUpItems[n]; break; } } if (nextUpItem == null || nextUpItem.getId().equals(item.getId())) { //Now - let's be sure there isn't already a premiere for this series BaseRowItem existing = null; int existingPos = -1; for (int n = 0; n < adapter.size(); n++) { if (((BaseRowItem)adapter.get(n)).getBaseItem().getSeriesId().equals(item.getSeriesId())) { existing = (BaseRowItem)adapter.get(n); existingPos = n; break; } } if (existing == null) { TvApp.getApplication().getLogger().Debug("Adding new episode 1 to premieres " + item.getSeriesName()); adapter.add(new BaseRowItem(i++, item, preferParentThumb, false)); } else if (existing.getBaseItem().getParentIndexNumber() > item.getParentIndexNumber()) { //Replace the newer item with the earlier season TvApp.getApplication().getLogger().Debug("Replacing newer episode 1 with an older season for " + item.getSeriesName()); adapter.replace(existingPos, new BaseRowItem(i++, item, preferParentThumb, false)); } // otherwise, just ignore this newer season premiere since we have the older one already } else { TvApp.getApplication().getLogger().Info("Didn't add %s to premieres because different episode is in next up.", item.getSeriesName()); } } } setItemsLoaded(itemsLoaded + i); } if (adapter.size() == 0) removeRow(); currentlyRetrieving = false; } }); } }); } public void Retrieve(final NextUpQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetNextUpEpisodesAsync(query, new Response<ItemsResult>() { @Override public void onResponse(final ItemsResult response) { if (response.getTotalRecordCount() > 0) { if (adapter.size() > 0) adapter.clear(); int i = 0; for (BaseItemDto item : response.getItems()) { adapter.add(new BaseRowItem(i++, item, preferParentThumb, false)); } totalItems = response.getTotalRecordCount(); setItemsLoaded(itemsLoaded + i); if (i == 0) { removeRow(); currentlyRetrieving = false; } else { //If this was for a single series, get the rest of the episodes in the season if (query.getSeriesId() != null) { BaseItemDto first = adapter.size() == 1 ? ((BaseRowItem)adapter.get(0)).getBaseItem() : null; if (first != null && first.getIndexNumber() != null && first.getSeasonId() != null) { StdItemQuery rest = new StdItemQuery(); rest.setUserId(query.getUserId()); rest.setParentId(first.getSeasonId()); rest.setStartIndex(first.getIndexNumber()); TvApp.getApplication().getApiClient().GetItemsAsync(rest, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult innerResponse) { if (response.getItems() != null) { int n = response.getItems().length; for (BaseItemDto item : innerResponse.getItems()) { adapter.add(new BaseRowItem(n++, item, preferParentThumb, false)); } totalItems += innerResponse.getTotalRecordCount(); setItemsLoaded(itemsLoaded + n); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Unable to retrieve subsequent episodes in next up", exception); currentlyRetrieving = false; } }); } } } } else { // no results - don't show us removeRow(); currentlyRetrieving = false; } } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving next up items", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void Retrieve(final LiveTvChannelQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetLiveTvChannelsAsync(query, new Response<ChannelInfoDtoResult>() { @Override public void onResponse(ChannelInfoDtoResult response) { if (response.getTotalRecordCount() > 0) { int i = itemsLoaded; if (i == 0 && adapter.size() > 0) adapter.clear(); for (ChannelInfoDto item : response.getItems()) { adapter.add(new BaseRowItem(i, item)); i++; } totalItems = response.getTotalRecordCount(); setItemsLoaded(i); if (i == 0) removeRow(); } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving live tv channels", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void Retrieve(final RecommendedProgramQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetRecommendedLiveTvProgramsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { TvManager.updateProgramsNeedsLoadTime(); if (response.getTotalRecordCount() > 0) { int i = 0; int prevItems = adapter.size() > 0 ? adapter.size() : 0; if (query.getIsAiring()) { // show guide option as first item adapter.add(new BaseRowItem(new GridButton(TvApp.LIVE_TV_GUIDE_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_live_tv_guide), R.drawable.guide))); i++; } for (BaseItemDto item : response.getItems()) { adapter.add(new BaseRowItem(item, staticHeight)); i++; } totalItems = response.getTotalRecordCount(); setItemsLoaded(i); if (i == 0) { removeRow(); } else if (prevItems > 0) { // remove previous items as we re-retrieved // this is done this way instead of clearing the adapter to avoid bugs in the framework elements removeItems(0, prevItems); } } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving live tv programs", exception); removeRow(); //TODO suppress this message for now - put it back when server returns empty set for no live tv //Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void Retrieve(final RecordingGroupQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetLiveTvRecordingGroupsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { int i = 0; int prevItems = adapter.size() > 0 ? adapter.size() : 0; for (BaseItemDto item : response.getItems()) { item.setType("RecordingGroup"); // the API does not fill this in item.setIsFolder(true); // nor this adapter.add(new BaseRowItem(item)); i++; } totalItems = response.getTotalRecordCount(); setItemsLoaded(itemsLoaded + i); if (i == 0) { removeRow(); } else if (prevItems > 0) { // remove previous items as we re-retrieved // this is done this way instead of clearing the adapter to avoid bugs in the framework elements removeItems(0, prevItems); } } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving live tv recording groups", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void Retrieve(final RecordingQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetLiveTvRecordingsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { int i = 0; int prevItems = adapter.size() > 0 ? adapter.size() : 0; if (query.getGroupId() == null) { // and recordings as first item if showing all adapter.add(new BaseRowItem(new GridButton(TvApp.LIVE_TV_RECORDINGS_OPTION_ID, TvApp.getApplication().getResources().getString(R.string.lbl_recorded_tv), R.drawable.recgroup))); i++; } for (BaseItemDto item : response.getItems()) { adapter.add(new BaseRowItem(item)); i++; } totalItems = response.getTotalRecordCount(); setItemsLoaded(itemsLoaded + i); if (i == 0) { removeRow(); } else if (prevItems > 0) { // remove previous items as we re-retrieved // this is done this way instead of clearing the adapter to avoid bugs in the framework elements removeItems(0, prevItems); } } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving live tv recordings", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void Retrieve(final SpecialsQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetSpecialFeaturesAsync(TvApp.getApplication().getCurrentUser().getId(), query.getItemId(), new Response<BaseItemDto[]>() { @Override public void onResponse(BaseItemDto[] response) { if (response.length > 0) { int i = 0; if (adapter.size() > 0) adapter.clear(); for (BaseItemDto item : response) { adapter.add(new BaseRowItem(i++, item, preferParentThumb, false)); } totalItems = response.length; setItemsLoaded(itemsLoaded + i); if (i == 0) removeRow(); } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving special features", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void Retrieve(final TrailersQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetLocalTrailersAsync(TvApp.getApplication().getCurrentUser().getId(), query.getItemId(), new Response<BaseItemDto[]>() { @Override public void onResponse(BaseItemDto[] response) { if (response.length > 0) { int i = 0; if (adapter.size() > 0) adapter.clear(); for (BaseItemDto item : response) { item.setName(TvApp.getApplication().getString(R.string.lbl_trailer) + (i + 1)); adapter.add(new BaseRowItem(i++, item, preferParentThumb, false, BaseRowItem.SelectAction.Play)); } totalItems = response.length; setItemsLoaded(itemsLoaded + i); if (i == 0) removeRow(); } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving special features", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void RetrieveSimilarSeries(final SimilarItemsQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetSimilarItems(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { int i = 0; if (adapter.size() > 0) adapter.clear(); for (BaseItemDto item : response.getItems()) { adapter.add(new BaseRowItem(i++, item)); } totalItems = response.getTotalRecordCount(); setItemsLoaded(itemsLoaded + i); if (i == 0) removeRow(); } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving similar series items", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void RetrieveSimilarMovies(final SimilarItemsQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetSimilarItems(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { int i = 0; if (adapter.size() > 0) adapter.clear(); for (BaseItemDto item : response.getItems()) { adapter.add(new BaseRowItem(i++, item)); } totalItems = response.getTotalRecordCount(); setItemsLoaded(itemsLoaded + i); if (i == 0) removeRow(); } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving similar series items", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void Retrieve(final UpcomingEpisodesQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetUpcomingEpisodesAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { int i = 0; if (adapter.size() > 0) adapter.clear(); for (BaseItemDto item : response.getItems()) { if (query.getParentId() == null || item.getSeriesId() == null || item.getSeriesId().equals(query.getParentId())) adapter.add(new BaseRowItem(i++, item)); } totalItems = response.getTotalRecordCount(); setItemsLoaded(itemsLoaded + i); if (i == 0) removeRow(); } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving upcoming items", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void Retrieve(final PersonsQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetPeopleAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { int i = itemsLoaded; if (i == 0 && adapter.size() > 0) adapter.clear(); for (BaseItemDto item : response.getItems()) { adapter.add(new BaseRowItem(i++, item)); } totalItems = response.getTotalRecordCount(); setItemsLoaded(i); if (i == 0) removeRow(); } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving people", exception); removeRow(); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void Retrieve(SeasonQuery query) { final ItemRowAdapter adapter = this; TvApp.getApplication().getApiClient().GetSeasonsAsync(query, new Response<ItemsResult>() { @Override public void onResponse(ItemsResult response) { if (response.getTotalRecordCount() > 0) { int i = 0; int prevItems = adapter.size() > 0 ? adapter.size() : 0; for (BaseItemDto item : response.getItems()) { adapter.add(new BaseRowItem(i++, item)); } totalItems = response.getTotalRecordCount(); setItemsLoaded(itemsLoaded + i); if (prevItems > 0) { // remove previous items as we re-retrieved // this is done this way instead of clearing the adapter to avoid bugs in the framework elements removeItems(0, prevItems); } } else { // no results - don't show us removeRow(); } currentlyRetrieving = false; } @Override public void onError(Exception exception) { TvApp.getApplication().getLogger().ErrorException("Error retrieving season items", exception); Utils.showToast(TvApp.getApplication(), exception.getLocalizedMessage()); currentlyRetrieving = false; } }); } public void notifyRetrieveFinished() { if (mRetrieveFinishedListener != null) { mRetrieveFinishedListener.onResponse(); } } public void setRetrieveFinishedListener(EmptyResponse response) { this.mRetrieveFinishedListener = response; } public void notifyRetrieveStarted() { if (mRetrieveStartedListener != null) { mRetrieveStartedListener.onResponse(); } } public void setRetrieveStartedListener(EmptyResponse response) { this.mRetrieveStartedListener = response; } }