• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.tv.dvr.ui;
18 
19 import android.graphics.drawable.Drawable;
20 import android.media.tv.TvInputInfo;
21 import android.os.Bundle;
22 import android.support.annotation.NonNull;
23 import android.support.annotation.Nullable;
24 import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
25 import android.support.v17.leanback.widget.GuidedAction;
26 import android.util.Log;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 
31 import com.android.tv.MainActivity;
32 import com.android.tv.R;
33 import com.android.tv.TvApplication;
34 import com.android.tv.common.SoftPreconditions;
35 import com.android.tv.data.Channel;
36 import com.android.tv.data.Program;
37 import com.android.tv.dvr.ConflictChecker;
38 import com.android.tv.dvr.ConflictChecker.OnUpcomingConflictChangeListener;
39 import com.android.tv.dvr.DvrUiHelper;
40 import com.android.tv.dvr.ScheduledRecording;
41 import com.android.tv.util.Utils;
42 
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.HashSet;
46 import java.util.List;
47 
48 public 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 
setConflicts(List<ScheduledRecording> conflicts)62     void setConflicts(List<ScheduledRecording> conflicts) {
63         mConflicts = conflicts;
64     }
65 
getConflicts()66     List<ScheduledRecording> getConflicts() {
67         return mConflicts;
68     }
69 
70     @Override
onProvideTheme()71     public int onProvideTheme() {
72         return R.style.Theme_TV_Dvr_Conflict_GuidedStep;
73     }
74 
75     @Override
onCreateActions(@onNull List<GuidedAction> actions, Bundle savedInstanceState)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
onGuidedActionClicked(GuidedAction action)88     public void onGuidedActionClicked(GuidedAction action) {
89         if (action.getId() == ACTION_VIEW_SCHEDULES) {
90             DvrUiHelper.startSchedulesActivityForOneTimeRecordingConflict(
91                     getContext(), getConflicts());
92         }
93         dismissDialog();
94     }
95 
getConflictDescription()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
getScheduleTitle(ScheduledRecording schedule)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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onCreateGuidance(Bundle savedInstanceState)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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onCreateGuidance(Bundle savedInstanceState)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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onCreateGuidance(Bundle savedInstanceState)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
onCreateActions(@onNull List<GuidedAction> actions, Bundle savedInstanceState)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
onGuidedActionClicked(GuidedAction action)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
onDetach()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
onUpcomingConflictChange()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