154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez/*
254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * Copyright 2018 The Android Open Source Project
354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez *
454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * Licensed under the Apache License, Version 2.0 (the "License");
554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * you may not use this file except in compliance with the License.
654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * You may obtain a copy of the License at
754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez *
854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez *      http://www.apache.org/licenses/LICENSE-2.0
954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez *
1054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * Unless required by applicable law or agreed to in writing, software
1154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * distributed under the License is distributed on an "AS IS" BASIS,
1254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * See the License for the specific language governing permissions and
1454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * limitations under the License.
1554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez */
1654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
1754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezpackage com.android.car.media.browse;
1854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
1954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.content.Context;
2054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.media.browse.MediaBrowser;
2154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.os.Bundle;
2254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.support.annotation.NonNull;
2354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.support.annotation.Nullable;
2454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.support.v7.util.DiffUtil;
2554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.support.v7.widget.GridLayoutManager;
2654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.support.v7.widget.RecyclerView;
2754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.util.Log;
2854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.view.LayoutInflater;
2954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.view.View;
3054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport android.view.ViewGroup;
3154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
3254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport com.android.car.media.common.MediaItemMetadata;
33fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perezimport com.android.car.media.common.MediaSource;
3454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
3554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport java.util.ArrayList;
3654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport java.util.Collection;
3754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport java.util.LinkedHashMap;
3854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport java.util.List;
3954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport java.util.Objects;
4054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport java.util.function.Consumer;
4154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perezimport java.util.stream.Collectors;
4254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
43a51d23d9109f44187200495fdb4523d47255b14aRoberto Perezimport androidx.car.widget.PagedListView;
44a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez
4554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez/**
4654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * A {@link RecyclerView.Adapter} that can be used to display a single level of a
4754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * {@link android.service.media.MediaBrowserService} media tree into a
4854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * {@link androidx.car.widget.PagedListView} or any other {@link RecyclerView}.
4954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez *
5054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * <p>This adapter assumes that the attached {@link RecyclerView} uses a {@link GridLayoutManager},
5154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * as it can use both grid and list elements to produce the desired representation.
5254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez *
5354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * <p> The actual strategy to group and expand media items has to be supplied by providing an
5454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * instance of {@link ContentForwardStrategy}.
5554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez *
5654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * <p> The adapter will only start updating once {@link #start()} is invoked. At this point, the
5754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * provided {@link MediaBrowser} must be already in connected state.
5854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez *
5954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * <p>Resources and asynchronous data loading must be released by callign {@link #stop()}.
6054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez *
6154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * <p>No views will be actually updated until {@link #update()} is invoked (normally as a result of
6254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * the {@link Observer#onDirty()} event. This way, the consumer of this adapter has the opportunity
6354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * to decide whether updates should be displayd immediately, or if they should be delayed to
6454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * prevent flickering.
6554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez *
6654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez * <p>Consumers of this adapter should use {@link #registerObserver(Observer)} to receive updates.
6754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez */
68a51d23d9109f44187200495fdb4523d47255b14aRoberto Perezpublic class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implements
69a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        PagedListView.DividerVisibilityManager {
70450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez    private static final String TAG = "BrowseAdapter";
7154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    @NonNull
7254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private final Context mContext;
73fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez    private final MediaSource mMediaSource;
7454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private final ContentForwardStrategy mCFBStrategy;
75d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez    private MediaItemMetadata mParentMediaItem;
7654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private LinkedHashMap<String, MediaItemState> mItemStates = new LinkedHashMap<>();
7754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private List<BrowseViewData> mViewData = new ArrayList<>();
7854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private String mParentMediaItemId;
7954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private List<Observer> mObservers = new ArrayList<>();
8054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private List<MediaItemMetadata> mQueue;
8154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private CharSequence mQueueTitle;
8254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private int mMaxSpanSize = 1;
83450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez    private State mState = State.IDLE;
84450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez
85450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez    /**
86450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez     * Possible states of the adapter
87450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez     */
88450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez    public enum State {
89450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        /** Loading of this item hasn't started yet */
90450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        IDLE,
91450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        /** There is pending information before this item can be displayed */
92450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        LOADING,
93450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        /** It was not possible to load metadata for this item */
94450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        ERROR,
95450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        /** Metadata for this items has been correctly loaded */
96450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        LOADED
97450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez    }
9854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
9954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
10054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * An {@link BrowseAdapter} observer.
10154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
102d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez    public static abstract class Observer {
10354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /**
10454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         * Callback invoked anytime there is more information to be displayed, or if there is a
10554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         * change in the overall state of the adapter.
10654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         */
107d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez        protected void onDirty() {};
10854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
10954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /**
11054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         * Callback invoked when a user clicks on a playable item.
11154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         */
112d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez        protected void onPlayableItemClicked(MediaItemMetadata item) {};
11354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
11454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /**
11554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         * Callback invoked when a user clicks on a browsable item.
11654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         */
117d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez        protected void onBrowseableItemClicked(MediaItemMetadata item) {};
11854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
11954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /**
12054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         * Callback invoked when a user clicks on a the "more items" button on a section.
12154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         */
122d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez        protected void onMoreButtonClicked(MediaItemMetadata item) {};
12354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
12454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /**
12554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         * Callback invoked when the user clicks on the title of the queue.
12654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         */
127d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez        protected void onQueueTitleClicked() {};
12854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
12954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /**
13054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         * Callback invoked when the user clicks on a queue item.
13154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         */
132d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez        protected void onQueueItemClicked(MediaItemMetadata item) {};
13354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
13454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
135fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez    private MediaSource.ItemsSubscription mSubscriptionCallback =
136fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez            (mediaSource, parentId, items) -> {
137fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                if (items != null) {
138fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                    onItemsLoaded(parentId, items);
139fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                } else {
140fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                    onLoadingError(parentId);
141fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                }
142fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez            };
14354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
14454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
14554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
14654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * Represents the loading state of children of a single {@link MediaItemMetadata} in the
14754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * {@link BrowseAdapter}
14854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
14954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private class MediaItemState {
15054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /**
15154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         * {@link com.android.car.media.common.MediaItemMetadata} whose children are being loaded
15254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez         */
15354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        final MediaItemMetadata mItem;
15454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /** Current loading state for this item */
155450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        State mState = State.LOADING;
15654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /** Playable children of the given item */
15754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        List<MediaItemMetadata> mPlayableChildren = new ArrayList<>();
15854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /** Browsable children of the given item */
15954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        List<MediaItemMetadata> mBrowsableChildren = new ArrayList<>();
16054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        /** Whether we are subscribed to updates for this item or not */
16154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        boolean mIsSubscribed;
16254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
163fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez        MediaItemState(MediaItemMetadata item) {
164fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez            mItem = item;
16554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
16654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
167fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez        void setChildren(List<MediaItemMetadata> children) {
16854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            mPlayableChildren.clear();
16954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            mBrowsableChildren.clear();
170fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez            for (MediaItemMetadata child : children) {
17154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                if (child.isBrowsable()) {
17254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    // Browsable items could also be playable
173fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                    mBrowsableChildren.add(child);
17454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                } else if (child.isPlayable()) {
175fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                    mPlayableChildren.add(child);
17654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                }
17754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
17854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
17954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
18054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
18154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
18254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * Creates a {@link BrowseAdapter} that displays the children of the given media tree node.
18354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     *
184fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez     * @param mediaSource the {@link MediaSource} to get data from.
18554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * @param parentItem the node to display children of, or NULL if the
18654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * @param strategy a {@link ContentForwardStrategy} that would determine which items would be
18754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     *                 expanded and how.
18854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
189fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez    public BrowseAdapter(Context context, @NonNull MediaSource mediaSource,
19054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            @Nullable MediaItemMetadata parentItem, @NonNull ContentForwardStrategy strategy) {
19154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        mContext = context;
192fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez        mMediaSource = mediaSource;
19354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        mParentMediaItem = parentItem;
19454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        mCFBStrategy = strategy;
19554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
19654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
19754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
19854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * Initiates or resumes the data loading process and subscribes to updates. The client can use
19954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * {@link #registerObserver(Observer)} to receive updates on the progress.
20054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
20154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public void start() {
202fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez        mParentMediaItemId = mParentMediaItem != null ? mParentMediaItem.getId() :
203fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                mMediaSource.getRoot();
204fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez        mMediaSource.subscribeChildren(mParentMediaItemId, mSubscriptionCallback);
20554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        for (MediaItemState itemState : mItemStates.values()) {
20654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            subscribe(itemState);
20754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
20854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
20954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
21054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
21154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * Stops the data loading and releases any subscriptions.
21254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
21354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public void stop() {
21454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        if (mParentMediaItemId == null) {
21554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            // Not started
21654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            return;
21754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
218fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez        mMediaSource.unsubscribeChildren(mParentMediaItemId, mSubscriptionCallback);
21954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        for (MediaItemState itemState : mItemStates.values()) {
22054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            unsubscribe(itemState);
22154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
22254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        mParentMediaItemId = null;
22354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
22454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
22554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
226d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez     * Replaces the media item whose children are being displayed in this adapter. The content of
227d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez     * the adapter will be replaced once the children of the new item are loaded.
228d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez     *
229d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez     * @param parentItem new media item to expand.
230d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez     */
231d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez    public void setParentMediaItemId(@Nullable MediaItemMetadata parentItem) {
232fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez        String newParentMediaItemId = parentItem != null ? parentItem.getId() :
233fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                mMediaSource.getRoot();
234d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez        if (Objects.equals(newParentMediaItemId, mParentMediaItemId)) {
235d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez            return;
236d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez        }
237a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        stop();
238d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez        mParentMediaItem = parentItem;
239d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez        mParentMediaItemId = newParentMediaItemId;
240fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez        mMediaSource.subscribeChildren(mParentMediaItemId, mSubscriptionCallback);
241d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez    }
242d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez
243d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez    /**
24454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * Sets media queue items into this adapter.
24554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
24654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public void setQueue(List<MediaItemMetadata> items, CharSequence queueTitle) {
24754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        mQueue = items;
24854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        mQueueTitle = queueTitle;
24954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        notify(Observer::onDirty);
25054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
25154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
25254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
25354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * Registers an {@link Observer}
25454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
25554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public void registerObserver(Observer observer) {
25654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        mObservers.add(observer);
25754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
25854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
25954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
26054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * Unregisters an {@link Observer}
26154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
26254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public void unregisterObserver(Observer observer) {
26354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        mObservers.remove(observer);
26454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
26554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
26654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
26754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * @return the global loading state. Consumers can use this state to determine if more
26854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * information is still pending to arrive or not. This method will report
269450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez     * {@link State#ERROR} only if the list of immediate children fails to load.
27054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
271450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez    public State getState() {
27254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        return mState;
27354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
27454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
27554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
27654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * Sets the number of columns that items can take. This method only needs to be used if the
27754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * attached {@link RecyclerView} is NOT using a {@link GridLayoutManager}. This class will
27854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * automatically determine this value on {@link #onAttachedToRecyclerView(RecyclerView)}
27954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * otherwise.
28054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
28154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public void setMaxSpanSize(int maxSpanSize) {
28254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        mMaxSpanSize = maxSpanSize;
28354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
28454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
28554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
28654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * @return a {@link GridLayoutManager.SpanSizeLookup} that can be used to obtain the span size
28754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * of each item in this adapter. This method is only needed if the {@link RecyclerView} is NOT
28854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * using a {@link GridLayoutManager}. This class will automatically use it on\
28954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * {@link #onAttachedToRecyclerView(RecyclerView)} otherwise.
29054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
29154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public GridLayoutManager.SpanSizeLookup getSpanSizeLookup() {
29254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        return new GridLayoutManager.SpanSizeLookup() {
29354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            @Override
29454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            public int getSpanSize(int position) {
29554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                BrowseItemViewType viewType = mViewData.get(position).mViewType;
29654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                return viewType.getSpanSize(mMaxSpanSize);
29754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
29854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        };
29954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
30054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
30154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
30254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * Updates the {@link RecyclerView} with newly loaded information. This normally should be
30354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * invoked as a result of a {@link Observer#onDirty()} callback.
30454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     *
30554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * This method is idempotent and can be used at any time (even delayed if needed). Additions,
30654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * removals and insertions would be notified to the {@link RecyclerView} so it can be
30754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * animated appropriately.
30854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
30954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public void update() {
31054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        List<BrowseViewData> newItems = generateViewData(mItemStates.values());
31154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        List<BrowseViewData> oldItems = mViewData;
31254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        mViewData = newItems;
31354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        DiffUtil.DiffResult result = DiffUtil.calculateDiff(createDiffUtil(oldItems, newItems));
31454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        result.dispatchUpdatesTo(this);
31554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
31654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
31754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private void subscribe(MediaItemState state) {
31854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        if (!state.mIsSubscribed && state.mItem.isBrowsable()) {
319fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez            mMediaSource.subscribeChildren(state.mItem.getId(), mSubscriptionCallback);
32054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            state.mIsSubscribed = true;
321450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        } else {
322450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez            state.mState = State.LOADED;
32354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
32454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
32554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
32654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private void unsubscribe(MediaItemState state) {
32754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        if (state.mIsSubscribed) {
328fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez            mMediaSource.unsubscribeChildren(state.mItem.getId(), mSubscriptionCallback);
32954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            state.mIsSubscribed = false;
33054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
33154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
33254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
33354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    @NonNull
33454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    @Override
33554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public BrowseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
33654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        int layoutId = BrowseItemViewType.values()[viewType].getLayoutId();
33754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        View view = LayoutInflater.from(mContext).inflate(layoutId, parent, false);
33854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        return new BrowseViewHolder(view);
33954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
34054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
34154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    @Override
34254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public void onBindViewHolder(@NonNull BrowseViewHolder holder, int position) {
34354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        BrowseViewData viewData = mViewData.get(position);
34454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        holder.bind(mContext, viewData);
34554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
34654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
34754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    @Override
34854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public int getItemCount() {
34954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        return mViewData.size();
35054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
35154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
35254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    @Override
35354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public int getItemViewType(int position) {
35454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        return mViewData.get(position).mViewType.ordinal();
35554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
35654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
357fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez    private void onItemsLoaded(String parentId, List<MediaItemMetadata> children) {
35854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        if (parentId.equals(mParentMediaItemId)) {
35954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            // Direct children from the requested media item id. Update subscription list.
36054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            LinkedHashMap<String, MediaItemState> newItemStates = new LinkedHashMap<>();
3611d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez            List<MediaItemState> itemsToSubscribe = new ArrayList<>();
362fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez            for (MediaItemMetadata item : children) {
363fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                MediaItemState itemState = mItemStates.get(item.getId());
36454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                if (itemState != null) {
36554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    // Reuse existing section.
366fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                    newItemStates.put(item.getId(), itemState);
367fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                    mItemStates.remove(item.getId());
36854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                } else {
36954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    // New section, subscribe to it.
37054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    itemState = new MediaItemState(item);
371fc30bbfdb4221ecd18adf59501d5ca82ca631310Roberto Perez                    newItemStates.put(item.getId(), itemState);
3721d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez                    itemsToSubscribe.add(itemState);
37354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                }
37454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
37554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            // Remove unused sections
37654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            for (MediaItemState itemState : mItemStates.values()) {
37754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                unsubscribe(itemState);
37854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
37954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            mItemStates = newItemStates;
3801d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez            // Subscribe items once we have updated the map (updates might happen synchronously
3811d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez            // if data is already available).
3821d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez            for (MediaItemState itemState : itemsToSubscribe) {
3831d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez                subscribe(itemState);
3841d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez            }
38554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        } else {
38654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            MediaItemState itemState = mItemStates.get(parentId);
38754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            if (itemState == null) {
38854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                Log.w(TAG, "Loaded children for a section we don't have: " + parentId);
38954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                return;
39054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
39154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            itemState.setChildren(children);
392450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez            itemState.mState = State.LOADED;
39354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
39454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        updateGlobalState();
39554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        notify(Observer::onDirty);
39654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
39754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
39854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private void notify(Consumer<Observer> notification) {
39954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        for (Observer observer : mObservers) {
40054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            notification.accept(observer);
40154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
40254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
40354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
40454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private void onLoadingError(String parentId) {
40554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        if (parentId.equals(mParentMediaItemId)) {
406450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez            mState = State.ERROR;
40754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        } else {
40854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            MediaItemState state = mItemStates.get(parentId);
40954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            if (state == null) {
41054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                Log.w(TAG, "Error loading children for a section we don't have: " + parentId);
41154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                return;
41254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
41354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            state.setChildren(new ArrayList<>());
414450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez            state.mState = State.ERROR;
415450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez            updateGlobalState();
41654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
41754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        notify(Observer::onDirty);
41854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
41954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
42054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private void updateGlobalState() {
42154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        for (MediaItemState state: mItemStates.values()) {
422450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez            if (state.mState == State.LOADING) {
423450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez                mState = State.LOADING;
42454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                return;
42554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
42654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
427450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        mState = State.LOADED;
42854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
42954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
43054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private DiffUtil.Callback createDiffUtil(List<BrowseViewData> oldList,
43154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            List<BrowseViewData> newList) {
43254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        return new DiffUtil.Callback() {
43354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            @Override
43454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            public int getOldListSize() {
43554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                return oldList.size();
43654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
43754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
43854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            @Override
43954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            public int getNewListSize() {
44054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                return newList.size();
44154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
44254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
44354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            @Override
44454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            public boolean areItemsTheSame(int oldPos, int newPos) {
44554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                BrowseViewData oldItem = oldList.get(oldPos);
44654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                BrowseViewData newItem = newList.get(newPos);
44754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
44854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                return Objects.equals(oldItem.mMediaItem, newItem.mMediaItem)
44954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                        && Objects.equals(oldItem.mText, newItem.mText);
45054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
45154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
45254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            @Override
45354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            public boolean areContentsTheSame(int oldPos, int newPos) {
45454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                BrowseViewData oldItem = oldList.get(oldPos);
45554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                BrowseViewData newItem = newList.get(newPos);
45654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
45754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                return oldItem.equals(newItem);
45854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
45954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        };
46054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
46154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
46254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    @Override
46354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
46454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
46554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            GridLayoutManager manager = (GridLayoutManager) recyclerView.getLayoutManager();
46654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            mMaxSpanSize = manager.getSpanCount();
46754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            manager.setSpanSizeLookup(getSpanSizeLookup());
46854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
46954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
47054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
47154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private class ItemsBuilder {
47254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        private List<BrowseViewData> result = new ArrayList<>();
47354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
474450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        void addItem(MediaItemMetadata item, State state,
47554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                BrowseItemViewType viewType, Consumer<Observer> notification) {
476d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez            View.OnClickListener listener = notification != null ?
477d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez                    view -> BrowseAdapter.this.notify(notification) :
478d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez                    null;
479d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez            result.add(new BrowseViewData(item, viewType, state, listener));
48054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
48154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
48254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        void addItems(List<MediaItemMetadata> items, BrowseItemViewType viewType, int maxRows) {
48354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            int spanSize = viewType.getSpanSize(mMaxSpanSize);
48454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            int maxChildren = maxRows * (mMaxSpanSize / spanSize);
48554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            result.addAll(items.stream()
48654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    .limit(maxChildren)
48754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    .map(item -> {
48854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                        Consumer<Observer> notification = item.getQueueId() != null
48954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                                ? observer -> observer.onQueueItemClicked(item)
49054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                                : item.isBrowsable()
49154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                                        ? observer -> observer.onBrowseableItemClicked(item)
49254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                                        : observer -> observer.onPlayableItemClicked(item);
49354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                        return new BrowseViewData(item, viewType, null, view ->
49454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                                BrowseAdapter.this.notify(notification));
49554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    })
49654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    .collect(Collectors.toList()));
49754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
49854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
49954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        void addTitle(CharSequence title, Consumer<Observer> notification) {
50054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            result.add(new BrowseViewData(title, BrowseItemViewType.HEADER,
50154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    view -> BrowseAdapter.this.notify(notification)));
50254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
50354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
50454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
505450c73a79e566f1c4a8758648525b457005e9f9aRoberto Perez        void addBrowseBlock(MediaItemMetadata header, State state,
50654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                List<MediaItemMetadata> items, BrowseItemViewType viewType, int maxChildren,
50754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                boolean showHeader, boolean showMoreFooter) {
50854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            if (showHeader) {
50954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                addItem(header, state, BrowseItemViewType.HEADER, null);
51054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
51154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            addItems(items, viewType, maxChildren);
51254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            if (showMoreFooter) {
51354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                addItem(header, null, BrowseItemViewType.MORE_FOOTER,
51454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                        observer -> observer.onMoreButtonClicked(header));
51554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
51654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
51754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
51854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        List<BrowseViewData> build() {
51954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            return result;
52054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
52154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
52254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
52354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    /**
52454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * Flatten the given collection of item states into a list of {@link BrowseViewData}s. To avoid
52554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * flickering, the flatting will stop at the first "loading" section, avoiding unnecessary
52654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     * insertion animations during the initial data load.
52754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez     */
52854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    private List<BrowseViewData> generateViewData(Collection<MediaItemState> itemStates) {
52954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        ItemsBuilder itemsBuilder = new ItemsBuilder();
53054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
531a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        if (Log.isLoggable(TAG, Log.VERBOSE)) {
532a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez            Log.v(TAG, "Generating browse view from:");
533a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez            for (MediaItemState item : itemStates) {
534a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                Log.v(TAG, String.format("[%s%s] '%s' (%s)",
535a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                        item.mItem.isBrowsable() ? "B" : " ",
536a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                        item.mItem.isPlayable() ? "P" : " ",
537a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                        item.mItem.getTitle(),
538a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                        item.mItem.getId()));
539a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                List<MediaItemMetadata> items = new ArrayList<>();
540a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                items.addAll(item.mBrowsableChildren);
541a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                items.addAll(item.mPlayableChildren);
542a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                for (MediaItemMetadata child : items) {
543a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                    Log.v(TAG, String.format("   [%s%s] '%s' (%s)",
544a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                            child.isBrowsable() ? "B" : " ",
545a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                            child.isPlayable() ? "P" : " ",
546a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                            child.getTitle(),
547a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                            child.getId()));
548a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                }
549a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez            }
550a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        }
551a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez
55254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        if (mQueue != null && !mQueue.isEmpty() && mCFBStrategy.getMaxQueueRows() > 0
55354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                && mCFBStrategy.getQueueViewType() != null) {
55454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            if (mQueueTitle != null) {
55554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                itemsBuilder.addTitle(mQueueTitle, Observer::onQueueTitleClicked);
55654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
55754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            itemsBuilder.addItems(mQueue, mCFBStrategy.getQueueViewType(),
55854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    mCFBStrategy.getMaxQueueRows());
55954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
560a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez
561a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        boolean containsBrowsableItems = false;
562a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        boolean containsPlayableItems = false;
563a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        for (MediaItemState itemState : itemStates) {
564a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez            containsBrowsableItems |= itemState.mItem.isBrowsable();
565a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez            containsPlayableItems |= itemState.mItem.isPlayable();
566a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        }
567a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez
56854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        for (MediaItemState itemState : itemStates) {
56954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            MediaItemMetadata item = itemState.mItem;
570a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez            if (containsPlayableItems && containsBrowsableItems) {
571a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                // If we have a mix of browsable and playable items: show them all in a list
572a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                itemsBuilder.addItem(item, itemState.mState,
573a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                        BrowseItemViewType.PANEL_ITEM,
574a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                        item.isBrowsable()
575a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                                ? observer -> observer.onBrowseableItemClicked(item)
576a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                                : observer -> observer.onPlayableItemClicked(item));
577a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez            } else if (itemState.mItem.isBrowsable()) {
578a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                // If we only have browsable items, check whether we should expand them or not.
57954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                if (!itemState.mBrowsableChildren.isEmpty()
58054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                        && !itemState.mPlayableChildren.isEmpty()
58154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                        || !mCFBStrategy.shouldBeExpanded(item)) {
58254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    itemsBuilder.addItem(item, itemState.mState,
58354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                            mCFBStrategy.getBrowsableViewType(mParentMediaItem), null);
58454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                } else if (!itemState.mPlayableChildren.isEmpty()) {
58554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    itemsBuilder.addBrowseBlock(item,
58654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                            itemState.mState,
58754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                            itemState.mPlayableChildren,
58854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                            mCFBStrategy.getPlayableViewType(item),
58954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                            mCFBStrategy.getMaxRows(item, mCFBStrategy.getPlayableViewType(item)),
590a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                            mCFBStrategy.includeHeader(item),
591a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                            mCFBStrategy.showMoreButton(item));
59254a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                } else if (!itemState.mBrowsableChildren.isEmpty()) {
59354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                    itemsBuilder.addBrowseBlock(item,
59454a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                            itemState.mState,
59554a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                            itemState.mBrowsableChildren,
59654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                            mCFBStrategy.getBrowsableViewType(item),
59754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                            mCFBStrategy.getMaxRows(item, mCFBStrategy.getBrowsableViewType(item)),
598a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                            mCFBStrategy.includeHeader(item),
599a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                            mCFBStrategy.showMoreButton(item));
60054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                }
60154a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            } else if (item.isPlayable()) {
602a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                // If we only have playable items: show them as so.
60354a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez                itemsBuilder.addItem(item, itemState.mState,
604d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez                        mCFBStrategy.getPlayableViewType(mParentMediaItem),
605d1f0ec11284f6e6ed05d6a6fb7e2e086cc4a9837Roberto Perez                        observer -> observer.onPlayableItemClicked(item));
60654a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez            }
60754a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        }
60854a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez
60954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez        return itemsBuilder.build();
61054a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez    }
611a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez
612a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez    @Override
613a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez    public boolean shouldHideDivider(int position) {
614a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        return position >= mViewData.size() - 1
615a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                || position < 0
616a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                || mViewData.get(position).mViewType != BrowseItemViewType.PANEL_ITEM
617a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                || mViewData.get(position + 1).mViewType != BrowseItemViewType.PANEL_ITEM;
618a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez    }
61954a9df95716c26d7ae3a226ad506061fb9ed6e6cRoberto Perez}
620