1/*
2 * Copyright (C) 2017 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.example.partnersupportsampletvinput;
18
19import android.app.FragmentManager;
20import android.content.ContentResolver;
21import android.database.Cursor;
22import android.os.AsyncTask;
23import android.os.Bundle;
24import android.os.SystemClock;
25import android.support.annotation.NonNull;
26import android.support.media.tv.Channel;
27import android.support.media.tv.TvContractCompat;
28import android.support.v17.leanback.app.GuidedStepFragment;
29import android.support.v17.leanback.widget.GuidanceStylist;
30import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
31import android.support.v17.leanback.widget.GuidedAction;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.ViewGroup;
35import java.util.ArrayList;
36import java.util.HashMap;
37import java.util.Iterator;
38import java.util.List;
39
40/** Channel Scan Fragment shows detected channels */
41public class ChannelScanFragment extends GuidedStepFragment {
42    public static final long ACTION_ID_SCAN_RESULTS = 1;
43    public static final String KEY_POSTCODE = "postcode";
44    public static final String KEY_CHANNEL_NUMBERS = "channel numbers";
45    private GuidedAction mNextAction;
46    private List<Channel> mChannels = new ArrayList<>();
47    private Bundle mSavedInstanceState;
48
49    private AsyncTask<Void, Void, List<Channel>> mChannelScanTask;
50
51    @Override
52    public void onCreate(Bundle savedInstanceState) {
53        super.onCreate(savedInstanceState);
54        mSavedInstanceState = savedInstanceState;
55    }
56
57    @Override
58    public View onCreateView(
59            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
60        View v = super.onCreateView(inflater, container, savedInstanceState);
61        mChannelScanTask =
62                new AsyncTask<Void, Void, List<Channel>>() {
63                    @Override
64                    protected List<Channel> doInBackground(Void... voids) {
65                        return scanChannel();
66                    }
67
68                    @Override
69                    protected void onPostExecute(List<Channel> result) {
70                        mChannels = result;
71                        mNextAction.setFocusable(true);
72                        mNextAction.setEnabled(true);
73                        int position = findButtonActionPositionById(GuidedAction.ACTION_ID_NEXT);
74                        if (position >= 0) {
75                            notifyButtonActionChanged(position);
76                            View buttonView = getButtonActionItemView(position);
77                            buttonView.setFocusable(true);
78                            buttonView.requestFocus();
79                        }
80                    }
81                };
82        mChannelScanTask.execute();
83        return v;
84    }
85
86    @Override
87    public void onDestroyView() {
88        super.onDestroyView();
89        mNextAction = null;
90        if (mChannelScanTask != null) {
91            mChannelScanTask.cancel(true);
92            mChannelScanTask = null;
93        }
94    }
95
96    @Override
97    public void onResume() {
98        super.onResume();
99        // reset actions
100        List<GuidedAction> actions = new ArrayList<>();
101        onCreateActions(actions, mSavedInstanceState);
102        setActions(actions);
103        actions = new ArrayList<>();
104        onCreateButtonActions(actions, mSavedInstanceState);
105        setButtonActions(actions);
106    }
107
108    @NonNull
109    @Override
110    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
111        return new Guidance("Channel Scan", "Fake scanning for channels", null, null);
112    }
113
114    @Override
115    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
116        actions.add(
117                new GuidedAction.Builder(getActivity())
118                        .id(ACTION_ID_SCAN_RESULTS)
119                        .title("Channels Found:")
120                        .description("")
121                        .multilineDescription(true)
122                        .focusable(false)
123                        .infoOnly(true)
124                        .build());
125    }
126
127    @Override
128    public void onCreateButtonActions(
129            @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
130        mNextAction =
131                new GuidedAction.Builder(getActivity())
132                        .id(GuidedAction.ACTION_ID_NEXT)
133                        .title("NEXT")
134                        .focusable(false)
135                        .enabled(false)
136                        .build();
137        actions.add(mNextAction);
138        actions.add(
139                new GuidedAction.Builder(getActivity())
140                        .id(GuidedAction.ACTION_ID_CANCEL)
141                        .title("CANCEL")
142                        .build());
143    }
144
145    @Override
146    public void onGuidedActionClicked(GuidedAction action) {
147        if (action == null) {
148            return;
149        }
150        FragmentManager fm = getFragmentManager();
151        switch ((int) action.getId()) {
152            case (int) GuidedAction.ACTION_ID_NEXT:
153                LineupSelectionFragment fragment = new LineupSelectionFragment();
154                Bundle args = new Bundle();
155                args.putString(KEY_POSTCODE, getArguments().getString(KEY_POSTCODE));
156                ArrayList<String> channelNumbers = new ArrayList<>();
157                for (Channel channel : mChannels) {
158                    if (channel != null) {
159                        channelNumbers.add(channel.getDisplayNumber());
160                    }
161                }
162                args.putStringArrayList(KEY_CHANNEL_NUMBERS, channelNumbers);
163                fragment.setArguments(args);
164                GuidedStepFragment.add(getFragmentManager(), fragment);
165                break;
166            case (int) GuidedAction.ACTION_ID_CANCEL:
167                fm.popBackStack();
168                break;
169            default:
170        }
171    }
172
173    private List<Channel> scanChannel() {
174        List<Channel> channels = new ArrayList<>();
175        channels.add(createFakeChannel("KQED", "9.1", 1));
176        channels.add(createFakeChannel("BBC", "2-2", 2));
177        channels.add(createFakeChannel("What\'s on", "1.1", 3));
178        channels.add(createFakeChannel("FOX", "7.1", 4));
179        channels.add(createFakeChannel("Play Movie", "1.1", 5));
180        channels.add(createFakeChannel("PBS", "9.2", 6));
181        channels.add(createFakeChannel("ApiTestChannel", "A.1", 7));
182        updateChannels(channels);
183
184        return channels;
185    }
186
187    private void updateChannels(List<Channel> channels) {
188        HashMap<Integer, Channel> networkId2newChannel = new HashMap<>();
189        for (Channel channel : channels) {
190            networkId2newChannel.put(channel.getOriginalNetworkId(), channel);
191        }
192
193        ContentResolver cr = getActivity().getContentResolver();
194        Cursor cursor =
195                cr.query(
196                        TvContractCompat.buildChannelsUriForInput(SampleTvInputService.INPUT_ID),
197                        Channel.PROJECTION,
198                        null,
199                        null,
200                        null,
201                        null);
202        ArrayList<Channel> existingChannels = new ArrayList<>();
203        while (cursor.moveToNext()) {
204            existingChannels.add(Channel.fromCursor(cursor));
205        }
206        Iterator<Channel> i = existingChannels.iterator();
207        while (i.hasNext()) {
208            Channel existingChannel = i.next();
209            if (networkId2newChannel.containsKey(existingChannel.getOriginalNetworkId())) {
210                i.remove();
211                Channel newChannel =
212                        networkId2newChannel.remove(existingChannel.getOriginalNetworkId());
213                cr.update(
214                        TvContractCompat.buildChannelUri(existingChannel.getId()),
215                        newChannel.toContentValues(),
216                        null,
217                        null);
218            }
219        }
220        for (Channel newChannel : networkId2newChannel.values()) {
221            cr.insert(TvContractCompat.Channels.CONTENT_URI, newChannel.toContentValues());
222        }
223        for (Channel existingChannel : existingChannels) {
224            cr.delete(TvContractCompat.buildChannelUri(existingChannel.getId()), null, null);
225        }
226    }
227
228    private Channel createFakeChannel(String name, String number, int networkId) {
229        Channel channel =
230                new Channel.Builder()
231                        .setDisplayName(name)
232                        .setDisplayNumber(number)
233                        .setInputId(SampleTvInputService.INPUT_ID)
234                        .setOriginalNetworkId(networkId)
235                        .build();
236        onChannelDetected(channel);
237        SystemClock.sleep(1000);
238
239        return channel;
240    }
241
242    private void onChannelDetected(Channel channel) {
243        getActivity()
244                .runOnUiThread(
245                        new Runnable() {
246                            @Override
247                            public void run() {
248                                List<GuidedAction> actions = getActions();
249                                actions.add(
250                                        new GuidedAction.Builder(getActivity())
251                                                .id(ACTION_ID_SCAN_RESULTS)
252                                                .title(
253                                                        channel.getDisplayNumber()
254                                                                + " "
255                                                                + channel.getDisplayName())
256                                                .focusable(false)
257                                                .build());
258                                if (actions.size() >= 7) {
259                                    actions.remove(1);
260                                }
261                                setActions(actions);
262                            }
263                        });
264    }
265}
266