1 /*
2  * Copyright (C) 2019 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.annotation.TargetApi;
20 import android.app.DialogFragment;
21 import android.content.Context;
22 import android.os.Build;
23 import android.os.Bundle;
24 
25 import com.android.tv.MainActivity;
26 import com.android.tv.R;
27 import com.android.tv.TvSingletons;
28 import com.android.tv.data.ProgramImpl;
29 import com.android.tv.data.api.Program;
30 import com.android.tv.dialog.SafeDismissDialogFragment;
31 import com.android.tv.dvr.DvrManager;
32 import com.android.tv.dvr.data.ScheduledRecording;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.concurrent.TimeUnit;
37 
38 import androidx.leanback.app.GuidedStepFragment;
39 import androidx.leanback.widget.GuidanceStylist.Guidance;
40 import androidx.leanback.widget.GuidedAction;
41 import androidx.leanback.widget.GuidedActionsStylist;
42 
43 /** Fragment for DVR recording settings. */
44 @TargetApi(Build.VERSION_CODES.N)
45 @SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
46 public class DvrRecordingSettingsFragment extends GuidedStepFragment {
47     private static final String TAG = "RecordingSettingsFragment";
48 
49     private static final long ACTION_ID_START_EARLY = 100;
50     private static final long ACTION_ID_END_LATE = 101;
51 
52     private static final int SUB_ACTION_ID_START_ON_TIME = 1;
53     private static final int SUB_ACTION_ID_START_ONE_MIN = 2;
54     private static final int SUB_ACTION_ID_START_FIVE_MIN = 3;
55     private static final int SUB_ACTION_ID_START_FIFTEEN_MIN = 4;
56     private static final int SUB_ACTION_ID_START_HALF_HOUR = 5;
57 
58     private static final int SUB_ACTION_ID_END_ON_TIME = 6;
59     private static final int SUB_ACTION_ID_END_ONE_MIN = 7;
60     private static final int SUB_ACTION_ID_END_FIFTEEN_MIN = 8;
61     private static final int SUB_ACTION_ID_END_HALF_HOUR = 9;
62     private static final int SUB_ACTION_ID_END_ONE_HOUR = 10;
63     private static final int SUB_ACTION_ID_END_TWO_HOURS = 11;
64     private static final int SUB_ACTION_ID_END_THREE_HOURS = 12;
65 
66     private Program mProgram;
67     private String mFragmentTitle;
68     private String mStartEarlyActionTitle;
69     private String mEndLateActionTitle;
70     private String mTimeActionOnTimeText;
71     private String mTimeActionOneMinText;
72     private String mTimeActionFiveMinText;
73     private String mTimeActionFifteenMinText;
74     private String mTimeActionHalfHourText;
75     private String mTimeActionOneHourText;
76     private String mTimeActionTwoHoursText;
77     private String mTimeActionThreeHoursText;
78 
79     private GuidedAction mStartEarlyGuidedAction;
80     private GuidedAction mEndLateGuidedAction;
81     private long mStartEarlyTime = 0;
82     private long mEndLateTime = 0;
83     private DvrManager mDvrManager;
84 
85     @Override
onAttach(Context context)86     public void onAttach(Context context) {
87         super.onAttach(context);
88         mDvrManager = TvSingletons.getSingletons(getContext()).getDvrManager();
89         mProgram = getArguments().getParcelable(DvrRecordingSettingsActivity.PROGRAM);
90         if (mProgram == null) {
91             getActivity().finish();
92             return;
93         }
94         mFragmentTitle = getString(R.string.dvr_recording_settings_title);
95         mStartEarlyActionTitle = getString(R.string.dvr_start_early_title);
96         mEndLateActionTitle = getString(R.string.dvr_end_late_title);
97         mTimeActionOnTimeText = getString(R.string.dvr_recording_settings_time_none);
98         mTimeActionOneMinText = getString(R.string.dvr_recording_settings_time_one_min);
99         mTimeActionFiveMinText = getString(R.string.dvr_recording_settings_time_five_mins);
100         mTimeActionFifteenMinText = getString(R.string.dvr_recording_settings_time_fifteen_mins);
101         mTimeActionHalfHourText = getString(R.string.dvr_recording_settings_time_half_hour);
102         mTimeActionOneHourText = getString(R.string.dvr_recording_settings_time_one_hour);
103         mTimeActionTwoHoursText = getString(R.string.dvr_recording_settings_time_two_hours);
104         mTimeActionThreeHoursText = getString(R.string.dvr_recording_settings_time_three_hours);
105     }
106 
107     @Override
onCreateGuidance(Bundle savedInstanceState)108     public Guidance onCreateGuidance(Bundle savedInstanceState) {
109         String breadcrumb = mProgram.getTitle();
110         String title = mFragmentTitle;
111         String description = mProgram.getEpisodeTitle() + "\n" + mProgram.getDescription();
112         return new Guidance(title, description, breadcrumb, null);
113     }
114 
115     @Override
onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState)116     public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
117         mStartEarlyGuidedAction =
118                 new GuidedAction.Builder(getActivity())
119                         .id(ACTION_ID_START_EARLY)
120                         .title(mStartEarlyActionTitle)
121                         .description(mTimeActionOnTimeText)
122                         .subActions(buildChannelSubActionStart())
123                         .build();
124         actions.add(mStartEarlyGuidedAction);
125 
126         mEndLateGuidedAction =
127                 new GuidedAction.Builder(getActivity())
128                         .id(ACTION_ID_END_LATE)
129                         .title(mEndLateActionTitle)
130                         .description(mTimeActionOnTimeText)
131                         .subActions(buildChannelSubActionEnd())
132                         .build();
133         actions.add(mEndLateGuidedAction);
134     }
135 
136     @Override
onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState)137     public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
138         actions.add(
139                 new GuidedAction.Builder(getActivity())
140                         .clickAction(GuidedAction.ACTION_ID_OK)
141                         .build());
142         actions.add(
143                 new GuidedAction.Builder(getActivity())
144                         .clickAction(GuidedAction.ACTION_ID_CANCEL)
145                         .build());
146     }
147 
148     @Override
onGuidedActionClicked(GuidedAction action)149     public void onGuidedActionClicked(GuidedAction action) {
150         long actionId = action.getId();
151         if (actionId == GuidedAction.ACTION_ID_OK) {
152             long startEarlyTimeMs = TimeUnit.MINUTES.toMillis(mStartEarlyTime);
153             long endLateTimeMs = TimeUnit.MINUTES.toMillis(mEndLateTime);
154             long startTimeMs = mProgram.getStartTimeUtcMillis() - startEarlyTimeMs;
155             if (startTimeMs < System.currentTimeMillis()) {
156                 startTimeMs = System.currentTimeMillis();
157                 startEarlyTimeMs = mProgram.getStartTimeUtcMillis() - startTimeMs;
158             }
159             long endTimeMs = mProgram.getEndTimeUtcMillis() + endLateTimeMs;
160             Program customizedProgram =
161                     new ProgramImpl.Builder(mProgram)
162                         .setStartTimeUtcMillis(startTimeMs)
163                         .setEndTimeUtcMillis(endTimeMs)
164                         .build();
165             mDvrManager.addSchedule(customizedProgram , startEarlyTimeMs, endLateTimeMs);
166             List<ScheduledRecording> conflicts =
167                     mDvrManager.getConflictingSchedules(customizedProgram);
168             if (conflicts.isEmpty()) {
169                 DvrUiHelper.showAddScheduleToast(
170                         getContext(),
171                         customizedProgram.getTitle(),
172                         customizedProgram.getStartTimeUtcMillis(),
173                         customizedProgram.getEndTimeUtcMillis());
174                 dismissDialog();
175                 finishGuidedStepFragments();
176             } else {
177                 DvrUiHelper.showScheduleConflictDialog(getActivity(), customizedProgram);
178             }
179         } else if (actionId == GuidedAction.ACTION_ID_CANCEL) {
180             finishGuidedStepFragments();
181         }
182     }
183 
184     @Override
onSubGuidedActionClicked(GuidedAction action)185     public boolean onSubGuidedActionClicked(GuidedAction action) {
186         long actionId = action.getId();
187         switch ((int) actionId) {
188             case SUB_ACTION_ID_START_ON_TIME :
189                 mStartEarlyTime = 0;
190                 updateGuidedActions(true, mTimeActionOnTimeText);
191                 break;
192             case SUB_ACTION_ID_START_ONE_MIN :
193                 mStartEarlyTime = 1;
194                 updateGuidedActions(true, mTimeActionOneMinText);
195                 break;
196             case SUB_ACTION_ID_START_FIVE_MIN :
197                 mStartEarlyTime = 5;
198                 updateGuidedActions(true, mTimeActionFiveMinText);
199                 break;
200             case SUB_ACTION_ID_START_FIFTEEN_MIN :
201                 mStartEarlyTime = 15;
202                 updateGuidedActions(true, mTimeActionFifteenMinText);
203                 break;
204             case SUB_ACTION_ID_START_HALF_HOUR :
205                 mStartEarlyTime = 30;
206                 updateGuidedActions(true, mTimeActionHalfHourText);
207                 break;
208             case SUB_ACTION_ID_END_ON_TIME :
209                 mEndLateTime = 0;
210                 updateGuidedActions(false, mTimeActionOnTimeText);
211                 break;
212             case SUB_ACTION_ID_END_ONE_MIN :
213                 mEndLateTime = 1;
214                 updateGuidedActions(false, mTimeActionOneMinText);
215                 break;
216             case SUB_ACTION_ID_END_FIFTEEN_MIN :
217                 mEndLateTime = 15;
218                 updateGuidedActions(false, mTimeActionFifteenMinText);
219                 break;
220             case SUB_ACTION_ID_END_HALF_HOUR :
221                 mEndLateTime = 30;
222                 updateGuidedActions(false, mTimeActionHalfHourText);
223                 break;
224             case SUB_ACTION_ID_END_ONE_HOUR :
225                 mEndLateTime = 60;
226                 updateGuidedActions(false, mTimeActionOneHourText);
227                 break;
228             case SUB_ACTION_ID_END_TWO_HOURS :
229                 mEndLateTime = 120;
230                 updateGuidedActions(false, mTimeActionTwoHoursText);
231                 break;
232             case SUB_ACTION_ID_END_THREE_HOURS :
233                 mEndLateTime = 180;
234                 updateGuidedActions(false, mTimeActionThreeHoursText);
235                 break;
236             default :
237                 mStartEarlyTime = 0;
238                 mEndLateTime = 0;
239                 updateGuidedActions(true, mTimeActionOnTimeText);
240                 updateGuidedActions(false, mTimeActionOnTimeText);
241                 break;
242         }
243         return true;
244     }
245 
updateGuidedActions(boolean start, CharSequence description)246     private void updateGuidedActions(boolean start, CharSequence description) {
247         if (start) {
248             mStartEarlyGuidedAction.setDescription(description);
249             notifyActionChanged(findActionPositionById(ACTION_ID_START_EARLY));
250         } else {
251             mEndLateGuidedAction.setDescription(description);
252             notifyActionChanged(findActionPositionById(ACTION_ID_END_LATE));
253         }
254     }
255 
256     @Override
onCreateButtonActionsStylist()257     public GuidedActionsStylist onCreateButtonActionsStylist() {
258         return new DvrGuidedActionsStylist(true);
259     }
260 
buildChannelSubActionStart()261     private List<GuidedAction> buildChannelSubActionStart() {
262         List<GuidedAction> timeSubActions = new ArrayList<>();
263         timeSubActions.add(
264                 new GuidedAction.Builder(getActivity())
265                         .id(SUB_ACTION_ID_START_ON_TIME)
266                         .title(mTimeActionOnTimeText)
267                         .build());
268         timeSubActions.add(
269                 new GuidedAction.Builder(getActivity())
270                         .id(SUB_ACTION_ID_START_ONE_MIN)
271                         .title(mTimeActionOneMinText)
272                         .build());
273         timeSubActions.add(
274                 new GuidedAction.Builder(getActivity())
275                         .id(SUB_ACTION_ID_START_FIVE_MIN)
276                         .title(mTimeActionFiveMinText)
277                         .build());
278         timeSubActions.add(
279                 new GuidedAction.Builder(getActivity())
280                         .id(SUB_ACTION_ID_START_FIFTEEN_MIN)
281                         .title(mTimeActionFifteenMinText)
282                         .build());
283         timeSubActions.add(
284                 new GuidedAction.Builder(getActivity())
285                         .id(SUB_ACTION_ID_START_HALF_HOUR)
286                         .title(mTimeActionHalfHourText)
287                         .build());
288         return timeSubActions;
289     }
290 
buildChannelSubActionEnd()291     private List<GuidedAction> buildChannelSubActionEnd() {
292         List<GuidedAction> timeSubActions = new ArrayList<>();
293         timeSubActions.add(
294                 new GuidedAction.Builder(getActivity())
295                         .id(SUB_ACTION_ID_END_ON_TIME)
296                         .title(mTimeActionOnTimeText)
297                         .build());
298         timeSubActions.add(
299                 new GuidedAction.Builder(getActivity())
300                         .id(SUB_ACTION_ID_END_ONE_MIN)
301                         .title(mTimeActionOneMinText)
302                         .build());
303         timeSubActions.add(
304                 new GuidedAction.Builder(getActivity())
305                         .id(SUB_ACTION_ID_END_FIFTEEN_MIN)
306                         .title(mTimeActionFifteenMinText)
307                         .build());
308         timeSubActions.add(
309                 new GuidedAction.Builder(getActivity())
310                         .id(SUB_ACTION_ID_END_HALF_HOUR)
311                         .title(mTimeActionHalfHourText)
312                         .build());
313         timeSubActions.add(
314                 new GuidedAction.Builder(getActivity())
315                         .id(SUB_ACTION_ID_END_ONE_HOUR)
316                         .title(mTimeActionOneHourText)
317                         .build());
318         timeSubActions.add(
319                 new GuidedAction.Builder(getActivity())
320                         .id(SUB_ACTION_ID_END_TWO_HOURS)
321                         .title(mTimeActionTwoHoursText)
322                         .build());
323         timeSubActions.add(
324                 new GuidedAction.Builder(getActivity())
325                         .id(SUB_ACTION_ID_END_THREE_HOURS)
326                         .title(mTimeActionThreeHoursText)
327                         .build());
328         return timeSubActions;
329     }
330 
dismissDialog()331     protected void dismissDialog() {
332         if (getActivity() instanceof MainActivity) {
333             SafeDismissDialogFragment currentDialog =
334                     ((MainActivity) getActivity()).getOverlayManager().getCurrentDialog();
335             if (currentDialog instanceof DvrHalfSizedDialogFragment) {
336                 currentDialog.dismiss();
337             }
338         } else if (getParentFragment() instanceof DialogFragment) {
339             ((DialogFragment) getParentFragment()).dismiss();
340         }
341     }
342 }
343