1/*
2 * Copyright (C) 2015 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
17package com.android.tv.menu;
18
19import android.content.Context;
20import android.content.Intent;
21import android.media.tv.TvInputInfo;
22import android.view.View;
23
24import com.android.tv.ApplicationSingletons;
25import com.android.tv.R;
26import com.android.tv.TvApplication;
27import com.android.tv.analytics.Tracker;
28import com.android.tv.common.feature.CommonFeatures;
29import com.android.tv.data.Channel;
30import com.android.tv.dvr.DvrDataManager;
31import com.android.tv.recommendation.Recommender;
32import com.android.tv.util.SetupUtils;
33import com.android.tv.util.TvInputManagerHelper;
34
35import java.util.ArrayDeque;
36import java.util.ArrayList;
37import java.util.List;
38
39/**
40 * An adapter of the Channels row.
41 */
42public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<ChannelsRowItem> {
43    // There are four special cards: guide, setup, dvr, applink.
44    private static final int SIZE_OF_VIEW_TYPE = 5;
45
46    private final Context mContext;
47    private final Tracker mTracker;
48    private final Recommender mRecommender;
49    private final DvrDataManager mDvrDataManager;
50    private final int mMaxCount;
51    private final int mMinCount;
52
53    private final View.OnClickListener mGuideOnClickListener = new View.OnClickListener() {
54        @Override
55        public void onClick(View view) {
56            mTracker.sendMenuClicked(R.string.channels_item_program_guide);
57            getMainActivity().getOverlayManager().showProgramGuide();
58        }
59    };
60
61    private final View.OnClickListener mSetupOnClickListener = new View.OnClickListener() {
62        @Override
63        public void onClick(View view) {
64            mTracker.sendMenuClicked(R.string.channels_item_setup);
65            getMainActivity().getOverlayManager().showSetupFragment();
66        }
67    };
68
69    private final View.OnClickListener mDvrOnClickListener = new View.OnClickListener() {
70        @Override
71        public void onClick(View view) {
72            mTracker.sendMenuClicked(R.string.channels_item_dvr);
73            getMainActivity().getOverlayManager().showDvrManager();
74        }
75    };
76
77    private final View.OnClickListener mAppLinkOnClickListener = new View.OnClickListener() {
78        @Override
79        public void onClick(View view) {
80            mTracker.sendMenuClicked(R.string.channels_item_app_link);
81            Intent intent = ((AppLinkCardView) view).getIntent();
82            if (intent != null) {
83                getMainActivity().startActivitySafe(intent);
84            }
85        }
86    };
87
88    private final View.OnClickListener mChannelOnClickListener = new View.OnClickListener() {
89        @Override
90        public void onClick(View view) {
91            // Always send the label "Channels" because the channel ID or name or number might be
92            // sensitive.
93            mTracker.sendMenuClicked(R.string.menu_title_channels);
94            getMainActivity().tuneToChannel((Channel) view.getTag());
95            getMainActivity().hideOverlaysForTune();
96        }
97    };
98
99    public ChannelsRowAdapter(Context context, Recommender recommender,
100            int minCount, int maxCount) {
101        super(context);
102        mContext = context;
103        ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
104        mTracker = appSingletons.getTracker();
105        if (CommonFeatures.DVR.isEnabled(context)) {
106            mDvrDataManager = appSingletons.getDvrDataManager();
107        } else {
108            mDvrDataManager = null;
109        }
110        mRecommender = recommender;
111        mMinCount = minCount;
112        mMaxCount = maxCount;
113        setHasStableIds(true);
114    }
115
116    @Override
117    public int getItemViewType(int position) {
118        return getItemList().get(position).getLayoutId();
119    }
120
121    @Override
122    protected int getLayoutResId(int viewType) {
123        return viewType;
124    }
125
126    @Override
127    public long getItemId(int position) {
128        return getItemList().get(position).getItemId();
129    }
130
131    @Override
132    public void onBindViewHolder(MyViewHolder viewHolder, int position) {
133        int viewType = getItemViewType(position);
134        if (viewType == R.layout.menu_card_guide) {
135            viewHolder.itemView.setOnClickListener(mGuideOnClickListener);
136        } else if (viewType == R.layout.menu_card_setup) {
137            viewHolder.itemView.setOnClickListener(mSetupOnClickListener);
138        } else if (viewType == R.layout.menu_card_app_link) {
139            viewHolder.itemView.setOnClickListener(mAppLinkOnClickListener);
140        } else if (viewType == R.layout.menu_card_dvr) {
141            viewHolder.itemView.setOnClickListener(mDvrOnClickListener);
142            SimpleCardView view = (SimpleCardView) viewHolder.itemView;
143            view.setText(R.string.channels_item_dvr);
144        } else {
145            viewHolder.itemView.setTag(getItemList().get(position).getChannel());
146            viewHolder.itemView.setOnClickListener(mChannelOnClickListener);
147        }
148        super.onBindViewHolder(viewHolder, position);
149    }
150
151    @Override
152    public void update() {
153        if (getItemCount() == 0) {
154            createItems();
155        } else {
156            updateItems();
157        }
158    }
159
160    private void createItems() {
161        List<ChannelsRowItem> items = new ArrayList<>();
162        items.add(ChannelsRowItem.GUIDE_ITEM);
163        if (needToShowSetupItem()) {
164            items.add(ChannelsRowItem.SETUP_ITEM);
165        }
166        if (needToShowDvrItem()) {
167            items.add(ChannelsRowItem.DVR_ITEM);
168        }
169        if (needToShowAppLinkItem()) {
170            ChannelsRowItem.APP_LINK_ITEM.setChannel(
171                    new Channel.Builder(getMainActivity().getCurrentChannel()).build());
172            items.add(ChannelsRowItem.APP_LINK_ITEM);
173        }
174        for (Channel channel : getRecentChannels()) {
175            items.add(new ChannelsRowItem(channel, R.layout.menu_card_channel));
176        }
177        setItemList(items);
178    }
179
180    private void updateItems() {
181        List<ChannelsRowItem> items = getItemList();
182        // The current index of the item list to iterate. It starts from 1 because the first item
183        // (GUIDE) is always visible and not updated.
184        int currentIndex = 1;
185        if (updateItem(needToShowSetupItem(), ChannelsRowItem.SETUP_ITEM, currentIndex)) {
186            ++currentIndex;
187        }
188        if (updateItem(needToShowDvrItem(), ChannelsRowItem.DVR_ITEM, currentIndex)) {
189            ++currentIndex;
190        }
191        if (updateItem(needToShowAppLinkItem(), ChannelsRowItem.APP_LINK_ITEM, currentIndex)) {
192            if (!getMainActivity().getCurrentChannel()
193                    .hasSameReadOnlyInfo(ChannelsRowItem.APP_LINK_ITEM.getChannel())) {
194                ChannelsRowItem.APP_LINK_ITEM.setChannel(
195                        new Channel.Builder(getMainActivity().getCurrentChannel()).build());
196                notifyItemChanged(currentIndex);
197            }
198            ++currentIndex;
199        }
200        int numOldChannels = items.size() - currentIndex;
201        if (numOldChannels > 0) {
202            while (items.size() > currentIndex) {
203                items.remove(items.size() - 1);
204            }
205            notifyItemRangeRemoved(currentIndex, numOldChannels);
206        }
207        for (Channel channel : getRecentChannels()) {
208            items.add(new ChannelsRowItem(channel, R.layout.menu_card_channel));
209        }
210        int numNewChannels = items.size() - currentIndex;
211        if (numNewChannels > 0) {
212            notifyItemRangeInserted(currentIndex, numNewChannels);
213        }
214    }
215
216    /**
217     * Returns {@code true} if the item should be shown.
218     */
219    private boolean updateItem(boolean needToShow, ChannelsRowItem item, int index) {
220        List<ChannelsRowItem> items = getItemList();
221        boolean isItemInList = index < items.size() && item.equals(items.get(index));
222        if (needToShow && !isItemInList) {
223            items.add(index, item);
224            notifyItemInserted(index);
225        } else if (!needToShow && isItemInList) {
226            items.remove(index);
227            notifyItemRemoved(index);
228        }
229        return needToShow;
230    }
231
232    private boolean needToShowSetupItem() {
233        TvInputManagerHelper inputManager =
234                TvApplication.getSingletons(mContext).getTvInputManagerHelper();
235        return SetupUtils.getInstance(mContext).hasNewInput(inputManager);
236    }
237
238    private boolean needToShowDvrItem() {
239        TvInputManagerHelper inputManager =
240                TvApplication.getSingletons(mContext).getTvInputManagerHelper();
241        if (mDvrDataManager != null) {
242            for (TvInputInfo info : inputManager.getTvInputInfos(true, true)) {
243                if (info.canRecord()) {
244                    return true;
245                }
246            }
247        }
248        return false;
249    }
250
251    private boolean needToShowAppLinkItem() {
252        TvInputManagerHelper inputManager =
253                TvApplication.getSingletons(mContext).getTvInputManagerHelper();
254        Channel currentChannel = getMainActivity().getCurrentChannel();
255        return currentChannel != null
256                && currentChannel.getAppLinkType(mContext) != Channel.APP_LINK_TYPE_NONE
257                // Sometimes applicationInfo can be null. b/28932537
258                && inputManager.getTvInputAppInfo(currentChannel.getInputId()) != null;
259    }
260
261    private List<Channel> getRecentChannels() {
262        List<Channel> channelList = new ArrayList<>();
263        long currentChannelId = getMainActivity().getCurrentChannelId();
264        ArrayDeque<Long> recentChannels = getMainActivity().getRecentChannels();
265        // Add the last watched channel as the first one.
266        for (long channelId : recentChannels) {
267            if (addChannelToList(
268                    channelList, mRecommender.getChannel(channelId), currentChannelId)) {
269                break;
270            }
271        }
272        // Add the recommended channels.
273        for (Channel channel : mRecommender.recommendChannels(mMaxCount)) {
274            if (channelList.size() >= mMaxCount) {
275                break;
276            }
277            addChannelToList(channelList, channel, currentChannelId);
278        }
279        // If the number of recommended channels is not enough, add more from the recent channel
280        // list.
281        for (long channelId : recentChannels) {
282            if (channelList.size() >= mMinCount) {
283                break;
284            }
285            addChannelToList(channelList, mRecommender.getChannel(channelId), currentChannelId);
286        }
287        return channelList;
288    }
289
290    private static boolean addChannelToList(
291            List<Channel> channelList, Channel channel, long currentChannelId) {
292        if (channel == null || channel.getId() == currentChannelId
293                || channelList.contains(channel) || !channel.isBrowsable()) {
294            return false;
295        }
296        channelList.add(channel);
297        return true;
298    }
299}
300