1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.media.browse;
18 
19 import androidx.annotation.Nullable;
20 import androidx.recyclerview.widget.GridLayoutManager;
21 import androidx.recyclerview.widget.RecyclerView;
22 
23 import com.android.car.media.R;
24 import com.android.car.media.common.MediaItemMetadata;
25 import com.android.car.ui.recyclerview.CarUiGridLayoutStyle;
26 import com.android.car.ui.recyclerview.CarUiRecyclerView;
27 import com.android.car.ui.recyclerview.DelegatingContentLimitingAdapter;
28 
29 import java.util.List;
30 
31 /**
32  * Provides list limiting functionality to {@link BrowseAdapter}.
33  */
34 public class LimitedBrowseAdapter extends DelegatingContentLimitingAdapter<BrowseViewHolder> {
35 
36     private final CarUiRecyclerView mRecyclerView;
37     private final BrowseAdapter mBrowseAdapter;
38     private final int mMaxSpanSize;
39 
40     @Nullable private String mAnchorId;
41 
LimitedBrowseAdapter(CarUiRecyclerView recyclerView, BrowseAdapter browseAdapter, BrowseAdapter.Observer browseAdapterObserver)42     public LimitedBrowseAdapter(CarUiRecyclerView recyclerView, BrowseAdapter browseAdapter,
43             BrowseAdapter.Observer browseAdapterObserver) {
44         super(browseAdapter, R.id.browse_list_uxr_config);
45 
46         mRecyclerView = recyclerView;
47         mBrowseAdapter = browseAdapter;
48 
49         CarUiGridLayoutStyle layoutStyle = (CarUiGridLayoutStyle) mRecyclerView.getLayoutStyle();
50         mMaxSpanSize = layoutStyle.getSpanCount();
51         layoutStyle.setSpanSizeLookup(mSpanSizeLookup);
52         mRecyclerView.setLayoutStyle(layoutStyle);
53         mBrowseAdapter.registerObserver(browseAdapterObserver);
54         mBrowseAdapter.setOnListChangedListener(((previousList, currentList) -> {
55             updateUnderlyingDataChanged(currentList.size(), validateAnchor());
56         }));
57     }
58 
59     private final GridLayoutManager.SpanSizeLookup mSpanSizeLookup =
60             new GridLayoutManager.SpanSizeLookup() {
61                 @Override
62                 public int getSpanSize(int position) {
63                     if (getItemViewType(position) == getScrollingLimitedMessageViewType()) {
64                         return mMaxSpanSize;
65                     }
66 
67                     int itemIndex = positionToIndex(position);
68                     return mBrowseAdapter.getSpanSize(itemIndex, mMaxSpanSize);
69                 }
70             };
71 
72     /**
73      * @see BrowseAdapter#submitItems(MediaItemMetadata, List)
74      */
submitItems(@ullable MediaItemMetadata parentItem, @Nullable List<MediaItemMetadata> items)75     public void submitItems(@Nullable MediaItemMetadata parentItem,
76             @Nullable List<MediaItemMetadata> items) {
77         mBrowseAdapter.submitItems(parentItem, items);
78 
79         if (items == null) {
80             updateUnderlyingDataChanged(0, 0);
81             return;
82         }
83         // We can't take any action with the new items as they must first go through the
84         // AsyncListDiffer of ListAdapter. This is handled in the OnListChangedListener.
85     }
86 
validateAnchor()87     private int validateAnchor() {
88         if (mAnchorId == null) {
89             return 0;
90         }
91 
92         List<BrowseViewData> items = mBrowseAdapter.getCurrentList();
93         for (int i = 0; i < items.size(); i++) {
94             MediaItemMetadata mediaItem = items.get(i).mMediaItem;
95             if (mediaItem != null && mAnchorId.equals(mediaItem.getId())) {
96                 return i;
97             }
98         }
99 
100         // The anchor isn't present in the new list, reset it.
101         mAnchorId = null;
102         return 0;
103     }
104 
105 
106     @Override
computeAnchorIndexWhenRestricting()107     public int computeAnchorIndexWhenRestricting() {
108         List<BrowseViewData> items = mBrowseAdapter.getCurrentList();
109         if (items.size() <= 0) {
110             mAnchorId = null;
111             return 0;
112         }
113 
114         int anchorIndex = (getFirstVisibleItemPosition() + getLastVisibleItemPosition()) / 2;
115         if (0 <= anchorIndex && anchorIndex < items.size()) {
116             MediaItemMetadata mediaItem = items.get(anchorIndex).mMediaItem;
117             mAnchorId = mediaItem != null ? mediaItem.getId() : null;
118             return anchorIndex;
119         } else {
120             mAnchorId = null;
121             return 0;
122         }
123     }
124 
getFirstVisibleItemPosition()125     private int getFirstVisibleItemPosition() {
126         int firstItem = mRecyclerView.findFirstCompletelyVisibleItemPosition();
127         if (firstItem == RecyclerView.NO_POSITION) {
128             firstItem = mRecyclerView.findFirstVisibleItemPosition();
129         }
130         return firstItem;
131     }
132 
getLastVisibleItemPosition()133     private int getLastVisibleItemPosition() {
134         int lastItem = mRecyclerView.findLastCompletelyVisibleItemPosition();
135         if (lastItem == RecyclerView.NO_POSITION) {
136             lastItem = mRecyclerView.findLastVisibleItemPosition();
137         }
138         return lastItem;
139     }
140 }
141