1/*
2 * Copyright (C) 2016 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.dvr.ui;
18
19import android.graphics.drawable.Drawable;
20import android.media.tv.TvInputInfo;
21import android.os.Bundle;
22import android.support.annotation.NonNull;
23import android.support.annotation.Nullable;
24import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
25import android.support.v17.leanback.widget.GuidedAction;
26import android.util.Log;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.view.ViewGroup;
30
31import com.android.tv.MainActivity;
32import com.android.tv.R;
33import com.android.tv.TvApplication;
34import com.android.tv.common.SoftPreconditions;
35import com.android.tv.data.Channel;
36import com.android.tv.data.Program;
37import com.android.tv.dvr.ConflictChecker;
38import com.android.tv.dvr.ConflictChecker.OnUpcomingConflictChangeListener;
39import com.android.tv.dvr.DvrUiHelper;
40import com.android.tv.dvr.ScheduledRecording;
41import com.android.tv.util.Utils;
42
43import java.util.ArrayList;
44import java.util.Collections;
45import java.util.HashSet;
46import java.util.List;
47
48public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
49    private static final String TAG = "DvrConflictFragment";
50    private static final boolean DEBUG = false;
51
52    private static final int ACTION_DELETE_CONFLICT = 1;
53    private static final int ACTION_CANCEL = 2;
54    private static final int ACTION_VIEW_SCHEDULES = 3;
55
56    // The program count which will be listed in the description. This is the number of the
57    // program strings in R.plurals.dvr_program_conflict_dialog_description_many.
58    private static final int LISTED_PROGRAM_COUNT = 2;
59
60    protected List<ScheduledRecording> mConflicts;
61
62    void setConflicts(List<ScheduledRecording> conflicts) {
63        mConflicts = conflicts;
64    }
65
66    List<ScheduledRecording> getConflicts() {
67        return mConflicts;
68    }
69
70    @Override
71    public int onProvideTheme() {
72        return R.style.Theme_TV_Dvr_Conflict_GuidedStep;
73    }
74
75    @Override
76    public void onCreateActions(@NonNull List<GuidedAction> actions,
77            Bundle savedInstanceState) {
78        actions.add(new GuidedAction.Builder(getContext())
79                .clickAction(GuidedAction.ACTION_ID_OK)
80                .build());
81        actions.add(new GuidedAction.Builder(getContext())
82                .id(ACTION_VIEW_SCHEDULES)
83                .title(R.string.dvr_action_view_schedules)
84                .build());
85    }
86
87    @Override
88    public void onGuidedActionClicked(GuidedAction action) {
89        if (action.getId() == ACTION_VIEW_SCHEDULES) {
90            DvrUiHelper.startSchedulesActivityForOneTimeRecordingConflict(
91                    getContext(), getConflicts());
92        }
93        dismissDialog();
94    }
95
96    String getConflictDescription() {
97        List<String> titles = new ArrayList<>();
98        HashSet<String> titleSet = new HashSet<>();
99        for (ScheduledRecording schedule : getConflicts()) {
100            String scheduleTitle = getScheduleTitle(schedule);
101            if (scheduleTitle != null && !titleSet.contains(scheduleTitle)) {
102                titles.add(scheduleTitle);
103                titleSet.add(scheduleTitle);
104            }
105        }
106        switch (titles.size()) {
107            case 0:
108                Log.i(TAG, "Conflict has been resolved by any reason. Maybe input might have"
109                        + " been deleted.");
110                return null;
111            case 1:
112                return getResources().getString(
113                        R.string.dvr_program_conflict_dialog_description_1, titles.get(0));
114            case 2:
115                return getResources().getString(
116                        R.string.dvr_program_conflict_dialog_description_2, titles.get(0),
117                        titles.get(1));
118            case 3:
119                return getResources().getString(
120                        R.string.dvr_program_conflict_dialog_description_3, titles.get(0),
121                        titles.get(1));
122            default:
123                return getResources().getQuantityString(
124                        R.plurals.dvr_program_conflict_dialog_description_many,
125                        titles.size() - LISTED_PROGRAM_COUNT, titles.get(0), titles.get(1),
126                        titles.size() - LISTED_PROGRAM_COUNT);
127        }
128    }
129
130    @Nullable
131    private String getScheduleTitle(ScheduledRecording schedule) {
132        if (schedule.getType() == ScheduledRecording.TYPE_TIMED) {
133            Channel channel = TvApplication.getSingletons(getContext()).getChannelDataManager()
134                    .getChannel(schedule.getChannelId());
135            if (channel != null) {
136                return channel.getDisplayName();
137            } else {
138                return null;
139            }
140        } else {
141            return schedule.getProgramTitle();
142        }
143    }
144
145    /**
146     * A fragment to show the program conflict.
147     */
148    public static class DvrProgramConflictFragment extends DvrConflictFragment {
149        private Program mProgram;
150        @Override
151        public View onCreateView(LayoutInflater inflater, ViewGroup container,
152                Bundle savedInstanceState) {
153            Bundle args = getArguments();
154            if (args != null) {
155                mProgram = args.getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM);
156            }
157            SoftPreconditions.checkArgument(mProgram != null);
158            TvInputInfo input = Utils.getTvInputInfoForProgram(getContext(), mProgram);
159            SoftPreconditions.checkNotNull(input);
160            List<ScheduledRecording> conflicts = null;
161            if (input != null) {
162                conflicts = TvApplication.getSingletons(getContext()).getDvrManager()
163                        .getConflictingSchedules(mProgram);
164            }
165            if (conflicts == null) {
166                conflicts = Collections.emptyList();
167            }
168            if (conflicts.isEmpty()) {
169                dismissDialog();
170            }
171            setConflicts(conflicts);
172            return super.onCreateView(inflater, container, savedInstanceState);
173        }
174
175        @NonNull
176        @Override
177        public Guidance onCreateGuidance(Bundle savedInstanceState) {
178            String title = getResources().getString(R.string.dvr_program_conflict_dialog_title);
179            String descriptionPrefix = getString(
180                    R.string.dvr_program_conflict_dialog_description_prefix, mProgram.getTitle());
181            String description = getConflictDescription();
182            if (description == null) {
183                dismissDialog();
184            }
185            Drawable icon = getResources().getDrawable(R.drawable.ic_error_white_48dp, null);
186            return new Guidance(title, descriptionPrefix + " " + description, null, icon);
187        }
188    }
189
190    /**
191     * A fragment to show the channel recording conflict.
192     */
193    public static class DvrChannelRecordConflictFragment extends DvrConflictFragment {
194        private Channel mChannel;
195        private long mStartTimeMs;
196        private long mEndTimeMs;
197
198        @Override
199        public View onCreateView(LayoutInflater inflater, ViewGroup container,
200                Bundle savedInstanceState) {
201            Bundle args = getArguments();
202            long channelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID);
203            mChannel = TvApplication.getSingletons(getContext()).getChannelDataManager()
204                    .getChannel(channelId);
205            SoftPreconditions.checkArgument(mChannel != null);
206            TvInputInfo input = Utils.getTvInputInfoForChannelId(getContext(), mChannel.getId());
207            SoftPreconditions.checkNotNull(input);
208            List<ScheduledRecording> conflicts = null;
209            if (input != null) {
210                mStartTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_START_TIME_MS);
211                mEndTimeMs = args.getLong(DvrHalfSizedDialogFragment.KEY_END_TIME_MS);
212                conflicts = TvApplication.getSingletons(getContext()).getDvrManager()
213                        .getConflictingSchedules(mChannel.getId(), mStartTimeMs, mEndTimeMs);
214            }
215            if (conflicts == null) {
216                conflicts = Collections.emptyList();
217            }
218            if (conflicts.isEmpty()) {
219                dismissDialog();
220            }
221            setConflicts(conflicts);
222            return super.onCreateView(inflater, container, savedInstanceState);
223        }
224
225        @NonNull
226        @Override
227        public Guidance onCreateGuidance(Bundle savedInstanceState) {
228            String title = getResources().getString(R.string.dvr_channel_conflict_dialog_title);
229            String descriptionPrefix = getString(
230                    R.string.dvr_channel_conflict_dialog_description_prefix,
231                    mChannel.getDisplayName());
232            String description = getConflictDescription();
233            if (description == null) {
234                dismissDialog();
235            }
236            Drawable icon = getResources().getDrawable(R.drawable.ic_error_white_48dp, null);
237            return new Guidance(title, descriptionPrefix + " " + description, null, icon);
238        }
239    }
240
241    /**
242     * A fragment to show the channel watching conflict.
243     * <p>
244     * This fragment is automatically closed when there are no upcoming conflicts.
245     */
246    public static class DvrChannelWatchConflictFragment extends DvrConflictFragment
247            implements OnUpcomingConflictChangeListener {
248        private long mChannelId;
249
250        @Override
251        public View onCreateView(LayoutInflater inflater, ViewGroup container,
252                Bundle savedInstanceState) {
253            Bundle args = getArguments();
254            if (args != null) {
255                mChannelId = args.getLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID);
256            }
257            SoftPreconditions.checkArgument(mChannelId != Channel.INVALID_ID);
258            ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker();
259            List<ScheduledRecording> conflicts = null;
260            if (checker != null) {
261                checker.addOnUpcomingConflictChangeListener(this);
262                conflicts = checker.getUpcomingConflicts();
263                if (DEBUG) Log.d(TAG, "onCreateView: upcoming conflicts: " + conflicts);
264                if (conflicts.isEmpty()) {
265                    dismissDialog();
266                }
267            }
268            if (conflicts == null) {
269                if (DEBUG) Log.d(TAG, "onCreateView: There's no conflict.");
270                conflicts = Collections.emptyList();
271            }
272            if (conflicts.isEmpty()) {
273                dismissDialog();
274            }
275            setConflicts(conflicts);
276            return super.onCreateView(inflater, container, savedInstanceState);
277        }
278
279        @NonNull
280        @Override
281        public Guidance onCreateGuidance(Bundle savedInstanceState) {
282            String title = getResources().getString(
283                    R.string.dvr_epg_channel_watch_conflict_dialog_title);
284            String description = getResources().getString(
285                    R.string.dvr_epg_channel_watch_conflict_dialog_description);
286            return new Guidance(title, description, null, null);
287        }
288
289        @Override
290        public void onCreateActions(@NonNull List<GuidedAction> actions,
291                Bundle savedInstanceState) {
292            actions.add(new GuidedAction.Builder(getContext())
293                    .id(ACTION_DELETE_CONFLICT)
294                    .title(R.string.dvr_action_delete_schedule)
295                    .build());
296            actions.add(new GuidedAction.Builder(getContext())
297                    .id(ACTION_CANCEL)
298                    .title(R.string.dvr_action_record_program)
299                    .build());
300        }
301
302        @Override
303        public void onGuidedActionClicked(GuidedAction action) {
304            if (action.getId() == ACTION_CANCEL) {
305                ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker();
306                if (checker != null) {
307                    checker.setCheckedConflictsForChannel(mChannelId, getConflicts());
308                }
309            } else if (action.getId() == ACTION_DELETE_CONFLICT) {
310                for (ScheduledRecording schedule : mConflicts) {
311                    if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
312                        getDvrManager().stopRecording(schedule);
313                    } else {
314                        getDvrManager().removeScheduledRecording(schedule);
315                    }
316                }
317            }
318            super.onGuidedActionClicked(action);
319        }
320
321        @Override
322        public void onDetach() {
323            ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker();
324            if (checker != null) {
325                checker.removeOnUpcomingConflictChangeListener(this);
326            }
327            super.onDetach();
328        }
329
330        @Override
331        public void onUpcomingConflictChange() {
332            ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker();
333            if (checker == null || checker.getUpcomingConflicts().isEmpty()) {
334                if (DEBUG) Log.d(TAG, "onUpcomingConflictChange: There's no conflict.");
335                dismissDialog();
336            }
337        }
338    }
339}
340